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);
    // ...
}

最后更新于