前兩章介紹了,利用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配置的形式。