3.12 基于Java的容器配置 {#toc_5}

3.12.1 基本概念:@Bean@Configuration {#toc_6}

最核心的是Spring支持全新的Java配置,例如@Configuration注解的类和@Bean注解的方法。
@Bean注解用来说明通过Spring IoC容器来管理时一个新对象的实例化,配置和初始化的方法。这对于熟悉Spring以XML配置的方式,@Bean和 element元素扮演了相同的角色。你可以在任何使用@Componen的地方使用@Bean,但是更常用的是在配置@Configuration的类中使用。
一个用@Configuration注解的类说明这个类的主要是作为一个bean定义的资源文件。进一步的讲,被@Configuration注解的类通过简单地在调用同一个类中其他的@Bean方法来定义bean之间的依赖关系。简单的@Configuration 配置类如下所示:

  1. @Configuration
  2. public class AppConfig {
  3. @Bean
  4. public MyService myService() {
  5. return new MyServiceImpl();
  6. }
  7. }

上面的AppConfig类和Spring XML 的配置是等价的:

  1. <beans>
  2. <bean id="myService" class="com.acme.services.MyServiceImpl"/>
  3. </beans>

Full @Configuration vs ‘lite’ @Beans mode?

@Bean方法在没有使用@Configuration注解的类中声明时,它们被称为以“lite”进行处理。例如,用@Component修饰的类或者简单的类中都被认为是“lite”模式。

不同于full @Configuration,lite @Bean 方法不能简单的在类内部定义依赖关系。通常,在“lite”模式下一个@Bean方法不应该调用其他的@Bean方法。

只有在@Configuration注解的类中使用@Bean方法是确保使用“full”模式的推荐方法。这也可以防止同样的@Bean方法被意外的调用很多次,并有助于减少在’lite’模式下难以被追踪的细小bug。

这个模块下我们深入的讨论了@Configuration@Beans注解,首先我们将介绍基于Java配置的各种Spring容器的创建。

3.12.2 使用AnnotationConfigApplicationContext实例化Spring容器 {#toc_7}

下面的部分介绍Spring的AnnotationConfigApplicationContext,Spring 3.0的新内容。这个通用的ApplicationContext实现不仅可以接受@Configuration注解类为输入,还可以接受使用JSR-330元数据注解的简单类和@Component类。

@Configuration注解的类作为输入时,@Configuration类本身会被注册为一个bean,在这个类中所有用@Bean注解的方法都会被定义为一个bean。

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

简单构造

实例化使用@Configuration类作为输入实例化AnnotationConfigApplicationContext和实例化ClassPathXmlApplicationContext时使用Spring的XML文件作为输入的方式大致相同。这在无XML配置的Spring容器时使用:

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

如上所述,AnnotationConfigApplicationContext不限于仅使用@Configuration类。任何@Component或JSR-330注解的类都可以作为输入提供给构造函数。例如:

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

上面假设MyServiceImpl、Dependency1和Dependency2都用了Spring的依赖注入的注解,例如@Autowired

使用register(Class<?>…​)的方式构建容器

也可以使用无参构造函数实例化AnnotationConfigApplicationContext,然后使用register()方法配置。当使用编程方式构建AnnotationConfigApplicationContext时,这种方法特别有用。

使用scan(String …)组件扫描

启用组件扫描,只需要在你的@Configuration类中做如下配置:

  1. @Configuration
  2. @ComponentScan(basePackages = "com.acme")
  3. public class AppConfig {
  4. ...
  5. }

有Spring使用经验的用户,对Spring XML的context的声明非常熟悉:

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

在上面的例子中,com.acme将会被扫描,它会寻找任何@Component注解的类,这些类将会在Spring的容器中被注册成为一个bean。AnnotationConfigApplicationContext暴露的scan(String…​)方法以达到相同组件扫描的功能:

  1. public static void main(String[] args) {
  2. AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
  3. ctx.scan("com.acme");
  4. ctx.refresh();
  5. MyService myService = ctx.getBean(MyService.class);
  6. }

记住:使用@Configuration注解的类是使用@Component进行元注解,所以它们也是组件扫描的候选,假设AppConfig被定义在com.acme这个包下(或者它下面的任何包),它们都会在调用scan()方法期间被找出来,然后在refresh()方法中它们所有的@Bean方法都会被处理,在容器中注册成为bean。

AnnotationConfigWebApplicationContext对于web应用的支持

AnnotationConfigApplicationContext在WebApplicationContext中的变体为
AnnotationConfigWebApplicationContext。当配置Spring ContextLoaderListener servlet 监听器、Spring MVC DispatcherServlet的时候,可以用此实现。下面为配置典型的Spring MVC DispatcherServlet的web.xml代码段。注意contextClass上下文参数和init-param的使用:

  1. <web-app>
  2. <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
  3. instead of the default XmlWebApplicationContext -->
  4. <context-param>
  5. <param-name>contextClass</param-name>
  6. <param-value>
  7. org.springframework.web.context.support.AnnotationConfigWebApplicationContext
  8. </param-value>
  9. </context-param>
  10. <!-- Configuration locations must consist of one or more comma- or space-delimited
  11. fully-qualified @Configuration classes. Fully-qualified packages may also be
  12. specified for component-scanning -->
  13. <context-param>
  14. <param-name>contextConfigLocation</param-name>
  15. <param-value>com.acme.AppConfig</param-value>
  16. </context-param>
  17. <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
  18. <listener>
  19. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  20. </listener>
  21. <!-- Declare a Spring MVC DispatcherServlet as usual -->
  22. <servlet>
  23. <servlet-name>dispatcher</servlet-name>
  24. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  25. <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
  26. instead of the default XmlWebApplicationContext -->
  27. <init-param>
  28. <param-name>contextClass</param-name>
  29. <param-value>
  30. org.springframework.web.context.support.AnnotationConfigWebApplicationContext
  31. </param-value>
  32. </init-param>
  33. <!-- Again, config locations must consist of one or more comma- or space-delimited
  34. and fully-qualified @Configuration classes -->
  35. <init-param>
  36. <param-name>contextConfigLocation</param-name>
  37. <param-value>com.acme.web.MvcConfig</param-value>
  38. </init-param>
  39. </servlet>
  40. <!-- map all requests for /app/* to the dispatcher servlet -->
  41. <servlet-mapping>
  42. <servlet-name>dispatcher</servlet-name>
  43. <url-pattern>/app/*</url-pattern>
  44. </servlet-mapping>
  45. </web-app>

3.12.3 使用@Bean注解 {#toc_8}

@Bean是XML元素方法级注解的直接模拟。它支持由提供的一些属性,例如:init-methoddestroy-methodautowiring和name。

您可以在@Configuration@Component注解的类中使用@Bean注解。

定义一个bean

要定义一个bean,只需在一个方法上使用@Bean注解。您可以使用此方法在指定方法返回值类型的ApplicationContext中注册bean定义。默认情况下,bean名称与方法名称相同。以下是@Bean方法声明的简单示例:

  1. @Configuration
  2. public class AppConfig {
  3. @Bean
  4. public TransferService transferService() {
  5. return new TransferServiceImpl();
  6. }
  7. }

这种配置完全和下面的Spring XML配置等价:

  1. <beans>
  2. <bean id="transferService" class="com.acme.TransferServiceImpl"/>
  3. </beans>

两种声明都可以使得一个名为transferService的bean在ApplicationContext可用,绑定到TransferServiceImpl类型的对象实例上:

transferService -> com.acme.TransferServiceImpl

Bean 依赖

@Bean注解方法可以具有描述构建该bean所需依赖关系的任意数量的参数。例如,如果我们的TransferService需要一个AccountRepository,我们可以通过一个方法参数实现该依赖:

  1. @Configuration
  2. public class AppConfig {
  3. @Bean
  4. public TransferService transferService(AccountRepository accountRepository) {
  5. return new TransferServiceImpl(accountRepository);
  6. }
  7. }

这种解决原理和基于构造函数的依赖注入几乎相同,请参考相关章节,查看详细信息。

生命周期回调

任何使用了@Bean定义了的类都支持常规生命周期回调,并且可以使用JSR-250中的@PostConstruct@PreDestroy注解,详细信息,参考JSR-250注解。

完全支持常规的Spring生命周期回调。如果一个bean实现了InitializingBean,DisposableBean或Lifecycle接口,它们的相关方法就会被容器调用。

完全支持*Aware系列的接口,例如:BeanFactoryAware,BeanNameAware,MessageSourceAware,ApplicationContextAware等。

@Bean注解支持任意的初始化和销毁回调方法,这与Spring XML 中bean元素上的init方法和destroy-method属性非常相似:

  1. public class Foo {
  2. public void init() {
  3. // initialization logic
  4. }
  5. }
  6. public class Bar {
  7. public void cleanup() {
  8. // destruction logic
  9. }
  10. }
  11. @Configuration
  12. public class AppConfig {
  13. @Bean(initMethod = "init")
  14. public Foo foo() {
  15. return new Foo();
  16. }
  17. @Bean(destroyMethod = "cleanup")
  18. public Bar bar() {
  19. return new Bar();
  20. }
  21. }

默认情况下,使用Java config定义的具有公开关闭或停止方法的bean将自动加入销毁回调。如果你有一个公开的关闭或停止方法,但是你不希望在容器关闭时被调用,只需将@Bean(destroyMethod =””)添加到你的bean定义中即可禁用默认(推测)模式。 默认情况下,您可能希望通过JNDI获取资源,因为它的生命周期在应用程序之外进行管理。特别地,请确保始终为DataSource执行此操作,因为它已知在Java EE应用程序服务器上有问题。

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

另外,通过@Bean方法,通常会选择使用编程来进行JNDI查找:要么使用Spring的JndiTemplate/JndiLocatorDelegate帮助类,要么直接使用JNDI InitialContext,但不能使用JndiObjectFactoryBean变体来强制将返回类型声明为FactoryBean类型以代替目标的实际类型,它将使得在其他@Bean方法中更难用于交叉引用调用这些在此引用提供资源的方法。
当然上面的Foo例子中,在构造期间直接调用init()方法同样有效:

  1. @Configuration
  2. public class AppConfig {
  3. @Bean
  4. public Foo foo() {
  5. Foo foo = new Foo();
  6. foo.init();
  7. return foo;
  8. }
  9. // ...
  10. }

当您直接在Java中工作时,您可以对对象执行任何您喜欢的操作,并不总是需要依赖容器生命周期!

指定bean的作用域

使用@Scope注解

你可以指定@Bean注解定义的bean应具有的特定作用域。你可以使用Bean作用域章节中的任何标准作用域。

默认的作用域是单例,但是你可以用@Scope注解重写作用域。

  1. @Configuration
  2. public class MyConfiguration {
  3. @Bean
  4. @Scope("prototype")
  5. public Encryptor encryptor() {
  6. // ...
  7. }
  8. }

@Scope和scope 代理

Spring提供了一个通过范围代理来处理范围依赖的便捷方法。使用XML配置创建此类代理的最简单方法是元素。使用@Scope注解配置Java中的bean提供了与proxyMode属性相似的支持。默认是没有代理(ScopedProxyMode.NO),但您可以指定ScopedProxyMode.TARGET_CLASS或ScopedProxyMode.INTERFACES。

如果你使用Java将XML参考文档(请参阅上述链接)到范围的@Bean中移植范围限定的代理示例,则它将如下所示
如果你将XML 参考文档的scoped代理示例转化为Java @Bean,如下所示:

  1. // an HTTP Session-scoped bean exposed as a proxy
  2. @Bean
  3. @SessionScope
  4. public UserPreferences userPreferences() {
  5. return new UserPreferences();
  6. }
  7. @Bean
  8. public Service userService() {
  9. UserService service = new SimpleUserService();
  10. // a reference to the proxied userPreferences bean
  11. service.setUserPreferences(userPreferences());
  12. return service;
  13. }

自定义Bean命名

默认情况下,配置类使用@Bean方法的名称作为生成的bean的名称。但是,可以使用name属性来重写此功能。

  1. @Configuration
  2. public class AppConfig {
  3. @Bean(name = "myFoo")
  4. public Foo foo() {
  5. return new Foo();
  6. }
  7. }

Bean别名

如3.3.1节”bean 命名”中所讨论的,有时一个单一的bean需要给出多个名称,称为bean别名。 为了实现这个目标,@Bean注解的name属性接受一个String数组。

  1. @Configuration
  2. public class AppConfig {
  3. @Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })
  4. public DataSource dataSource() {
  5. // instantiate, configure and return DataSource bean...
  6. }
  7. }

Bean描述

有时候需要提供一个详细的bean描述文本是非常有用的。当对bean暴露(可能通过JMX)进行监控使,特别有用。

可以使用@Description注解对Bean添加描述:

  1. @Configuration
  2. public class AppConfig {
  3. @Bean
  4. @Description("Provides a basic example of a bean")
  5. public Foo foo() {
  6. return new Foo();
  7. }
  8. }

3.12.4 使用@Configuration注解 {#toc_9}

@Configuration是一个类级别的注解,指明此对象是bean定义的源。@Configuration类通过public @Bean注解的方法来声明bean。在@Configuration类上对@Bean方法的调用也可以用于定义bean之间的依赖。概述,请参考第3.12.1节”基本概念:@Bean@Configuration”。

bean依赖注入

@Beans相互依赖时,表示依赖关系就像一个bean方法调用另一个方法一样简单:

  1. @Configuration
  2. public class AppConfig {
  3. @Bean
  4. public Foo foo() {
  5. return new Foo(bar());
  6. }
  7. @Bean
  8. public Bar bar() {
  9. return new Bar();
  10. }
  11. }

上面的例子,foo接受一个bar的引用来进行构造器注入:

这种方法声明的bean的依赖关系只有在@Configuration类的@Bean方法中有效。你不能在@Component类中来声明bean的依赖关系。

方法查找注入

如前所述,方法查找注入是一个你很少用用到的高级特性。在单例的bean对原型的bean有依赖性的情况下,它非常有用。这种类型的配置使用,Java提供了实现此模式的自然方法。

  1. public abstract class CommandManager {
  2. public Object process(Object commandState) {
  3. // grab a new instance of the appropriate Command interface
  4. Command command = createCommand();
  5. // set the state on the (hopefully brand new) Command instance
  6. command.setState(commandState);
  7. return command.execute();
  8. }
  9. // okay... but where is the implementation of this method?
  10. protected abstract Command createCommand();
  11. }

使用Java支持配置,您可以创建一个CommandManager的子类,覆盖它抽象的createCommand()方法,以便它查找一个新的(原型)命令对象:

  1. @Bean
  2. @Scope("prototype")
  3. public AsyncCommand asyncCommand() {
  4. AsyncCommand command = new AsyncCommand();
  5. // inject dependencies here as required
  6. return command;
  7. }
  8. @Bean
  9. public CommandManager commandManager() {
  10. // return new anonymous implementation of CommandManager with command() overridden
  11. // to return a new prototype Command object
  12. return new CommandManager() {
  13. protected Command createCommand() {
  14. return asyncCommand();
  15. }
  16. }
  17. }

有关基于Java配置内部如何工作的更多信息

下面的例子展示了一个@Bean注解的方法被调用两次:

  1. @Configuration
  2. public class AppConfig {
  3. @Bean
  4. public ClientService clientService1() {
  5. ClientServiceImpl clientService = new ClientServiceImpl();
  6. clientService.setClientDao(clientDao());
  7. return clientService;
  8. }
  9. @Bean
  10. public ClientService clientService2() {
  11. ClientServiceImpl clientService = new ClientServiceImpl();
  12. clientService.setClientDao(clientDao());
  13. return clientService;
  14. }
  15. @Bean
  16. public ClientDao clientDao() {
  17. return new ClientDaoImpl();
  18. }
  19. }

clientDao()方法clientService1()和clientService2()被各自调用了一次。因为这个方法创建并返回了一个新的ClientDaoImpl实例,你通常期望会有2个实例(每个服务各一个)。这有一个明显的问题:在Spring中,bean实例默认情况下是单例。神奇的地方在于:所有的@Configuration类在启动时都使用CGLIB进行子类实例化。在子类中,子方法在调用父方法创建一个新的实例之前会首先检查任何缓存(作用域)的bean。注意,从Spring 3.2开始,不再需要将CGLIB添加到类路径中,因为CGLIB类已经被打包在org.springframework.cglib下,直接包含在spring-core JAR中。

根据不同的bean作用域,它们的行为也是不同的。我们这里讨论的都是单例模式。

这里有一些限制是由于CGLIB在启动时动态添加的特性,特别是配置类都不能是final类型。然而从4.3开始,配置类中允许使用任何构造函数,包含@Autowired使用或单个非默认构造函数声明进行默认注入。如果你希望避免CGLIB带来的任何限制,那么可以考虑子在非@Configuration注解类中声明@Bean注解方法。例如,使用@Component注解类。在 @Bean方法之间的交叉调用不会被拦截,所以你需要在构造器或者方法级别上排除依赖注入。

3.12.5 基于Java组合配置 {#toc_10}

使用@Import注解

和Spring XML文件中使用元素来帮助模块化配置类似,@Import注解允许从另一个配置类加载@Bean定义:

  1. @Configuration
  2. public class ConfigA {
  3. @Bean
  4. public A a() {
  5. return new A();
  6. }
  7. }
  8. @Configuration
  9. @Import(ConfigA.class)
  10. public class ConfigB {
  11. @Bean
  12. public B b() {
  13. return new B();
  14. }
  15. }

现在,在实例化上下文时不是同时指明ConfigA.class和ConfigB.class,而是仅仅需要明确提供ConfigB:

  1. public static void main(String[] args) {
  2. ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
  3. // now both beans A and B will be available...
  4. A a = ctx.getBean(A.class);
  5. B b = ctx.getBean(B.class);
  6. }

这种方法简化了容器实例化,因为只需要处理一个类,而不是需要开发人员在构建期间记住大量的@Configuration注解类。

从Spring Framework 4.2开始,@Import注解也支持对常规组件类的引用,类似AnnotationConfigApplicationContext.register方法。如果你希望避免组件扫描,使用一些配置类作为所有组件定义的入口,这个方法特别有用。

引入的@Bean定义中注入依赖

上面的类中可以运行,但是太过简单。在大多数实际场景中,bean在配置类之间相互依赖。当使用XML时,这没有问题,因为没有编译器参与,一个bean可以简单的声明为ref=”someBean”并且相信Spring在容器初始化过程处理它。当然,当使用@Configuration注解类,Java编译器会对配置模型放置约束,以便其他对其他引用的bean进行Java语法校验。
幸运的是,解决这个问题也很简单。正如我们讨论过的,@Bean方法可以有任意数量的参数来描述bean的依赖。让我们考虑一下真实场景和一系列@Configuration类,每个bean都依赖了其他配置中声明的bean:

  1. @Configuration
  2. public class ServiceConfig {
  3. @Bean
  4. public TransferService transferService(AccountRepository accountRepository) {
  5. return new TransferServiceImpl(accountRepository);
  6. }
  7. }
  8. @Configuration
  9. public class RepositoryConfig {
  10. @Bean
  11. public AccountRepository accountRepository(DataSource dataSource) {
  12. return new JdbcAccountRepository(dataSource);
  13. }
  14. }
  15. @Configuration
  16. @Import({ServiceConfig.class, RepositoryConfig.class})
  17. public class SystemTestConfig {
  18. @Bean
  19. public DataSource dataSource() {
  20. // return new DataSource
  21. }
  22. }
  23. public static void main(String[] args) {
  24. ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
  25. // everything wires up across configuration classes...
  26. TransferService transferService = ctx.getBean(TransferService.class);
  27. transferService.transfer(100.00, "A123", "C456");
  28. }

这里有其他的方法实现相同的结果。记住@Configuration类最终只是容器中的另一个bean:这意味着它们可以像任何其他bean一样利用@Autowired@Value注入等!

确保您注入的依赖关系是最简单的。@Configuration类在上下文初始化期间处理,强制要求依赖使用这种方式进行注入可能导致意外的早期初始化问题。如果可能,就采用如上述例子所示的基于参数的注入。

同时,也要特别小心通过@Bean的BeanPostProcessor BeanFactoryPostProcessor定义。它们应该被声明为static的@Bean方法,不会触发包含它们的配置类的实例化。否则,@Autowired@Value将在配置类上不生效,因为它太早被创建为一个实例了。

  1. @Configuration
  2. public class ServiceConfig {
  3. @Autowired
  4. private AccountRepository accountRepository;
  5. @Bean
  6. public TransferService transferService() {
  7. return new TransferServiceImpl(accountRepository);
  8. }
  9. }
  10. @Configuration
  11. public class RepositoryConfig {
  12. private final DataSource dataSource;
  13. @Autowired
  14. public RepositoryConfig(DataSource dataSource) {
  15. this.dataSource = dataSource;
  16. }
  17. @Bean
  18. public AccountRepository accountRepository() {
  19. return new JdbcAccountRepository(dataSource);
  20. }
  21. }
  22. @Configuration
  23. @Import({ServiceConfig.class, RepositoryConfig.class})
  24. public class SystemTestConfig {
  25. @Bean
  26. public DataSource dataSource() {
  27. // return new DataSource
  28. }
  29. }
  30. public static void main(String[] args) {
  31. ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
  32. // everything wires up across configuration classes...
  33. TransferService transferService = ctx.getBean(TransferService.class);
  34. transferService.transfer(100.00, "A123", "C456");
  35. }

只有Spring Framework 4.3才支持@Configuration类中的构造方法注入。注意,如果目标bean只定义了一个构造函数,那么则不需要指定@Autowired;如果目标bean只定义一个构造函数,则不需要指定@Autowired;在上面的例子中,@Autowired在RepositoryConfig构造函数中是不必要的。

在上面的场景中,使用@Autowired可以很好的提供所需的模块化,但是准确的决定在哪里自动注入定义的bean还是模糊的。例如,作为开发者来看待ServiceConfig,如何准确的确定自动注入 AccountRepository 是在哪里声明的?它没有明确的出现在代码中,这可能还不错。记住,Spring Tool Suite 提供工具可以渲染图形展示对象是如何装配的,这些可能是你所需要的。同时,你的Java IDE 也可以很简单的找出所有AccountRepository类型的声明和使用,这将很快的展示出你@Bean方法的位置和返回类型。

在这种歧义不可接受的情况下,你希望从IDE中直接从一个@Configuration类导航到另一个类,可以考虑自动装配配置类的本身:

  1. @Configuration
  2. public class ServiceConfig {
  3. @Autowired
  4. private RepositoryConfig repositoryConfig;
  5. @Bean
  6. public TransferService transferService() {
  7. // navigate 'through' the config class to the @Bean method!
  8. return new TransferServiceImpl(repositoryConfig.accountRepository());
  9. }
  10. }

在上面的情形中,定义的AccountRepository是完全透明的。但是,ServiceConfig和RepositoryConfig是紧密耦合在一起了;这需要权衡。这种紧密耦合的可以通过基于接口或者抽象@Configuration类来缓解。可以考虑下面的代码:

  1. @Configuration
  2. public class ServiceConfig {
  3. @Autowired
  4. private RepositoryConfig repositoryConfig;
  5. @Bean
  6. public TransferService transferService() {
  7. return new TransferServiceImpl(repositoryConfig.accountRepository());
  8. }
  9. }
  10. @Configuration
  11. public interface RepositoryConfig {
  12. @Bean
  13. AccountRepository accountRepository();
  14. }
  15. @Configuration
  16. public class DefaultRepositoryConfig implements RepositoryConfig {
  17. @Bean
  18. public AccountRepository accountRepository() {
  19. return new JdbcAccountRepository(...);
  20. }
  21. }
  22. @Configuration
  23. @Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config!
  24. public class SystemTestConfig {
  25. @Bean
  26. public DataSource dataSource() {
  27. // return DataSource
  28. }
  29. }
  30. public static void main(String[] args) {
  31. ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
  32. TransferService transferService = ctx.getBean(TransferService.class);
  33. transferService.transfer(100.00, "A123", "C456");
  34. }

现在ServiceConfig与具体DefaultRepositoryConfig就是送耦合,IDE内置的工具也仍然有用:对开发人员来说,可以轻松的获取RepositoryConfig实现层级类型。用这种方法,定位到@Configuration类及它的依赖类和定位基于接口的代码就没什么区别。

有条件的包括@Configuration类或@Bean方法

通常,有条件的开启或者禁用一个完整的@Configuration类,甚至是基于有任意系统状态的单独@Bean方法。一个常见的例子就是使用@Profile注解来激活仅在Spring 环境中启用的特定的profile文件(有关详细信息,请参阅第3.13.1节“Bean definition profiles”)
@Profile注解是用一个更加灵活的@Conditional注解实现的。@Conditional注解表示@Bean在被注册前应该查阅特定的org.springframework.context.annotation.Condition实现。
Condition接口的实现只提供了一个返回true或者false的matches(…)方法。例如
@Profile是Condition的具体实现:

  1. @Override
  2. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  3. if (context.getEnvironment() != null) {
  4. // Read the @Profile annotation attributes
  5. MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
  6. if (attrs != null) {
  7. for (Object value : attrs.get("value")) {
  8. if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
  9. return true;
  10. }
  11. }
  12. return false;
  13. }
  14. }
  15. return true;
  16. }

@Conditional详细信息请参考javadocs。

Java and XML 混合配置

Spring 对@Configuration配置类的支持的目的不是100%来替换Spring XML配置的。一些基本特性,例如:Spring XML命名空间仍然是容器配置的一个理想方式。在 XML更便于使用或者是必须使用的情况下,要么以“XML为中心”的方式来实例化容器,比如,ClassPathXmlApplicationContext,要么以“Java为中心”的方式,使用AnnotationConfigurationApplicationContext 和@ImportResource注解来引入所需的XML。

以XML为中心使用@Configuration

假设你可能会以XML包含@Configuration类的方式来启动一个Spring容器。例如,在一个现有使用Spring XML的大型代码库中,根据需要从已有的XML文件中创建@Configuration类是很简单的。下面你可以发现在以XML为中心的情形下使用@Configuration类的选项。谨记,@Configuration类最终只是容器中的一个bean。在这个例子中,我们会创建一个名为AppConfig的@Configuration类,它作为一个bean的定义包含在system-test-config.xml中。因为context:annotation-config/是打开的,容器会识别@Configuration,并且会处理AppConfig中声明的@Bean方法。

  1. @Configuration
  2. public class AppConfig {
  3. @Autowired
  4. private DataSource dataSource;
  5. @Bean
  6. public AccountRepository accountRepository() {
  7. return new JdbcAccountRepository(dataSource);
  8. }
  9. @Bean
  10. public TransferService transferService() {
  11. return new TransferService(accountRepository());
  12. }
  13. }

system-test-config.xml:

  1. <beans>
  2. <!-- enable processing of annotations such as @Autowired and @Configuration -->
  3. <context:annotation-config/>
  4. <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
  5. <bean class="com.acme.AppConfig"/>
  6. <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  7. <property name="url" value="${jdbc.url}"/>
  8. <property name="username" value="${jdbc.username}"/>
  9. <property name="password" value="${jdbc.password}"/>
  10. </bean>
  11. </beans>

jdbc.properties:

jdbc.url=jdbc:hsqldb:hsql://localhost/xdb

jdbc.username=sa

jdbc.password=

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

在上面的system-test-config.xml中,AppConfig中的bean没有声明id的元素。然而它也可以被接受,在没有其他bean引用的情况下也没有必要给出,也不太可能通过名字的方式从容器中显式取出。像DataSource bean一样,只能通过类型自动注入,所以明确的bean id也不严格要求。

因为@Configuration@Component的一个元注解,对于component的扫描@Configuration注解类会自动成为候选者。和上面的场景相同,利用component扫描可以重新定义system-test-config.xml。注意在个案例中,我们不需要明确的声明context:annotation-config/,因为开启context:component-scan/,功能是相同的。

system-test-config.xml:

  1. <beans>
  2. <!-- picks up and registers AppConfig as a bean definition -->
  3. <context:component-scan base-package="com.acme"/>
  4. <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
  5. <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  6. <property name="url" value="${jdbc.url}"/>
  7. <property name="username" value="${jdbc.username}"/>
  8. <property name="password" value="${jdbc.password}"/>
  9. </bean>
  10. </beans>

@Configuration类为中心的 XML @ImportResource的使用

在以@Configuration类为主要机制的配置容器的应用程序中,仍然有必要使用一些XML。在这些场景中,只需使用@ImportResource,并根据需要定义一些XML。这样实现了“以Java为中心”方式来配置容器,并将XML保持在最低限度。

  1. @Configuration
  2. @ImportResource("classpath:/com/acme/properties-config.xml")
  3. public class AppConfig {
  4. @Value("${jdbc.url}")
  5. private String url;
  6. @Value("${jdbc.username}")
  7. private String username;
  8. @Value("${jdbc.password}")
  9. private String password;
  10. @Bean
  11. public DataSource dataSource() {
  12. return new DriverManagerDataSource(url, username, password);
  13. }
  14. }

properties-config.xml

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

jdbc.properties

jdbc.url=jdbc:hsqldb:hsql://localhost/xdb

jdbc.username=sa

jdbc.password=

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