# 1.12 基于Java的容器配置

这节包括了如何在Java代码里面使用注解来配置Spring容器。

## 1.12.1 基本概念：@Bean 和 @Configuration

Spring新的Java配置支持中的中心构件是@Configuration和@Bean。

@Bean注解用于指示方法实例化、配置和初始化要由Spring IOC容器管理的新对象。对于熟悉Spring的\<beans/>XML配置的用户，@Bean注解与\<bean/>元素具有相同的作用。你可以将@Bean注解的方法与任何spring @Component一起使用。但是，它们最常用于@Configuration bean。

用@Configuration注解类表明它的主要用途是作为bean定义的源。此外，@Configuration classes允许通过调用同一类中的其他@Bean方法来定义bean间的依赖关系。最简单的@Configuration类如下：

```
@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}
```

上面的AppConfig类和下面的XML配置相同：

```
<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
```

> 完整的@Configuration与“lite”@Bean模式？
>
> 当@Bean方法在没有用@Configuration注解的类中声明时，它们被称为在“lite”模式下处理。在@Component甚至是普通老类中声明的bean方法被认为是“lite”，因为包含类的主要目的不同，@Bean方法在这里可以看做一种奖励。例如，服务组件可以通过在每个适用的组件类上附加的@Bean方法向容器公开管理视图。在这种情况下，@Bean方法是一种通用的工厂方法机制。
>
> 与完整的@Configuration不同，lite\@Bean方法不能声明bean之间的依赖关系。相反，它们对其包含组件的内部状态进行操作，也可以选择对可能声明的参数进行操作。因此，这样的@Bean方法不应调用其他@Bean方法。每个这样的方法实际上只是特定bean引用的工厂方法，没有任何特殊的运行时语义。这里的积极副作用是不需要在运行时应用cglib子类化，所以在类设计方面没有限制（也就是说，包含类可能是最终的等等）。
>
> 在常见的场景中，@Bean方法将在@Configuration类中声明，确保始终使用“full”模式，因此跨方法引用将被重定向到容器的生命周期管理。这防止了通过常规Java调用意外调用相同的bean方法，这有助于减少在“Lite”模式下操作时难以跟踪的细微错误。

下面的章节将深入讨论@Bean和@Configuration注解。但是，首先，我们介绍了使用基于Java的配置创建Spring容器的各种方法。

## 1.12.2 通过AnnotationConfigApplicationContext来实例化Spring容器

下面是SpringAnnotationConfigApplicationContext的介绍，他是在Spring 3.0被引入的。

他是ApplicationContext的一个实现，他可以接收@Configuration， @Component ，和使用JSR-330注解的类作为输入。

当@Configuration作为输入时，@Configuration类本身被注册成为一个bean，并且他里面包含的所有@Bean 方法，都会被注册成Bean。

当@Component和JSR-330类作为输入时，它们被注册为bean定义，并且假设在必要时在这些类中使用诸如@Autowired或@Inject之类的DI元数据。

**简单构造器**

和ClassPathXmlApplicationContext需要XML文件作为输入一样，AnnotationConfigApplicationContext需要@Configuration 作为实例化参数，这样可以完全不使用XML文件：

```
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
```

前面提到了AnnotationConfigApplicationContext不仅仅可使用@Configuration，也可以使用@Component 或者JSR-330 annotated class 。 如下所示：

```
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
```

上面例子假设MyServiceImpl, Dependency1, 和 Dependency2 使用了Spring的依赖注入注解如： @Autowired.

**使用 register(Class\<?>…​) 来程序的方式构建容器**

你可以使用无参数构造函数来实例化AnnotationConfigApplicationContext，然后通过register()拉配置他。这个在程序的方式中比较有用：

```
public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
```

**使用scan(String…​)允许组件扫描**

想使用组件扫描，可以通过如下方式：

```
@Configuration
@ComponentScan(basePackages = "com.acme") 
public class AppConfig  {
    ...
}
```

> 有经验的Spring用户可能很熟悉下面的XML配置用法：

```
<beans>
    <context:component-scan base-package="com.acme"/>
</beans>
```

上面的例子中，com.acme 包被扫描任何@Component注解的类，这些类被注入到Spring容器中。AnnotationConfigApplicationContext 提供了scan(String…​)方法和component-scan一样的功能。如下：

```
public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}
```

> 记住，@Configuration类是用@Component元注解的，因此它们是组件扫描的候选者。在前面的示例中，假设AppConfig是在com.acme包（或下面的任何包）中声明的，则会在调用scan（）期间取到他。在refresh（）之后，它的所有@Bean方法都被处理并注册为容器中的bean定义。

**使用AnnotationConfigWebApplicationContext来支持Web应用程序**

AnnotationConfigApplicationContext的一个WebApplicationContext变种是AnnotationConfigWebApplicationContext。当你配置Spring ContextLoaderListener，或者Spring MVC DispatcherServlet时，可以使用他。下面的web.xml配置了一个标准的Spring MVC web应用：

```
web-app>
    <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
        instead of the default XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- Configuration locations must consist of one or more comma- or space-delimited
        fully-qualified @Configuration classes. Fully-qualified packages may also be
        specified for component-scanning -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>

    <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Declare a Spring MVC DispatcherServlet as usual -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
            instead of the default XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- Again, config locations must consist of one or more comma- or space-delimited
            and fully-qualified @Configuration classes -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- map all requests for /app/* to the dispatcher servlet -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>
```

## 1.12.3 使用@Bean注解

@Bean是方法级的注解，和XML中的\<bean/>元素有同样的作用。注解支持一些属性如： *init-method* destroy-method *autowiring*

你可以在 @Configuration或者@Component 注解中使用@Bean。

**声明一个Bean**

要声明bean，可以使用@Bean注解对方法进行注解。你可以使用此方法在指定为方法返回值的类型的ApplicationContext中注册bean定义。默认情况下，bean名称与方法名称相同。下面的示例显示了@Bean方法声明：

```
@Configuration
public class AppConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}
```

上面例子和下面相同：

```
<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
```

两种声明都可以在ApplicationContext定义transferService。并将transferService和一个TransferServiceImpl的实例对象绑定起来：

```
transferService -> com.acme.TransferServiceImpl
```

你也可以将@Bean定义在一个返回接口的方法：

```
@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}
```

但是，这会将高级类型预测的可见性限制为指定的接口类型（TransferService）。然后，在容器只知道一次完整类型（transferserviceimpl）的情况下，受影响的单例bean已经被实例化。非懒惰的单例bean根据其声明顺序进行实例化，因此你可能会看到不同的类型匹配结果，具体取决于另一个组件尝试通过非声明类型进行匹配的时间（例如@Autowired TransferServiceImpl，一旦transferService bean被实例化时，它就会被解析）。

> 如果通过已声明的服务接口一致地引用你的类型，则@Bean返回类型可以安全地加入该设计决策。但是，对于实现多个接口的组件或可能由它们的实现类型引用的组件，声明更加具体的返回类型可能会更安全（至少在引用bean的注入点所要求的特定类型）。

**Bean定义**

@Bean注解的方法可以有多个参数，如下所示，在实例化是TransferService需要一个AccountRepository，我们使用方法参数来实现这个依赖，如下：

```
@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}
```

解析机制与基于构造函数的依赖注入非常相似。有关详细信息，请参阅相关部分。

**接收生命周期回调**

用@Bean注解定义的任何类都支持常规的生命周期回调，并且可以使用jsr-250中的@PostConstruct和@PreDestroy注解。更多详细信息，请参见JSR-250注解。

还完全支持常规的Spring生命周期回调。如果bean实现了InitialingBean、DisposableBean或Lifecycle，那么容器将调用它们各自的方法。

还完全支持\*Aware接口的标准集（如BeanFactoryAware、BeannameAware、MessageSourceAware、ApplicationContextAware等）。

@Bean注解支持指定任意的初始化和销毁回调方法，很像spring xml的init方法和bean元素上的destroy方法属性，如下例所示：

```
public class BeanOne {

    public void init() {
        // initialization logic
    }
}

public class BeanTwo {

    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}
```

> 默认情况下，使用具有公共close或shutdown方法的Java配置定义的bean将自动加入销毁回调。如果你有一个公共close或shutdown方法，并且不希望在容器关闭时调用它，则可以将@Bean（destroyMethod=“”）添加到bean定义中，以禁用默认（inferred）模式。
>
> 默认情况下，你可能希望为使用JNDI获取的资源执行此操作，因为它的生命周期是在应用程序外部管理的。特别是，确保对DataSource总是这样做，因为它在JavaEE应用服务器上是有问题的。
>
> 下面的示例演示如何防止DataSource的自动销毁回调：

```
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
    return (DataSource) jndiTemplate.lookup("MyDS");
}
```

另外，对于@Bean方法，你通常使用编程的JNDI查找，或者使用Spring的JNDITemplate或JNDIlocatedElegate帮助器，或者直接使用JNDI InitialContext，但不使用JNDIObjectFactoryBean变量（这将强制你将返回类型声明为factoryBean类型，而不是实际的目标类型，从而使它更难用于那些打算引用所提供的资源的@Bean方法中的交叉引用调用）。

对于上述示例中的beanone，在构造期间直接调用init（）方法同样有效，如下示例所示：

```
@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        BeanOne beanOne = new BeanOne();
        beanOne.init();
        return beanOne;
    }

    // ...
}
```

> 当你直接在Java中工作时，你可以用你的对象做任何事情，并不总是需要依赖于容器的生命周期。

**声明Bean作用域**

使用@Scope来声明作用域

在使用@Bean注解时候，你可以使用在Bean Scopes章节中讲到的任何定义scopes的方法。

默认的作用域是singleton， 但是你可以使用@Scope来重新，如下：

```
@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}
```

\*\*@Scope和scoped-proxy

\<aop:scoped-proxy/>元素。Java配置bean的@Scope注解的proxyMode属性，可以提供他等价的支持。默认值为no proxy（ScopedProxyMode.NO），但可以指定ScopedProxyMode.TARGET\_CLASS或ScopedProxyMode.INTERFACES。

如果将具有范围的代理示例从XML引用文档（参见范围代理）导入到使用Java的“bean”，则类似于以下内容：

```
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
    return new UserPreferences();
}

@Bean
public Service userService() {
    UserService service = new SimpleUserService();
    // a reference to the proxied userPreferences bean
    service.setUserPreferences(userPreferences());
    return service;
}
```

**自定义Bean名字**

默认情况下，配置类使用@Bean方法的名称作为结果bean的名称。但是，可以使用name属性覆盖此功能，如下示例所示：

```
@Configuration
public class AppConfig {

    @Bean(name = "myThing")
    public Thing thing() {
        return new Thing();
    }
}
```

**Bean的别名**

正如在命名bean中所讨论的，有时需要为单个bean指定多个名称，也称为bean别名。@Bean注解的name属性为此接受一个字符串数组。下面的示例演示如何为bean设置多个别名：

```
@Configuration
public class AppConfig {

    @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }
}
```

**Bean描述**

有时，提供一个更详细的bean文本描述会有所帮助。当bean暴露（可能通过jmx）用于监视时，这尤其有用。 要向@Bean添加描述，可以使用@description注解，如下示例所示：

```
@Configuration
public class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    public Thing thing() {
        return new Thing();
    }
}
```

## 1.12.4 使用 @Configuration

@Configuration是一个类级注解，指示对象是bean定义的源。@Configuration类通过public @Bean注解方法声明bean。对@Configuration classes上的@Bean方法的调用也可用于定义Bean之间的依赖关系。请参见基本概念：@Bean和@Configuration以获取一般介绍。

**注入bean间依赖**

当bean相互依赖时，表示这种依赖就如同让一个bean方法调用另一个bean方法一样简单，如下示例所示：

```
@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        return new BeanOne(beanTwo());
    }

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}
```

在上面例子中，beanOnes通过构造函数注入，引入beanTwo。

> 只有当@Bean方法在@Configuration类中声明时，此声明bean间依赖关系的方法才有效。不能使用普通的@Component类声明bean之间的依赖关系。

**查找方法注入**

如前所述，查找方法注入是一个高级特性，你应该很少使用。它在单例作用域bean依赖于原型作用域bean的情况下很有用。对于这种类型的配置，使用Java提供了实现这种模式的自然手段。下面的示例演示如何使用查找方法注入：

```
public abstract class CommandManager {
    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}
```

通过使用Java配置，可以创建CommandManager的子类，并重写抽象的createCommand（）方法，从而查找新的（prototype）命令对象。以下示例显示了如何执行此操作：

```
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
    AsyncCommand command = new AsyncCommand();
    // inject dependencies here as required
    return command;
}

@Bean
public CommandManager commandManager() {
    // return new anonymous implementation of CommandManager with createCommand()
    // overridden to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}
```

**有关基于Java的配置如何在内部工作的进一步信息**

下面展示了@Bean注解方法被调用两次：

```
@Configuration
public class AppConfig {

    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}
```

clientDao()在clientService1() 和clientService2()中都被调用了一次。 因为 clientDao()创建了一个ClientDaoImpl实例，你可能认为创建了两个ClientDaoImpl实例， 但是在Spring中，bean实例通常拥有singleton作用域范围。

这是问题的关键：所有的@Configuration类都会在启动的时候被CGLIB继承，在子类中，它会去检测所有的缓存（作用域范围）的bean，然后才回去调用父类的方法去创建实例。

> 当然，根据Scope的不同情况可能不同，这里我们只讨论singleton。
>
> 在Spring3.2中，不在需要将CGLIB添加到你的classpath中了，因为CGLIB包已经被包括在了org.springframework.cglib，也就是spring-core JAR中。
>
> 使用CGLIB在启动时动态添加功能是有限制的。通常来说配置类必须不是final的。但是在4.3中，configuration 中允许添加任何构造函数，包括@Autowired或者单独的非默认的构造函数。
>
> 如果你希望避免任何cglib强加的限制，请考虑在非@Configuration类（例如，在普通的@Component类上）上声明@Bean方法。然后，@Bean方法之间的跨方法调用不会被拦截，因此你必须完全依赖于构造函数或方法级别的依赖注入。

## 1.12.5 组合基于Java的配置

Spring基于Java的配置特性允许你组合注解，这可以减少配置的复杂性。

**使用@Import注解**

与在Spring XML文件中使用\<import/>元素来帮助模块化配置一样，@Import注解允许从另一个配置类加载@Bean定义，如下例所示：

```
@Configuration
public class ConfigA {

    @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }
}
```

现在在实例化容器时，不需要显示的引入ConfigA.class 和 ConfigB.class，只要ConfigB就够了：

```
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}
```

这种方法简化了容器的实例化，因为只需要处理一个类，而不需要在构造期间记住大量@Configuration类。

> 从SpringFramework4.2开始，@Import还支持引用常规组件类，类似于AnnotationConfigApplicationContext.register方法。如果你希望避免组件扫描，那么使用一些配置类作为入口点来显式定义所有组件，这尤其有用。

**Imported @Bean上的注入依赖**

前面的例子是可行的，但很简单。在大多数实际场景中，bean在配置类之间相互依赖。使用XML时，这不是一个问题，因为不涉及编译器，你可以声明ref=“somebean”，并信任spring在容器初始化期间解决它。当使用@Configuration类时，Java编译器将约束放置在配置模型上，因为对其他bean的引用必须是有效的Java语法。

幸运的是，解决这个问题很简单。正如我们已经讨论过的那样，@Bean方法可以有任意数量的参数来描述bean依赖性。考虑以下更真实的场景，其中有几个@Configuration类，每个类取决于其他类中声明的bean：

```
@Configuration
public class ServiceConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

@Configuration
public class RepositoryConfig {

    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}
```

还有另一种方法可以达到同样的结果。记住，@Configuration类最终只是容器中的另一个bean：这意味着它们可以利用@Autowired和@Value注入以及与任何其他bean相同的其他特性。

> 确保以这种方式注入的依赖项是最简单的类型。@Configuration类在上下文的初始化过程中被提前处理，强制以这种方式注入依赖项可能会导致意外的早期初始化。尽可能采用基于参数的注入，如前面的示例所示。
>
> 另外，通过@Bean特别是BeanPostProcessor和BeanFactoryPostProcessor定义。这些方法通常应声明为static @Bean方法，而不是触发其包含的配置类的实例化。否则，@Autowired和@Value不能在configuration类本身上工作，因为它被创建为bean实例太早了。

下面的示例显示如何将一个bean自动连接到另一个bean：

```
@Configuration
public class ServiceConfig {

    @Autowired
    private AccountRepository accountRepository;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(accountRepository);
    }
}

@Configuration
public class RepositoryConfig {

    private final DataSource dataSource;

    @Autowired
    public RepositoryConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}
```

> @Configuration类中的构造函数注入仅在Spring Framework 4.3之后受支持。还要注意，如果目标bean只定义一个构造函数，则不需要指定@Autowired。在前面的示例中，RepositoryConfig构造函数上不需要@Autowired。

**完全符合条件的imported beans，便于导航**

在前面的场景中，使用@Autowired可以很好地工作并提供所需的模块性，但是确定在哪里声明autowired bean定义仍然有点含糊不清。例如，作为查看serviceConfig的开发人员，你如何确切知道@Autowired AccountRepositor bean的声明位置？它在代码中不是明确的，这可能很好。请记住，Spring工具套件提供了一种工具，它可以图形化的呈现显示所有东西是如何连接的，这可能是你所需要的全部。此外，你的Java IDE可以很容易地找到AccountRepository类型的所有声明和用法，并快速显示返回该类型的@Bean方法的位置。 如果这种模糊性是不可接受的，并且你希望在你的IDE中从一个@Configuration类直接导航到另一个@Configuration类，请考虑自动连接配置类本身。以下示例显示了如何执行此操作：

```
@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        // navigate 'through' the config class to the @Bean method!
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}
```

在前面的情况下，定义AccountRepository是完全明确的。但是，serviceConfig现在与RepositoryConfig紧密耦合。这就是权衡。通过使用基于接口或基于抽象类的@Configuration类，可以稍微减轻这种紧密耦合。请考虑以下示例：

```
@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}

@Configuration
public interface RepositoryConfig {

    @Bean
    AccountRepository accountRepository();
}

@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(...);
    }
}

@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class})  // import the concrete config!
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}
```

现在ServiceConfig与具体的DefaultRepositoryConfig松散地耦合在一起，而内置的IDE工具仍然很有用：你可以很容易地获得RepositoryConfig实现的类型层次结构。通过这种方式，导航@Configuration类及其依赖项与导航基于接口的代码的常规过程没有什么不同。

如果要影响某些bean的启动创建顺序，请考虑将它们中的一些声明为@lazy（用于在第一次访问时创建而不是在启动时创建）或@dependson某些其他bean（确保在当前bean之前创建特定的其他bean，这超出了后者的直接依赖性的含义）。

**条件的引入@Configuration和@Bean**

根据某些任意的系统状态，有条件地启用或禁用完整的@Configuration类，甚至单个的@Bean方法，通常很有用。其中一个常见的例子是，只有在Spring环境中启用了特定的概要文件时，才使用@Profile注解来激活bean（有关详细信息，请参见bean定义概要文件）。

@Profile注解实际上是通过使用一个更灵活的名为@Conditional的注解来实现的。@Conditional annotation指示在注册@Bean之前应该咨询的特定org.springframework.context.annotation.Condition实现。

Condition接口的实现提供了一个返回true或false的匹配（…）方法。例如，下面的列表显示了用于@Profile的实际条件实现：

```
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    if (context.getEnvironment() != null) {
        // Read the @Profile annotation attributes
        MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
        if (attrs != null) {
            for (Object value : attrs.get("value")) {
                if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
                    return true;
                }
            }
            return false;
        }
    }
    return true;
}
```

**组合Java和XML配置**

Spring的@Configuration类支持并不打算完全替代Spring XML。一些工具（如SpringXML名称空间）仍然是配置容器的理想方法。在XML方便或必要的情况下，你可以选择：以“以XML为中心”的方式实例化容器，例如使用ClassPathXmlApplicationContext，或者使用“AnnotationConfigApplicationContext”和“@ImportResource”按需导入XML，实例化容器。

**以XML为中心使用@Configuration类**

最好从XML引导Spring容器，并以特别的方式包含@Configuration类。例如，在使用SpringXML的大型现有代码库中，根据需要创建@Configuration类并从现有XML文件中包含它们更容易。在本节后面，我们将介绍在这种“以XML为中心”的情况下使用@Configuration类的选项。

**将@Configuration classes声明为plain spring \<bean/>元素**

记住，@Configuration类最终是容器中的bean定义。在本系列示例中，我们创建了一个名为appconfig的@Configuration类，并将其作为\<bean/>定义包含在system-test-config.xml中。由于打开了\<context:annotation-config/>，容器识别@Configuration注解并正确处理appconfig中声明的@Bean方法。 下面的示例展示了Java中的一个普通配置类：

```
@Configuration
public class AppConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public TransferService transferService() {
        return new TransferService(accountRepository());
    }
}
```

下面是system-test-config.xml file文件：

```
<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="com.acme.AppConfig"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>
```

jdbc.properties 文件：

```
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
```

```
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}
```

> 在system-test-config.xml文件中，appconfig \<bean/>不声明id元素。虽然这样做是可以接受的，但这是不必要的，因为没有其他bean引用过它，并且不太可能按名称从容器中显式地提取它。类似地，数据源bean也只是按类型自动连接的，因此不严格要求显式bean id。

**使用\<context:component-scan/>来查找@Configuration类**

因为@Configuration是用@Component元注解的，@Configuration注解类自动成为组件扫描的候选者。使用前面示例中描述的相同场景，我们可以重新定义system-test-config.xml以利用组件扫描。注意，在这种情况下，我们不需要显式声明\<context:annotation-config/>，因为\<context:component-scan/>启用了相同的功能。 以下示例显示修改后的system-test-config.xml文件：

```
<beans>
    <!-- picks up and registers AppConfig as a bean definition -->
    <context:component-scan base-package="com.acme"/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>
```

**以@Configuration类为中心使用XML与@ImportResource**

在@Configuration classes是配置容器的主要机制的应用程序中，至少需要使用一些XML。在这些场景中，你可以使用@ImportResource，并且只定义所需的XML。这样做实现了“以Java为中心”的方法来配置容器，并将XML保持在最低限度。下面的示例（包括配置类、定义Bean的XML文件、属性文件和主类）显示了如何使用@ImportResource注解来实现“以Java为中心”的配置，根据需要使用XML：

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

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }
}
```

```
<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
```

properties-config.xml

```
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
```

```
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.flydean.com/spring-framework-documentation5/core-technologies/1.the-ioc-container/1.12java-based-container-configuration.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
