原文:
7.12.1 基本概念: @Bean 和 @Configuration
在Spring新的Java配置支持中,其核心構(gòu)件是@Configuration注解類(lèi)和@Bean注解方法.
@Bean 注解用來(lái)表示方法實(shí)例化,配置以及初始化由Spring IoC容器管理的新對(duì)象.
對(duì)于那些熟悉Spring <beans/> XML配置的人來(lái)說(shuō),@Bean 注解扮演了與<bean/> 元素相同的角色.你可以在任何Spring@Component 上使用@Bean 注解方法,但通常情況下,它們經(jīng)常與@Configuration beans一起使用.
@Configuration 注解類(lèi)表示其主要目的作為bean定義的來(lái)源(source).
此外, @Configuration 類(lèi)允許在同一個(gè)類(lèi)中調(diào)用其它的@Bean 方法來(lái)定義它們之間的依賴(lài)關(guān)系.
可能最簡(jiǎn)單的@Configuration 類(lèi)可以像下面這樣讀取:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
AppConfig 類(lèi)等價(jià)于下面的Spring <beans/> XML:
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
完整(full)@Configuration vs 精簡(jiǎn)(lite) @Beans 模式?
當(dāng) @Bean 方法聲明在未用@Configuration 注解的類(lèi)中時(shí),他們被稱(chēng)為“精簡(jiǎn)”模式處理.
例如,bean 方法聲明在@Component 中或普通類(lèi)(plain old class)中將被認(rèn)為是精簡(jiǎn)的('lite').
不像完整@Configuration, 精簡(jiǎn)@Bean 方法不能容易地聲明bean之間的依賴(lài)關(guān)系.通常情況下,當(dāng)處于精簡(jiǎn)模式時(shí),一個(gè)@Bean 方法不能調(diào)用另一個(gè)@Bean方法.
只有在@Configuration 類(lèi)中使用@Bean 方法才是一種可確保總是處于完整模式下的推薦方法.
這可以阻止同一個(gè)@Bean方法不小心被調(diào)用多次,有助于減少細(xì)微的bug(當(dāng)運(yùn)行在精簡(jiǎn)模式下時(shí),這些bug很難追查)。
@Bean 和 @Configuration 注解會(huì)在下面的章節(jié)中詳細(xì)討論. 但首先,我們要講解基于Java配置來(lái)創(chuàng)建Spring 容器的各種方法.
7.12.2 使用AnnotationConfigApplicationContext來(lái)實(shí)例化Spring 容器
下面的章節(jié)記錄了Spring的AnnotationConfigApplicationContext, 它是Spring 3.0中新引入的.
這個(gè)多用途的 ApplicationContext 實(shí)現(xiàn)不僅有能力接受@Configuration 類(lèi)作為它的輸入,也可以接受@Component 類(lèi)和使用JSR-330 元數(shù)據(jù)注解類(lèi)作為它的輸入.
當(dāng)@Configuration 類(lèi)作為輸入時(shí), @Configuration 類(lèi)自身也將作為bean定義進(jìn)行注冊(cè),并且這個(gè)類(lèi)中所有聲明了@Bean 方法也會(huì)以bean的定義進(jìn)行注冊(cè).
當(dāng)提供@Component 和JSR-330時(shí),它們也是按bean定義進(jìn)行注冊(cè), 并會(huì)假設(shè)在那些類(lèi)的必要地方使用DI元數(shù)據(jù),如@Autowired 或 @Injectare.
簡(jiǎn)單構(gòu)造
與實(shí)例化ClassPathXmlApplicationContext時(shí)需要Spring XML文件方式相同, 當(dāng)實(shí)例化AnnotationConfigApplicationContext時(shí),也需要@Configuration 類(lèi)作為它的輸入.
這允許在Spring容器中完全自由地使用XML:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
正如上面所講的, AnnotationConfigApplicationContext 不限制于只同@Configuration 類(lèi)工作. 任何@Component 或JSR-330 注解類(lèi)都可以作為其構(gòu)造器的輸入.例如:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
上面的代碼會(huì)假設(shè)MyServiceImpl, Dependency1 和 Dependency2 使用的是Spring 依賴(lài)注入,如@Autowired.
通過(guò)編程使用register(Class<?>…)來(lái)構(gòu)建容器
AnnotationConfigApplicationContext 可以使用無(wú)參構(gòu)造器來(lái)實(shí)例化,然后再調(diào)用register() 方法來(lái)配置. 當(dāng)通過(guò)編程來(lái)構(gòu)建AnnotationConfigApplicationContext時(shí),這種方法是特別有用的.
public static void main(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();
}
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…)來(lái)啟用組件掃描
要啟用組件掃描,只需要像這樣下面注解你的@Configuration類(lèi):
@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig { ... }
@ComponentScan(basePackages = "com.acme")
public class AppConfig { ... }
有經(jīng)驗(yàn)的 Spring用戶(hù)非常熟悉與它等價(jià)的來(lái)自Spring context:命名空間的XML聲明:
<beans>
<context:component-scan base-package="com.acme"/>
</beans>
<context:component-scan base-package="com.acme"/>
</beans>
在上面的例子中,com.acme 包將掃描, 會(huì)查找所有@Component注解的類(lèi), 那些類(lèi)也會(huì)以Spring bean的定義注冊(cè)在容器中. AnnotationConfigApplicationContext 暴露了 scan(String…) 方法以允許執(zhí)行相同組件掃描功能:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
記住@Configuration 類(lèi)是帶有@Component的meta-annotated, 因此它們是組件掃描的候選人!
在上面的例子中, 假設(shè)AppConfig 聲明在com.acme 包(或它的子包)中,在調(diào)用scan()方法時(shí),它也會(huì)挑選出來(lái),并當(dāng)refresh()的時(shí)候,它所有的 @Bean 方法將被處理,并以bean的定義注冊(cè)到容器中.
使用 AnnotationConfigWebApplicationContext來(lái)支持Web應(yīng)用程序
AnnotationConfigApplicationContext 的WebApplicationContext變異是AnnotationConfigWebApplicationContext. 此實(shí)現(xiàn)可用于配置Spring ContextLoaderListener servlet 監(jiān)聽(tīng)器, Spring MVC DispatcherServlet等等.
下面在web.xml中配置典型Spring MVC web application的片斷.
注意 contextClass 和 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>
<!-- 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>
7.12.3 使用@Bean 注解
@Bean 是一個(gè)方法級(jí)注解,XML <bean/> 元素的直接模擬.
你可將 @Bean 注解用在@Configuration注解或@Component注解的類(lèi)中.
聲明bean
要聲明一個(gè)bean,可簡(jiǎn)單地在方法上使用@Bean 注解.你使用這種方法來(lái)在A(yíng)pplicationContext中注冊(cè)bean的定義,其特定類(lèi)型是方法的返回值.默認(rèn)情況下,bean的名稱(chēng)將與方法名稱(chēng)同名.
下面是一個(gè)@Bean 方法聲明的簡(jiǎn)單示例:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
上述的配置與下面的Spring XML配置完全等價(jià):
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
兩種Bean聲明都使名為transferService 的bean在ApplicationContext中可用,并且都綁定TransferServiceImpl的對(duì)象實(shí)例上:
transferService -> com.acme.TransferServiceImpl
Bean 依賴(lài)
@Bean 注解方法可以有任意數(shù)量的參數(shù)來(lái)描述構(gòu)建bean時(shí)所需的依賴(lài).例如,如果我們的TransferService需要一個(gè)AccountRepository,我們可以通過(guò)方法參數(shù)來(lái)實(shí)現(xiàn)那種依賴(lài):
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
接收生命周期回調(diào)
任何使用@Bean 注解定義的類(lèi)都支持正常的生命周期回調(diào),也可以使用 JSR-250中的@PostConstruct 和 @PreDestroy 注解, 參考JSR-250 annotations 來(lái)了解詳情.
完全支持 Spring lifecycle 回調(diào). 如果一個(gè)bean實(shí)現(xiàn)了InitializingBean, DisposableBean, 或 Lifecycle, 它們各自的方法都會(huì)被容器調(diào)用.
標(biāo)準(zhǔn)集合的 *Aware 接口,如 BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware等等都完全支持.
@Bean 注解支持指定任意初始化和銷(xiāo)毀回調(diào)方法, 非常類(lèi)似于Spring XML bean元素中的
init-method和destroy-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();
}
}
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();
}
}
默認(rèn)情況下,使用Java配置的Bean都有一個(gè) public close 或shutdown 方法以在銷(xiāo)毀回調(diào)時(shí)自動(dòng)調(diào)用.
如果你有一個(gè)public close 或shutdown 方法,并且不想在容器關(guān)閉時(shí)自動(dòng)被調(diào)用,你可以簡(jiǎn)單地在Bean定義中添加@Bean(destroyMethod="")來(lái)禁用默認(rèn)(推斷)模式.
你可能很想為那些通過(guò)JNDI獲取到的資源那樣做,因?yàn)樗鼈兊纳芷谑窃趹?yīng)用程序外被管理的.
特別是,對(duì)于DataSource這樣的資源一定要保證這樣做,因?yàn)樗鼈冊(cè)贘ava EE應(yīng)用程序服務(wù)器上可能會(huì)出現(xiàn)問(wèn)題.
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup("MyDS");
}
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup("MyDS");
}
此外,使用@Bean 方法,你通常會(huì)選擇使用編程來(lái)進(jìn)行JNDI查找: 要么使用Spring的JndiTemplate/JndiLocatorDelegate幫助類(lèi),要么直接使用 JNDI InitialContext,但絕不是JndiObjectFactoryBean,因?yàn)樗鼤?huì)強(qiáng)制你聲明FactoryBean 的返回類(lèi)型,而不是實(shí)際目標(biāo)類(lèi)型, 這使得它很難在其它@Bean中跨引用調(diào)用.
當(dāng)然,在上面 Foo 的情況中, 其效果與直接在構(gòu)造器中調(diào)用init()方法是一樣的:
@Configuration
public class AppConfig {
@Bean
public Foo foo() {
Foo foo = new Foo();
foo.init();
return foo;
}
// ...
}
public class AppConfig {
@Bean
public Foo foo() {
Foo foo = new Foo();
foo.init();
return foo;
}
// ...
}
指定bean范圍
使用@Scope注解
默認(rèn)scope是singleton,但你可以用@Scope 注解進(jìn)行覆蓋:
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
@Scope and scoped-proxy
通過(guò)scoped proxies,Spring提供了一種便利的方式來(lái)與scoped依賴(lài)一起工作. 在XML配置中,創(chuàng)建代理的最簡(jiǎn)單方式是使用<aop:scoped-proxy/> 元素. 在Java中,使用@Scope注解與proxyMode屬性來(lái)配置bean也可以達(dá)到同樣的效果. 默認(rèn)是無(wú)代理( ScopedProxyMode.NO),但你可以指定為ScopedProxyMode.TARGET_CLASS 或ScopedProxyMode.INTERFACES.
如果你看了XML參考文檔中的 scoped proxy 例子,再來(lái)看 @Bean ,它看起來(lái)就像下面這樣:
// 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;
}
@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;
}
自定義bean名稱(chēng)
默認(rèn)情況下, 配置類(lèi)使用@Bean 方法的名稱(chēng)作為結(jié)果bean的名稱(chēng).這可以通過(guò)name 屬性來(lái)覆蓋.
@Configuration
public class AppConfig {
@Bean(name = "myFoo")
public Foo foo() {
return new Foo();
}
}
public class AppConfig {
@Bean(name = "myFoo")
public Foo foo() {
return new Foo();
}
}
Bean 別名
如在Section 7.3.1, “Naming beans”中講述的, 有時(shí)候期望單個(gè)bean上能指定多個(gè)名稱(chēng),即別名. @Bean 注解的name屬性可通過(guò)接受一個(gè)數(shù)組來(lái)達(dá)到這個(gè)目的.
@Configuration
public class AppConfig {
@Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
public class AppConfig {
@Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
Bean 描述
有時(shí)候,為bean提供更加詳盡的文本描述通常是有幫助的. 特別是bean出于監(jiān)控目的暴露(可通過(guò)JMX)時(shí),特別有用.
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Foo foo() {
return new Foo();
}
}
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Foo foo() {
return new Foo();
}
}
7.12.4 使用@Configuration注解
@Configuration 是一個(gè)類(lèi)級(jí)注解,用來(lái)表示bean定義的來(lái)源. @Configuration 類(lèi)可通過(guò)public @Bean注解方法來(lái)聲明bean.在@Configuration中調(diào)用@Bean方法也可用來(lái)定義bean之間的依賴(lài). 參考 Section 7.12.1, “Basic concepts: @Bean and @Configuration"一般介紹.
注入bean 依賴(lài)
當(dāng) @Beans依賴(lài)另一個(gè)bean時(shí), 表示依賴(lài)很簡(jiǎn)單,就像一個(gè)bean的方法調(diào)用另一個(gè):
@Configuration
public class AppConfig {
@Bean
public Foo foo() {
return new Foo(bar());
}
@Bean
public Bar bar() {
return new Bar();
}
}
public class AppConfig {
@Bean
public Foo foo() {
return new Foo(bar());
}
@Bean
public Bar bar() {
return new Bar();
}
}
在上面的例子中,通過(guò)構(gòu)造器注入, foo bean 收到了bar的引用.
聲明bean依賴(lài)的方法只能是@Bean 方法在@Configuration 類(lèi)中聲明. 不能通過(guò)@Component類(lèi)來(lái)聲明bean依賴(lài)關(guān)系.
Lookup 方法注入
如前所述,Lookup方法注入是一個(gè)高級(jí)的功能,你應(yīng)該很少使用。它在一個(gè)singleton范圍bean依賴(lài)于原型范圍的bean是有用的。使用java類(lèi)型進(jìn)行這種配置,提供了一種自然的方式實(shí)現(xiàn)這一模式。
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();
}
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配置支持 , 你可以創(chuàng)建CommandManager的子類(lèi),抽象createCommand()方法可通過(guò)查找新(prototype(原型))command 對(duì)象來(lái)覆蓋:
@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();
}
}
}
@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();
}
}
}
關(guān)于基于Java配置內(nèi)部是如何工作的更多信息
下面的例子展示了一個(gè)@Bean注解方法被調(diào)用了兩次:
@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();
}
}
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();
}
}
注:4.2.5版本上沒(méi)發(fā)現(xiàn)這個(gè)問(wèn)題
clientDao() 在clientService1()和clientService2()中各調(diào)用了一次.因?yàn)檫@個(gè)方法會(huì)創(chuàng)建一個(gè)新ClientDaoImpl 的實(shí)例,并將其返回,你可能正希望有2個(gè)實(shí)例(一個(gè)服務(wù)一個(gè)).
那肯定是有問(wèn)題的:在Spring中,實(shí)例bean默認(rèn)有一個(gè)singleton scope . 這個(gè)魔法來(lái)自: 所有@Configuration 類(lèi)在啟動(dòng)期間都會(huì)使用CGLIB來(lái)子類(lèi)化. 在子類(lèi)中,孩子方法在調(diào)用其父類(lèi)方法創(chuàng)建新實(shí)例前,它會(huì)為所有緩存(scoped)bean首先檢查容器.注意在 Spring 3.2中,沒(méi)有必須在類(lèi)路徑中添加CGLIB,因?yàn)镃GLIB的類(lèi)已被重新打包放到了org.springframework.cglib之下,并且直接包含在spring-core JAR中.
根據(jù)bean的scope不同,行為也可能不同.在這里我們談?wù)摰氖莝ingletons.
CGLIB在啟動(dòng)時(shí)動(dòng)態(tài)添加特性有個(gè)限制 dynamically adds features at startup-time:
- Configuration 類(lèi)不應(yīng)該是final
- 它們必須有一個(gè)無(wú)參構(gòu)造器
7.12.5 組合基于 Java的配置
使用@Import注解
非常像Spring xml文件中的<import/> 元素,這樣有助于模塊化配置,@Import 注解允許從其它配置類(lèi)中加載@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();
}
}
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
現(xiàn)在,實(shí)例化上下文時(shí),不需要同時(shí)指定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);
}
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);
}
這種方法簡(jiǎn)化了容器實(shí)例化,只需要處理一個(gè)類(lèi)就可以了,在構(gòu)造期間,不需要開(kāi)發(fā)者記住大量數(shù)目的@Configuration 類(lèi).
Injecting dependencies on imported @Bean definitions
上述例子可以工作,但過(guò)于簡(jiǎn)單化。在大多數(shù)實(shí)際情況下,bean將有一個(gè)跨配置類(lèi)的依賴(lài)關(guān)系。使用XML時(shí),這本身不是一個(gè)問(wèn)題,因?yàn)?/a>不涉及到編譯器,通過(guò)一個(gè)簡(jiǎn)單聲明ref =“somebean",就可以信任Spring在容器初始化就可以完成這些工作。
當(dāng)然,當(dāng)使用@Configuration類(lèi)時(shí),java編譯器限制了配置模型,在引用其它bean時(shí)必須是有效的java語(yǔ)法。
當(dāng)然,當(dāng)使用@Configuration類(lèi)時(shí),java編譯器限制了配置模型,在引用其它bean時(shí)必須是有效的java語(yǔ)法。
幸運(yùn)的是,解決這個(gè)問(wèn)題很簡(jiǎn)單。正如我們已經(jīng)討論過(guò)的,@Bean可以有一個(gè)任意數(shù)量的參數(shù)來(lái)描述的bean的依賴(lài)關(guān)系。讓我們考慮一個(gè)帶有@Configuration類(lèi)的真實(shí)的場(chǎng)景,每個(gè)依賴(lài)的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");
}
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");
}
這是達(dá)到同樣結(jié)果的另一種方式.記住 @Configuration 類(lèi)最終只是容器中的另一個(gè)bean: 這意味著,它可以像其它bean一樣利用@Autowired 和 @Value 注入!
確保你注入的依賴(lài)關(guān)系是最簡(jiǎn)單的一種。@Configuration 類(lèi)在上下文初始化時(shí)很早就被處理了,并強(qiáng)制注入依賴(lài)的資源(這種方式可能會(huì)導(dǎo)致意外的早期初始化). 無(wú)論何時(shí),只要有可能,就要借助以參數(shù)為基礎(chǔ)的注入,如上面的例子。
同時(shí),通過(guò)@Bean來(lái)定義BeanPostProcessor 和 BeanFactoryPostProcessor 時(shí)要特別小心.那些通常應(yīng)該聲明為static @Bean 方法, 不觸發(fā)其包含配置類(lèi)的實(shí)例化. 否則,@Autowired 和 @Value 將不能在配置類(lèi)上工作,因?yàn)閯?chuàng)建bean實(shí)例的過(guò)程過(guò)早了.
@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");
}
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 類(lèi)中的構(gòu)造器注入只在Spring Framework 4.3中支持. 同時(shí)也要注意到如果目標(biāo)bean只定義了一個(gè)構(gòu)造器,也沒(méi)有必要指定 @Autowired ;在上面的例子中, @Autowired 不必出現(xiàn)在RepositoryConfig 構(gòu)造器上.
在上述場(chǎng)景中,@Autowired 工作得很好并提供了預(yù)期的模塊化,但要明確確定自動(dòng)裝配bean定義聲明的位置依然有些模糊.例如,當(dāng)開(kāi)發(fā)者查看ServiceConfig時(shí), 你如何明確的知道@Autowired AccountRepository bean在哪里聲明的呢?在代碼中雖是不明確的,但這可能正好.
同時(shí),你的Java IDE 也能容易地找到所有聲明和AccountRepository 類(lèi)型的使用, 并且可以快速向你展示@Bean 方法返回類(lèi)型的地址.
如果這種模糊性是不可接受的,你希望能在IDE中直接從一個(gè)@Configuration 導(dǎo)航到另一個(gè),那么可考慮自動(dòng)裝裝配置類(lèi)自身:
@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());
}
}
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());
}
}
在上面的場(chǎng)景中,可以非常明確地知道AccountRepository 在什么位置定義的. 但現(xiàn)在, ServiceConfig 緊密耦合了RepositoryConfig,這是一種權(quán)衡. 通過(guò)使用基于接口的或基于抽象類(lèi)的@Configuration類(lèi),這種緊密耦合可以有所緩解。考慮下面的代碼:
@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");
}
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");
}
現(xiàn)在ServiceConfig相對(duì)于具體的DefaultRepositoryConfig來(lái)說(shuō)是松散耦合的,并且內(nèi)置IDE工具仍然是有用的: 對(duì)于開(kāi)發(fā)者來(lái)說(shuō),很容易看到RepositoryConfig實(shí)現(xiàn)的類(lèi)型層次結(jié)構(gòu).通過(guò)這種方式, 導(dǎo)航@Configuration 類(lèi)及其依賴(lài)與平常導(dǎo)航基于接口的代碼沒(méi)有什么不同.
有條件地包含@Configuration 類(lèi)或 @Bean 方法
基于某些系統(tǒng)狀態(tài)有條件地啟用或禁用某個(gè)@Configuration類(lèi)甚至單個(gè)@Bean方法,這通常是有用的. 這方面一個(gè)常見(jiàn)的例子是當(dāng)某個(gè)特定profile在Spring環(huán)境中啟用了后,使用@Profile 注解來(lái)激活beans (參考Section 7.13.1, “Bean definition profiles”來(lái)了解細(xì)節(jié)).
@Profile 注解實(shí)際上是使用更加靈活的@Conditional注解來(lái)實(shí)現(xiàn)的. @Conditional注解表示特定的org.springframework.context.annotation.Condition實(shí)現(xiàn)(在@Bean注冊(cè)應(yīng)該先進(jìn)行咨詢(xún)).
Condition 接口的實(shí)現(xiàn)只是簡(jiǎn)單的提供了一個(gè)返回true或false的matches(…)方法.
下面是@Profile的實(shí)際Condition實(shí)現(xiàn):
@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;
}
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;
}
結(jié)合Java和XML配置
Spring@Configuration 類(lèi)支持的目的不是100%完全代替Spring XML. 一些如Spring XML命名空間的設(shè)施仍然是容器中配置的理想方式. 在這種情況下,XML依然是便利的或必須的,你可以選擇:
要么使用ClassPathXmlApplicationContext以XML為中心的方式來(lái)實(shí)例化容器,要么以Java為中心使用AnnotationConfigApplicationContext, @ImportResource注解導(dǎo)入必要XML的方式來(lái)實(shí)例化容器.
"以XML為中心的”@Configuration的使用
從XML,并以包含@Configuration類(lèi)的特設(shè)方式來(lái)啟動(dòng)Spring容器是更可取的方案.舉例來(lái)說(shuō),在一個(gè)大量使用Spring XML的現(xiàn)有代碼庫(kù)中,它可以根據(jù)需要來(lái)創(chuàng)建@Configuration類(lèi),并從現(xiàn)有XML中包含它們.
下面你會(huì)發(fā)現(xiàn)在這種“XML為中心”的情況下使用@Configuration類(lèi)的操作.
記住@Configuration類(lèi)最終只是容器中的bean定義.在這個(gè)例子中,我們創(chuàng)建了名為AppConfig的@Configuration 類(lèi),并以<bean/>定義將其包含在了system-test-config.xml文件中.
由于開(kāi)啟了<context:annotation-config/> ,容器會(huì)識(shí)別@Configuration 注解并會(huì)適當(dāng)?shù)靥幚鞟ppConfig中聲明的@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());
}
}
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>
<!-- 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=
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);
// ...
}
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
在上面的system-test-config.xml文件中, AppConfig <bean/> 并沒(méi)有聲明id元素.然而這樣做是可以接受的,因?yàn)闆](méi)有其它bean會(huì)引用它, 并且顯示式通過(guò)名稱(chēng)來(lái)獲取它也是不太可能發(fā)生的.
DataSource bean也有類(lèi)似的原因 -它只根據(jù)類(lèi)型來(lái)自動(dòng)裝配,因此明確的bean id 不是嚴(yán)格需要的.
因?yàn)?@Configuration是@Component的元注解, @Configuration注解類(lèi)將自動(dòng)參與組件掃描.
與上面場(chǎng)景相同,我們可以重新定義 system-test-config.xml 以利用組件掃描功能.
注意,在這種情況中,我們不需要明確地聲明<context:annotation-config/>, 因?yàn)?/span><context:component-scan/> 開(kāi)啟了同樣的功能.
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>
<!-- 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 class-centric use of XML with @ImportResource
在@Configuration類(lèi)的應(yīng)用程序中,其主要機(jī)制是用于配置容器, 使用一些XML它仍然可能是必須的.在這些場(chǎng)景中, 只要簡(jiǎn)單地使用@ImportResource 并定義需要的XML就可以了.這樣就實(shí)現(xiàn)了“java為中心”的方法來(lái)配置容器,并最低限度的保留了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);
}
}
@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>
<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=
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);
// ...
}
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}