IoC容器12——基于Java的容器配置

1 基本概念:@Bean和@Configuration

Spring新的Java配置支持的核心组件是@Configuration注释的类和@Bean注释的方法。web

@Bean注解被用于代表一个方法实例化、配置和初始化一个被Spring IoC容器管理的新对象。相似于Spring的<beans/>XML配置,@Bean注解扮演与<bean/>相同的角色。可使用@Bean注解的方法在任何Spring组件中,然而,它们更经常使用于@Configuration注释的bean。spring

使用@Configuration注解一个类代表它的主要目的是做为bean定义的一个源。此外,@Configuration类容许经过简单的在同一个类中调用其它@Bean方法来定义bean间的依赖关系。最简单的@Configuration类以下所示:sql

@Configuration
public class AppConfig {

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

}

上面的AppConfig类等价于下面的Spring <bean/> XML:编程

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

完整的@Configuration vs ‘精简‘ @Bean模式数组

当@Bean方法在未注释@Configuration的类中声明,它们被‘精简’模式进行处理。例如,在@Component类中或在普通旧类中声明的bean方法被做为精简模式。缓存

不像完整的@Configuration,精简的@Bean方法不能轻松的声明bean间的依赖。在精简模式操做中,一般一个@Bean方法不该该调用另外一个@Bean方法。服务器

只有在@Configuration类中使用@Bean方法是确保老是使用完整模式推荐的方法。这将防止相同的@Bean方法意外的被屡次调用而且有助于减小在“精简”模式下操做时难以追逐的微妙错误。app

2 使用AnnotationConfigApplicationContext实例化Spring容器

下面的部分介绍了Spring的AnnotationConfigApplicationContext,在Spring 3.0中是新增的。这个多功能的ApplicationContext实现不只能够接受@Configuration类做为输入,还能够接受普通的@Component类和JSR-330元数据注释的类。async

当@Configuration类被做为输入提供它自己被注册为一个bean定义,而且类中全部声明的@Bean方法也被注册为bean定义。ide

当@Component和JSR-330类被提供,它们被注册为bean定义,而且假设在必要时这些类中使用注入@Autowired或@Inject之类的依赖注入元数据。

简单的构造

与使用Spring XML文件做为实例化ClassPathXmlApplicationContext时的输入方式大体形同,@Configuration类能够做为实例化AnnotationConfigApplicationContext的输入。这容许彻底无XML的使用Spring容器:

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注解类能够做为构造函数的输入。例如:

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()方法配置它。这个方法在使用编程的方式配置AnnotationConfigApplicationContext时十分有用。

public static void mian(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方法:

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

等价的XML声明是使用Spring的context:命名空间:

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

在上面的例子中,将会扫描com.acme包,查找任何@Component注释的类,而且在容器中这些类将会被注册为Spring bean定义。AnnotationConfigApplicationContext暴露了scan(String...)方法来调用一样的组件扫描功能:

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应用

AnnotationConfigWebApplicationContext是AnnotationConfigApplicationContext的WebApplicationContext的变体。当配置Spring的ContextLoaderListener servlet listener、Spring MVC DispathcerServlet等组件时使用这个实现。下面是配置典型的Spring MVC Web应用程序的web.xml片断。注意contextClass的context-param和init-param的使用方法:

<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>

3 使用@Bean注解

@Bean是方法级的注解而且是XML <bean/>元素的直接模拟。注解支持<bean/>提供的一些属性,例如init-method, destory-method, autowiring和name。

能够在@Configuration和@Component注释的类中使用@Bean注解。

声明一个bean

为了声明一个bean,只要用@Bean注释一个方法便可。使用这个方法在容器中注册一个bean定义,由方法的返回值指定bean的类型。默认的,bean的名字与方法名字相同。下面是@Bean方法声明的一个简单例子:

@Configuration
public class AppConfig {

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

}

上面的配置与下面的Spring XML等价:

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

两个声明都生成了一个在ApplicationContext中可用的名为transferService的bean,与TransferServiceImpl类型的对象实例绑定:

transferService -> com.acme.TransferServiceImpl

Bean依赖关系

一个@Bean注释的方法能够有任意数量的参数来表述构建这个bean所须要的依赖关系。例如若是TransferService须要一个AccountRepository,能够经过方法参数赋予依赖关系。

@Configuration
public class AppConfig {

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

}

它的解析机制与基于构造函数的依赖注入几乎相同。

接收生命周期回调

任何使用@Bean注解定义的类支持常规的生命周期回调而且可使用JSR-250的@PostConstruct和@PreDestory注解。

常规的Spring生命周期回调也被彻底支持。若是一个bean实现了InitializingBean、DisposableBean或者Lifecycle,它们各自的方法会被容器调用。

标准的*Aware接口集合例如BeanFactoryAware、BeanNameAware、MessageSourceAware、ApplicationContextAware等等也被全面支持。

@Bean注解支持指定任意初始化和销毁回调函数,与Spring XML <bean>元素的init-method和destory-method属性十分类似:

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

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

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public Foo foo() {
        return new Foo();
    }

    @Bean(destroyMethod = "cleanup")
    public Bar bar() {
        return new Bar();
    }

}

默认的,使用Java配置的bean定义拥有的公有的close或者shutdown方法自动被视做销毁回调。若是类中有公有的close或shutdown方法而且不想让其在容器关闭时被调用,将@Bean("destoryMethod="")加入bean定义便可关闭默认(inferred)的模式。

默认状况下,可能在经过JNDI获取资源时作上面的处理,由于其生命周期在应用程序以外管理。特别的请确保始终为DataSource执行上面的操做,由于它已知在Java EE应用程序服务器上有问题。

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

另外,使用@Bean方法一般会选择使用编程式的JNDI查找:或者使用Spring的JndiTemplate/IndiLocatorDelegate助手或者直接使用JNDI InitialContext,可是不要使用JndiObjectFactoryBean的变体,它强制你将返回类型声明为FactoryBean类型而不是实际的目标类型,使其在其它@Bean方法中更难于为了提供资源进行交叉引用调用。

固然在上面Foo的例子中,等价于在构造时直接调用init()方法:

@Configuration
public class AppConfig {
    @Bean
    public Foo foo() {
        Foo foo = new Foo();
        foo.init();
        return foo;
    }

    // ...

}

当使用Java配置,可使用对象作任何想作的事情,不须要依赖于容器的生命周期。

指定bean做用域

使用@Scope注解

能够为使用@Bean注解的bean定义指定做用域。可使用任何标准的做用域。

默认的做用域是singleton,可是可使用@Scope注解覆盖这个值:

@Configuration
public class MyConfiguration {

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

}

@Scope和做用域代理

Spring提供一个便捷的方式经过做用于代理来使用做用域依赖。使用XML配置建立这样一个代理的最简单方式是aop:scoped-proxy/元素。使用@Scope注解在Java中定义bean经过proxyMode属性提供了一个等价的支持。默认的值是没有代理(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;
}

(@SessionScope的proxyMode()的默认值是TARGET_CLASS)

自定义bean命名

默认的,配置类使用@Bean方法的名字做为结果bean的名字。这个功能可使用name属性被覆盖。

@Configuration
public class AppConfig {

    @Bean(name = "myFoo")
    public Foo foo() {
        return new Foo();
    }

}

Bean别名

有些时候须要赋给一个bean多个名字,或者被称为bean别名。@Bean注解的name属性为了这个目的接收一个String数组。

@Configuration
public class AppConfig {

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

}

Bean描述

有时提供一个更详细的文本描述是有用的。当bean被暴露(可能经过JMX)进行监视时,会颇有用。

使用@Description注解给@Bean体检一个描述:

@Configuration
public class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    public Foo foo() {
        return new Foo();
    }

}

4 使用@Configuration注解

@Configuration是一个类级别的注解表示对象是bean定义的源。@Configuration类经过@Bean注释的公有方法声明bean。调用@Configuration类的@Bean方法能够用于定义bean之间的依赖关系。

注入bean之间的依赖

当@Bean依赖于其它@Bean,表达依赖关系的方法是一个bean方法调用另外一个:

@Configuration
public class AppConfig {

    @Bean
    public Foo foo() {
        return new Foo(bar());
    }

    @Bean
    public Bar bar() {
        return new Bar();
    }

}

上面的例子中,foo bean接收一个bar的引用经过构造函数注入。

这种声明bean之间依赖关系的方法仅在@Configuration类中的@Bean方法有效。不能使用普通的@Component类声明bean之间的依赖关系。

查找方法注入

如前所述,查找方法注入是一种应该不多使用的高级功能。在singleton做用域bean有一个prototype做用域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 command() 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()中调用一次。由于这个方法建立ClientDaoImpl的一个新实例并返回它,你可能会指望有两个实例(每一个service一个)。可确定是有问题的:在Spring中,默认将bean实例化为singleton。这就是神奇的地方:全部@Configuration类在启动时经过CGLIB被继承。在这个子类中,方法在调用父类方法并建立新的实例以前首先检查容器中的缓存bean。注意,从Spring3.2开始不须要将CGLIB添加到类路径由于CGLIB类被从新打包到org.springframework.cglib而且直接包含域spring-core JAR包。

其它做用域的bean的行为是不一样的。这里讨论的是singleton做用域。

因为CGLIB在启动时动态添加功能,所以有一些限制,特别是配置类不能是final。然而,从4.3开始,在配置类中容许任何构造函数,包括使用@Autowired或者只有一个非默认的用于默认注入的构造函数。

若是想要避免任何CGLIB带来的限制,能够在非@Configuration类中声明@Bean方法,例如在普通@Component类中。@Bean方法之间的交叉调用不会被拦截,因此不得不明确的依靠构造函数或者方法级别的依赖注入。

5 组合基于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类。

从Spring Framework 4.2开始,@Import也能够支持应用常规的组件类,相似于AnnotationConfigApplicationContext.register方法。若是想避免组件扫描,而是使用一些配置类做为明确的定义全部组件的入口,是十分有用的。

在被导入的@Bean定义中注入依赖关系

上面的例子很简单。在更实际的场景中,bean会有跨配置类的依赖关系。当使用XML,这自己不是问题,由于没有发生编译,而且能够简单的声明ref="someBean"而后信任Spring会早初始化时注入。固然,在使用@Configuration类时,Java编译器在配置模型中加入一些约束,对其它bean的引用必须是Java语法有效的。

幸运的事,解决这个问题很简单。@Bean方法能够任意数量的参数来描述bean的依赖关系。考虑一个更加实际的场景,几个@Configuration类,每一个都有bean依赖于其它类中定义的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:这意味着它们能够向任何其它bean同样使用@Autowired和@Value注入等。

要保证你只经过最简单的方式注入依赖关系。@Configuration类在上下文初始化时很早的被处理而且经过这种方法强制注入的依赖关系可能会致使不指望的过早实例化。只要容许,任什么时候候都应使用上面例子中的给予参数的注入。

同时,经过@Bean使用BeanPostProcessor和BeanFactoryPostProcessor时要十分当心。它们应该总被声明为静态@Bean方法,不与容器配置类的实例化绑定。然而,@Autowired和@Value不会在配置类自己工做由于它会被过早的建立为一个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;在上面的例子中,@Autowired不须要在RepositoryConfig的构造函数上注释。

在上面的场景中,使用@Autowired工做良好而且提供了所需的模块性,可是明确的肯定被自动装配的bean定义在何处仍有必定的不肯定性。例如,若是一个开发者查看ServiceCOnfig,你如何明确的知道@Autowired AccountRepository bean在何处声明?这在代码中不明确,这可能很好。由于Spring工具套件提供绘制图的工具,展现了被装配的每一个组件。同时,Java IDE也能轻松的找到全部依赖和AccountRepository类型的使用之处,同时会很快的展现返回那个类型的@Bean方法的位置。

在这种歧义不被接受的状况下而且但愿在IDE中直接从一个@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类和它们的依赖关系变得与通常基于接口的代码导航没有区别。

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

有条件的开启或关闭一个完整的@Configuration类或单独的@Bean方法时有用的,基于某种任意系统状态。一个常见的例子是尽在Spring环境启用特定配置时使用@Profile注解来激活bean。

@Profile注解实际上时使用更加令过的注解@Conditional来实现的。@Conditional注解指定在@Bean注册以前应该参考的特定的org.springframework.context.annotation.Condition实现。

实现Condition接口仅须要提供一个matches()方法,它返回true或false。例如,这里有一个用于@Profile的Condition实现:

@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的100%替代品。一些工具如Spring XML命名空间还是配置容器的理想方式。在一些状况下,XML更方便或必须,须要作一个选择:或者使用XML为中心的方法初始化容器,例如ClassPathXmlApplicationContext,或者使用Java为中心的AnnotationConfigApplicationContext初始化方法和使用@ImportResoure注解来导入须要的XML。

XML为中心的配置使用@Configuration类

也许从XML引导Spring容器,并以专用的方式包含@Configuration类时最好的方式。例如,在一个使用Spring XML的大型现有代码库中,根据须要建立@Configuration类并将它们包含到现有的XML文件会更容易。下面会展现在这种XML为中心的状况下使用@Configuration类的方法。

记住@Configuration类最终仅仅是容器中的bean定义。在这个例子中,咱们建立名为AppConfig的@Configuration类而且在system-test-config.xml做为一个bean定义包含它。由于开启了context:annotation-config/,容器会识别@Configuration注解而且正确处理在AppConfig中声明的@Bean方法。

@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:

<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会引用它,而且它不也不会经过名字从容器中获取。一样的是DataSource bean——它仅仅会经过类型装配,因此一个明确的bean id不严格须要。

由于@Configuration使用了元注解@Component,被@Configuration注释的类自动成为组件扫描的候选者。与上面场景类似的,能够从新定义syste-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为中心的配置中使用@ImportResource导入XML

在@Configuration类为主要配置机制的应用中,仍然须要使用一些XML。在这种场景下,使用@ImportResource而且定义须要的XML便可。这样作实现了以“以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);
    }

}
properties-config.xml
<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
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);
    // ...
}
相关文章
相关标签/搜索