多年以來,Spring大量的XML配置及複雜的依賴管理飽受非議。html
爲了實現免XML的開發體驗。Spring加入了新的配置註解以支持Java Config開發模式,當中最重要的註解就是@Configuration和@Bean。java
在Spring新的Java-configuration支持中,最核心的部分就是使用@Configuration註解的類和使用@Bean註解的類。git
@Bean註解用於指示一個方法實例化。配置,初始化一個新的被Spring容器管理的對象。github
對於熟悉Spring <beans/>
XML配置的人來講,@Bean註解跟<bean/>
元素做用一樣。你可以在不論什麼Spring @Component中使用@Bean註解的方法。只是。它們一般和@Configuration註解的beans一塊使用。web
使用@Configuration註解一個類意味着它的主要目的是做爲bean定義的來源。spring
此外。@Configuration類贊成bean之間(inter-bean)的依賴,你僅僅需簡單地調用該類中其它的@Bean方法。
演示樣例:編程
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
上面的AppConfig類等價於如下的Spring <bean/>
XML:api
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
當@Bean方法聲明在沒有被@Conguration註解的類裏,這就是所謂的以’精簡’模式處理。數組
好比,在一個@Component中,甚至在一個普通的類中聲明的bean方法都會以’精簡’處理。緩存
跟完整@Configuration不一樣的是,精簡@Bean方法難以聲明bean之間的依賴。一般,在精簡模式中操做時,不該該在一個@Bean方法中調用還有一個@Bean方法。
一種推薦的方式是僅僅在@Configuration類中使用@Bean方法,這樣可以確保老是使用’完整’模式,避免@Bean方法意外地被調用屢次,下降那些在精簡模式下產生的很是難跟蹤的微妙bugs。
AnnotationConfigApplicationContext是在Spring 3.0中新增的。
這個多功能的ApplicationContext實現就能夠接收@Configuration類做爲輸入。也可接收普通的@Component類。及使用JSR-330元數據註解的類。
當將@Configuration類做爲輸入時,@Configuration類自己被註冊爲一個bean定義,並且該類中所有聲明的@Bean方法也被註冊爲bean定義。
當將@Component和JSR-330類做爲輸入時。它們被註冊爲bean定義,並且在需要的地方使用DI元數據。比方@Autowired或@Inject。
跟實例化一個ClassPathXmlApplicationContext時將Spring XML文件用做輸入類似,在實例化一個AnnotationConfigApplicationContext時可以使用@Configuration類做爲輸入。這就贊成Spring容器全然零XML配置:
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註解的類都可以做爲AnnotationConfigApplicationContext構造器的輸入。好比:
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 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();
}
scan(String…)
的組件掃描想要啓用組件掃描,僅僅需按例如如下方式註解你的@Configuration類:
@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig {
...
}
注: 有經驗的Spring用戶會比較熟悉來自Springcontext:
命名空間的等效XML聲明:
<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是AnnotationConfigApplicationContext的WebApplicationContext變種。當配置Spring ContextLoaderListener servlet監聽器。Spring MVC DispatcherServlet等會用到。如下的web.xml片斷配置了一個典型的Spring MVC web應用。
注意contextClass的context-param和init-param的使用:
<web-app>
<!-- 配置ContextLoaderListener使用 AnnotationConfigWebApplicationContext取代默認的XmlWebApplicationContext -->
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<!-- Configuration位置必須包括一個或多個逗號或空格分隔的全限定 @Configuration類.組件掃描中要指定該全限定包.-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.AppConfig</param-value>
</context-param>
<!-- 像尋常那樣使用ContextLoaderListener啓動根應用上下文 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--像尋常那樣聲明一個Spring MVC DispatcherServlet -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置DispatcherServlet使用AnnotationConfigWebApplicationContext取代默認的XmlWebApplicationContext -->
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<!-- 再次。配置路徑必須包括一個或多個逗號,或空格分隔的全限定@Configuration類 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.web.MvcConfig</param-value>
</init-param>
</servlet>
<!-- 將/app/*的所有請求映射到該dispatcher servlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
@Bean是一個方法級別的註解。XML<bean/>
元素的等價物。該註解提供一些<bean/>
提供的元素,好比init-method, destroy-method, autowiring和name
。
你可以在一個@Configuration註解或@Component註解的類中使用@Bean註解。
想要聲明一個bean,僅僅需簡單的使用@Bean註解一個方法。你可以使用該方法註冊一個bean定義到ApplicationContext中。類型經過方法的返回值指定。默認狀況下,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,該bean都綁定到一個TransferServiceImpl類型的對象實例:transferService -> com.acme.TransferServiceImpl
。
一個@Bean註解的方法可以有隨意數量的參數來描寫敘述構建該bean所需的依賴。
舉例來講,假設咱們的TransferService需要一個AccountRepository,咱們可以經過一個方法參數提供該依賴:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
這樣的處理機制對於基於構造器的依賴注入很是重要,詳細參考相關章節。
每個使用@Bean註解定義的類都支持常規的生命週期回調,並可以使用來自JSR-250的@PostConstruct和@PreDestroy註解,詳細參考JSR-250註解,常規的Spring生命週期回調也全支持。
假設一個bean實現InitializingBean,DisposableBean或Lifecycle。它們對應的方法會被容器調用。
標準的*Aware
接口集合,比方BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware等也都所有支持。
@Bean註解支持指定隨意數量的初始化和銷燬回調方法,類似於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();
}
}
注: 默認狀況下,使用Java config定義的beans有一個public的close
或shutdown
方法被本身主動註冊爲銷燬回調。假設你有一個public的close
或shutdown
方法。並且不想在容器關閉時調用它。你僅僅需簡單地將@Bean(destroyMethod="")
加入到bean定義以此禁用默認的判斷(inferred)模式。
對於一個經過JNDI獲取的資源來講,由於它的生命週期是由server管理,而不是應用。因此你可能想默認就這樣作,特別是針對DataSource這樣的資源:
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup("MyDS");
}
固然,在上面的Foo演示樣例中。直接在構造器中調用init()
方法也是有效的:
@Configuration
public class AppConfig {
@Bean
public Foo foo() {
Foo foo = new Foo();
foo.init();
return foo;
}
// ...
}
注: 當直接使用Java時。你可以對你的對象作不論什麼想作的事,而不老是需要依賴於容器的生命週期。
1.使用@Scope註解
你可以爲經過@Bean註解定義的bean指定一個特定的做用域。你可以使用Bean Scopes做用域中定義的不論什麼標準做用域。默認做用域爲singleton,但你可以使用@Scope做用域覆蓋它:
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
2.@Scope和scoped-proxy
Spring提供一個方便的方式來經過scoped proxies處理做用域的依賴。最簡單的方式是建立一個這樣的proxy,在使用XML配置時對應配置爲<aop:scoped-proxy/>
元素。在Java中經過@Scope註解和proxyMode屬性可以達到一樣的功能。默認沒有proxy(ScopedProxyMode.NO),但你可以指定爲ScopedProxyMode.TARGET_CLASS或ScopedProxyMode.INTERFACES。
假設你想從XML參考文檔的scoped proxy演示樣例過渡到使用Java的@Bean,它看起來可能例如如下所看到的:
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
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;
}
3.本身定義bean名稱
默認狀況下,配置類使用@Bean方法名做爲結果bean的名稱。
該功能可以被name
屬性覆蓋。
@Configuration
public class AppConfig {
@Bean(name = "myFoo")
public Foo foo() {
return new Foo();
}
}
4.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...
}
}
5.Bean描寫敘述
有時候爲一個bean提供詳細的文本描寫敘述是很是有幫助的。特別是當beans被暴露(可能經過JMX)用於監控目的時。
可以使用@Description註解爲一個@Bean加入描寫敘述:
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Foo foo() {
return new Foo();
}
}
@Configuration註解是一個類級別的註解,它意味着該對象是一個bean定義的來源。@Configuration類經過public的@Bean註解的方法來聲明beans。調用@Configuration類上的@Bean方法可用於定義bean之間的依賴。
當@Beans依賴其它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之間的引用。
正如前面提到的,lookup method injection是一個高級的特性。你應該少用。當一個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();
}
}
}
如下的演示樣例展現了一個@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中。實例化的beans默認狀況下做用域爲singleton。
這就是魔法產生的地方:所有的@Configuration類在啓動期間都是被CGLIB子類化過的(代理)。在子類中。子類的方法首先檢查容器是否緩存(scoped)對應的beans。假設沒有緩存纔會調用父類的方法,建立一個新的實例。
注意在Spring3.2以後,已經不需要加入CGLIB的依賴,由於CGLIB被又一次打包到org.springframework下。並直接包括在spring-core JAR中。
注: 該行爲依賴於bean的做用域,此處咱們討論的是單例。由於CGLIB動態代理的特性這裏有一些限制:配置類不能爲final的,它們應該有一個無參構造器。
《Spring Boot參考指南》翻譯完成,歡迎各位拍磚。