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

原创
2017/07/12 15:56
阅读数 52

1 基本概念:@Bean和@Configuration

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

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

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

@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方法意外的被多次调用并且有助于减少在“精简”模式下操作时难以追逐的微妙错误。

2 使用AnnotationConfigApplicationContext实例化Spring容器

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

当@Configuration类被作为输入提供它本身被注册为一个bean定义,并且类中所有声明的@Bean方法也被注册为bean定义。

当@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);
    // ...
}
展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部