前两章介绍了,利用java注解,我们可以大大减少xml配置的篇幅,只需在xml里开启对相关注解的支持即可。这一节介绍基于java代码的容器配置,让我们完全摆脱对xml的依赖。
java代码配置实际上是结合注解和java代码,其中的关键在@Configuration和@Bean这两个注解。@Configuration标注java类,指明这是一个Spring的配置类,我们可以看做xml配置文件的替代物;@Bean标注配置类里的方法,指明这是一个定义bean的工厂方法,可以看做<bean>标签的替代物。
@Configuration类
@Configuration可以类比一个xml配置文件,它通过成员方法来定义bean;@Configuration类上可以附加其他注解来开启一些容器级别的功能,比如:java package扫描,属性占位符替换等。
@Configuration和@Bean
我们直接看一个示例就能明白这两个注解的工作方式:
@Configuration
public class AppConfig {
@Bean
public ClientService clientService() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientDao clientDao() {
return new ClientDaoImpl();
}
}
从@Configuration的定义可知,它标注的类本身也定义了一个bean;只不过容器会特殊对待,会扫描到所有的@Bean方法,识别这些bean定义。
与xml配置相比,@Bean方法是朴素的java代码,非常直观地创建bean对象并初始化它,java编译器还能帮我们做类型检查,有一种返璞归真的感觉。
组合@Configuration
一个Configuration类,可以Import另外一个Configuration类。这样一来,当前者被加载的时候,后者也被加载。
@Configuration
public class ConfigA {
...
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
...
}
@ComponentScan
该注解用来自动扫描java package,找到这些package下面用@Component注解定义的bean,与前面介绍的xml component-scan标签作用一致;也可以添加过滤器,具体方式也是一致的。
@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig {
...
}
还可用调用context.scan方法来手动扫描java package:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
加载Configuration
Spring 3.0提供了一种新类型的Context叫做AnnotationConfigApplicationContext,可以接受一个或多个@Configuration标注的类class对象,作为容器初始化的入口。
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
动态注册Configuration
可以调用AnnotationConfigApplicationContext.register动态注册Configuration类:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
@Bean
@Bean注解相当于xml的<bean>标签,基本实现了后者的全部功能。而且由于@Bean注解的是方法,可以使用java代码来初始化bean,充分利java语言的能力,避免xml这种文本式配置的不便。
Bean生命周期回调
@Bean注解可以指定任意的回调方法:
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public BeanOne beanOne() {
return new BeanOne();
}
@Bean(destroyMethod = "cleanup")
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
值得注意的是,@Bean注解定义的bean,如果包含public的close或shutdown方法,会被认定为默认的bean销毁方法,可以通过以下的方式来取消这种行为:
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup("MyDS");
}
上面的代码里面,DataSource是来自外部的资源,所以并不希望在容器关闭的时候,DataSource实例的close方法被调用,通过将destroyMethod=""取消了对close的自动调用。
最后,如果bean的初始化方法,在bean对象创建好了就能调用,那么直接在@Bean方法里面调用就好,没必要使用@Bean的initMethod属性来指定:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
}
Bean作用域
通过@Scope注解来声明:
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
Spring提供了一种Scoped proxies机制来解决不同作用域bean之间的依赖问题,@Scope注解通过proxyMode属性来支持该机制。proxyMode默认值是ScopedProxyMode.NO(没有代理),你可以指定ScopedProxyMode.TARGET_CLASS或ScopedProxyMode.INTERFACES。
@SessionScope是spring-web基于@Scope创建的一个注解,可以更加方便地声明bean为session作用域,它默认将代理模式设定为ScopedProxyMode.TARGET_CLASS。
Bean的名字
bean的名字默认就是@Bean方法的名字,当然可以通过注解属性来指定:
@Configuration
public class AppConfig {
@Bean(name = "myThing")
public Thing thing() {
return new Thing();
}
}
还可以一次声明多个bean的别名:
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
Bean描述
通过注解@Description指定,在通过JMX监控bean的时候比较有用:
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
static @Bean方法
一般@Bean都声明为Configuration的实例方法,这样的bean总是晚于Configuration的实例被创建。但是之前讲过,像BeanPostProcessor和BeanFactoryPostProcessor这样的特殊bean,需要在容器的初始化早期被创建。
因此,我们需要static的@Bean方法,以便Spring容器提前初始化他们:
@Configuration
public class Config {
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
configurer.setLocation(new ClassPathResource("q.properties"));
return configurer;
}
}
依赖注入
上一章节介绍的@Autowire注解仍然有效,不过在Configuration类里,Spring更推荐将依赖注入到bean的构造参数里,有几种方法可以做到这一点。
@Bean方法参数
最简单最直接的方式是通过@Bean方法的参数:
@Configuration
public class ServiceConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
容器在调用ServiceConfig.transferService初始化bean的时候,会自动寻找匹配的AccountRepository bean作为参数。
调用@Bean方法
如果被依赖的bean在同一个Configuration里定义,那么可以直接调用对应的@Bean方法:
@Configuration
public class ServiceConfig {
@Bean
public AccountRepository accountRepository() {
return new AccountRepository();
}
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository());
}
}
配置类Autowire配置类
如果被依赖的bean在其他Configuration里,那么可以这个配置类Autowire进来,这利用了配置类也是一个bean的事实。
这种方式的好处是,可以明确地限定被依赖的bean来自哪个配置类。
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
配置类Autowire依赖bean
最后一种方式,先把外部Bean作为字段Autowire进来,稍微有些画蛇添足之感:
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
结合XML和JAVA配置
如果系统中存在xml和java两种配置形式,那么有两种结合方式,一种是以xml为中心,创建ClassPathXmlApplicationContext,在xml里面开启annotation-config支持。
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService() {
return new TransferService(accountRepository());
}
}
<beans>
<!-- 开启对@Configuration支持 -->
<context:annotation-config/>
//通过bean引入AppConfig
<bean class="com.acme.AppConfig"/>
//或者通过scan方式
<context:component-scan base-package="com.acme"/>
//其他xml配置
</beans>
二是,以java配置为中心,使用AnnotationConfigApplicationContext,在@Configuration类通过@ImportResource注解来导入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>
@Bean方法的工作原理
先看看下面这个样例代码:
@Configuration
public class AppConfig {
@Bean
public ClientService clientService() {
return new ClientServiceImpl(clientDao());
}
@Bean
public ClientService clientService2() {
return new ClientServiceImpl(clientDao());
}
@Bean
public ClientDao clientDao() {
return new ClientDaoImpl();
}
}
clientDao方法虽然被调用了多次,但容器内只会有一个ClientDao实例,因为它的作用域是SingleTon(@Bean的默认作用域)。这看起来与Java方法调用语义相违背,是如何发生的呢?答案是CGLIB技术。
AppConfig本身可以理解为其他bean的FactoryBean,但是容器不会直接使用它,而是创建AppConfig的一个CGLIB子类,这个子类会拦截所有的@Bean方法,并插入bean生命周期管理逻辑。
同时,受制于java语言本身的规则:子类无法覆盖父类的private、final方法,也无法覆盖父类的static方法,因此Spring限制@Bean方法不能是pivate的或final的;而对于static的@bean方法,Spring既需要它,又无法提供任何保护机制,需要使用者来遵守:不直接调用static的@bean方法。
注:CGLIB技术是AOP主题的内容,暂时不深究。
普通bean里面定义@Bean方法
在一个非@Configuration注解的bean里面也可以定义@Bean方法,例如:
@Component
public class FactoryMethodComponent {
@Bean
protected TestBean publicInstance() {
TestBean tb = new TestBean("publicInstance", 1);
return tb;
}
}
此时对容器来说,@Bean依然定义了一个有效的bean,该bean能够被正常使用;但是,由于有没有CGLib子类来拦截publicInstance方法,每次对它的调用会创建一个新的实例。
注:虽然不会报错,单绝对应该避免这种使用方式,不明白Sping为什么不禁止它。
总结
@Configuration注解使我们可以用Java代码彻底取代了xml配置文件;@Bean注解让我们可以编写一段java代码来定义一个bean,无论是对比xml格式的bean定义,还是@Component模式的bean定义,都要强大得多。
@Configuration配置类相比xml更干净整洁,相比@Component又能将配置信息集中起来;在一个大型系统里,我们可以为每个模块创建单独一个@Configuration配置类,是一种非常好的组织Spring配置的形式。