> For the complete documentation index, see [llms.txt](https://iqeq00.gitbook.io/microservices-book/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://iqeq00.gitbook.io/microservices-book/springboot/configuration.md).

# @Configuration

所在包：org.springframework.context.annotation

所在 jar：spring-context.jar（5.1.6.RELEASE）

用于进行配置，修饰类，标示某一个类是配置类。

## 源码

```java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {

    /**
     * Explicitly specify the name of the Spring bean definition associated with the
     * {@code @Configuration} class. If left unspecified (the common case), a bean
     * name will be automatically generated.
     * <p>The custom name applies only if the {@code @Configuration} class is picked
     * up via component scanning or supplied directly to an
     * {@link AnnotationConfigApplicationContext}. If the {@code @Configuration} class
     * is registered as a traditional XML bean definition, the name/id of the bean
     * element will take precedence.
     * @return the explicit component name, if any (or empty String otherwise)
     * @see org.springframework.beans.factory.support.DefaultBeanNameGenerator
     */
    @AliasFor(annotation = Component.class)
    String value() default "";

}
```

标识一个 java 类声明了一个或者多个被 @Bean 注解标示的方法，而且可以被 Spring 的容器进行处理，生成 Bean 的定义，以及针对于运行期的 Bean 服务请求。

## Java Config 实现方式

```java
     @Configuration
   public class AppConfig {

       @Bean
       public MyBean myBean() {
           // instantiate, configure and return bean ...
       }
   }
```

@Configuration 类通常是要么通过 AnnotationConfigApplicationContext ，要么通过 AnnotationConfigWebApplicationContext 来启动、实现的。

针对之前的代码的一个简单实例：

```java
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
   ctx.register(AppConfig.class);
   ctx.refresh();
   MyBean myBean = ctx.getBean(MyBean.class);
   // use myBean ...
```

首先创造一个 AnnotationConfigApplicationContext 应用上下文的实例，接着把 Java Config 配置类注册到应用上下文当中，刷新应用上下文以后就可以通过 getBean 方法获取（从 Spring 容器内）之前定义好的 MyBean 实例，根据后面的请求，进行 MyBean 操作。

**这也解释了，我们在 Spring Boot 应用当中，所有的 Java 配置类，怎么就能被 Spring Boot 或者 Spring 找到的。**

## Xml 实现方式

```markup
<beans>
    <context:annotation-config/>
    <bean class="com.acme.AppConfig"/>
</beans>
```

传统方式，在 Java Config 兴起以前，当时的标准就是使用 xml 来实现配置。在 Spring Boot 当中也是支持 xml 方式的，但是很少会这么用，通常都是使用注解方式。

```markup
<context:annotation-config/>
```

xml 方式必须要加上上面的命名空间，才能起效果。

## @ComponentScan

组件扫描注解，修饰 @SpringBootApplication 注解。这个注解，主要是扫描应用的所有组件。

对于 Spring Boot 应用来说，常常会把包含 main 方法的这个类，也就是启动类，定义在一个包的最顶层。其他的控制器、服务层、数据层、组件层都定义在启动类的子包里。

因为根据注解扫描原则，注解会扫描这个包本身以及这个包的所有子包。所以一般 Spring Boot 的启动类都是位于整个应用的最顶层，应用的其他文件都在它的子包内。

## 使用环境 API

外部的值，可以通过把 org.springframework.core.env.Environment 组件注入到 @Configuration 所在的类当中，从而使用它们。

比如使用自动装配注解来注入：

### @Autowired

自动装配注解，自动装配到所需的类里面。

```java
   @Configuration
   public class AppConfig {

       @Autowired 
       Environment env;

       @Bean
       public MyBean myBean() {
           MyBean myBean = new MyBean();
           myBean.setName(env.getProperty("bean.name"));
           return myBean;
       }
   }
```

这里的 env 被 @Autowired 自动装配到 AppConfig 类里，而 myBean 的 name 值就是通过 `env.getProperty("bean.name")` 的方式来获取的外部值。

### @PropertySource

属性源注解。通过环境对象（Environment）去解析的属性位于一个或者多个 属性源（property source）对象当中，而且被 @Configuration 修饰的类还可以通过使用 @PropertySource 注解贡献属性源（property source）到环境对象（Environment）中。

也就是说，只要通过 @PropertySource 指定了外部文件，那么就可以使用外部文件里面的值了。

```java
   @Configuration
   @PropertySource("classpath:/com/acme/app.properties")
   public class AppConfig {

       @Inject 
       Environment env;

       @Bean
       public MyBean myBean() {
           return new MyBean(env.getProperty("bean.name"));
       }
   }
```

例如上面的 `bean.name` 就是定义在 classpath:/com/acme/app.properties 外部文件里面的值。

### @Value

值注解。外部的值可以通过值注解注入到 @Configuration 修饰的类中。

```java
   @Configuration
   @PropertySource("classpath:/com/acme/app.properties")
   public class AppConfig {

       @Value("${bean.name}") 
       String beanName;

       @Bean
       public MyBean myBean() {
           return new MyBean(beanName);
       }
   }
```

当实例化或者加载 AppConfig 类实例的时候，就会从 app.properties 外部配置当中，找到 bean.name 的配置信息，并把值设置给 beanName。

这种方式经常会与 Spring 的 PropertySourcesPlaceholderConfigurer 搭配使用。

## @Import

导入注解。可以通过它来组合两个或者多个被 @Configuration 修饰的类，把多个配置类组合到一起，目的是实现配置的拆分。

类似于 Spring XML 配置方式中的 `<import>` 标签。

由于 @Configuration 对象，是做为一个 Spring Bean 对象在容器当中被管理的，因此被导入的 @Configuration 对象（配置）也是可以被注入的，比如通过构造器来注入。

```java
   @Configuration
   public class DatabaseConfig {

       @Bean
       public DataSource dataSource() {
           // instantiate, configure and return DataSource
       }
   }

   @Configuration
   @Import(DatabaseConfig.class)
   public class AppConfig {

       private final DatabaseConfig dataConfig;

       public AppConfig(DatabaseConfig dataConfig) {
           this.dataConfig = dataConfig;
       }

       @Bean
       public MyBean myBean() {
           // reference the dataSource() bean method
           return new MyBean(dataConfig.dataSource());
       }
   }
```

DatabaseConfig 做为 AppConfig 的一个变量，通过构造方法注入进来，然后就可以使用 DatabaseConfig 相关的配置信息了。

### 启动

现在，不管是 AppConfig，还是被导入的 DatabaseConfig 都可以通过注册到 Spring Context 当中来启动。只需要注册 AppConfig 就可以了，因为 DatabaseConfig 已经被包含在 AppConfig 中了。

启动方法：给 Spring Context 注册导入的 @Configuration 类。

```java
new AnnotationConfigApplicationContext(AppConfig.class);
```

## @Profile

多环境注解。实际的项目当中，至少分为开发、测试、生产三种环境，每种环境的配置信息，应该都是不一样的，比如数据库、缓存等信息。

@Configuration 所修饰的类还可以被 @Profile 修饰，来指定不同环境信息。

```java
   @Profile("development")
   @Configuration
   public class EmbeddedDatabaseConfig {

       @Bean
       public DataSource dataSource() {
           // instantiate, configure and return embedded DataSource
       }
   }

   @Profile("production")
   @Configuration
   public class ProductionDatabaseConfig {

       @Bean
       public DataSource dataSource() {
           // instantiate, configure and return production DataSource
       }
   }
```

上面的例子分别声明了开发阶段和生产阶段的数据源配置。

在 Spring Bean 的方法层次上，还可以声明一些 @Profile 的条件。

```java
   @Configuration
   public class ProfileDatabaseConfig {

       @Bean("dataSource")
       @Profile("development")
       public DataSource embeddedDatabase() { ... }

       @Bean("dataSource")
       @Profile("production")
       public DataSource productionDatabase() { ... }
   }
```

都是叫做 dataSource 的 Spring Bean，配置信息也在同一个 @Configuration 类当中，只是通过 @Profile 的值不同来区分。但是这是一个条件判断，当前环境如果是 development，DataSource Spring Bean 就是 embeddedDatabase，而当前环境如果是 production，DataSource Spring Bean 就是 productionDatabase。

上下两种方式，本质上都是一样的，只是上面的配置方式需要两个 @Configuration 配置类，而下面的方式，一个 @Configuration 配置类就可以了。

## @ImportResource

导入配置文件注解。@Configuration 类也可以使用 @ImportResource 注解将 Spring XML 配置文件导入 @Configuration类。

```java
   @Configuration
   @ImportResource("classpath:/com/acme/database-config.xml")
   public class AppConfig {

       @Inject DataSource dataSource; // from XML

       @Bean
       public MyBean myBean() {
           // inject the XML-defined dataSource bean
           return new MyBean(this.dataSource);
       }
   }
```

database-config.xml 配置文件中的内容就被导入到 AppConfig 当中了。

用途不是特别大，不过对于老系统的架构迁移来说，也是一种解决方案，比如从 Spring XML 架构迁移到 Spring Boot 架构。

## 嵌套

@Configuration 类可以相互嵌套。

```java
   @Configuration
   public class AppConfig {

       @Inject 
       DataSource dataSource;

       @Bean
       public MyBean myBean() {
           return new MyBean(dataSource);
       }

       @Configuration
       static class DatabaseConfig {
           @Bean
           DataSource dataSource() {
               return new EmbeddedDatabaseBuilder().build();
           }
       }
   }
```

内层的 DataSource 也会自动注入到外层的 dataSource 当中。

在这种情况下，仅需要针对应用程序上下文注册 AppConfig。由于是嵌套的 @Configuration 类，因此将自动注册 DatabaseConfig。当 AppConfig 和 DatabaseConfig 之间的关系已经很明显的时候，就无需使用 @Import 注解了。

此外，嵌套的 @Configuration 类可以与 @Profile 注解一起搭配使用，为内层的 @Configuration 类提供同一配置 bean 的两个不同选择，比如开发环境和测试环境，相同 bean 的两种选择。

在一个 Spring Boot 的项目中，基本都会存在一个 config 的代码包。里面存放着项目的各种各样配置类，而配置的具体值，很可能放置在外部的 properties 或者 yml 文件中。

## 延迟加载

默认情况下，在容器启动的时候，被 @Bean 注解修饰的方法会提早实例化。为了避免这种情况，而是用到的时候才初始化。@Configuration 注解可以与 @Lazy 注解搭配使用，用来标识所有的被 @Bean 注解所修饰的方法，在默认情况下都是延迟初始化的。另外 @Lazy 也可以用于单个的 @Bean 方法搭配使用。也就是如果 @Lazy 修饰类，类中所有的 @Bean 方法都是延迟初始化，如果 @Lazy 修饰具体的一个 @Bean 方法，只有这个方法才是延迟初始化。

## 测试

Spring 测试模块中提供的 Spring TestContext 框架提供了 @ContextConfiguration 注解，它可以接受@Configuration 类对象的数组。

```java
     @RunWith(SpringRunner.class)
   @ContextConfiguration(classes = {AppConfig.class, DatabaseConfig.class})
   public class MyTests {

       @Autowired MyBean myBean;

       @Autowired DataSource dataSource;

       @Test
       public void test() {
           // assertions against myBean ...
       }
   }
```

## 约束

@Configuration 所修饰的类是有几点约束。

* @Configuration 必须修饰类。
* @Configuration 类必须是非最终类，不能被 final 修饰。
* @Configuration 类必须是非本地的（即不得在方法中声明）。
* 嵌套的 @Configuration 类必须声明为静态，被 static 修饰。
* @Bean 方法可能不会创建更多的配置类（任何这样的实例都将被视为常规 Bean，其配置注释保持未被检测到）。

## 结尾

在 Spring Boot 的开发中，这种 @Configuration 类是一定会出现的。有些配置是使用了某些外部 jar 配置的，还有些配置是根据项目实际需要自定义的。自定义的配置一定要以 @Configuration 这种方式来应用。
