一文了解和 Spring Bean 有關(guān)的那些注解
隨著Spring的流行,我們經(jīng)歷過基于XML-Based 的配置,隨著SpringBoot的流行,我們逐漸使用基于注解的配置替換掉了基于XML-Based的配置,那么你知道基于注解的配置的基礎(chǔ)組件都是什么嗎?都包括哪些要素?那么本節(jié)就來探討一下。注:本篇文章更多的是討論Spring基于注解的配置一覽,具體的技術(shù)可能沒有那么深,請各位大佬見諒。
探討主題:
- 基礎(chǔ)概念:@Bean 和 @Configuration
- 使用AnnotationConfigApplicationContext 實例化Spring容器
- 使用@Bean 注解
- 使用@Configuration 注解
- 編寫基于Java的配置
- Bean定義配置文件
- PropertySource 抽象類
- 使用@PropertySource
- 占位符的聲明
基礎(chǔ)概念:@Bean 和 @Configuration
Spring中新的概念是支持@Bean注解 和 @Configuration 注解的類。@Bean 注解用來表明一個方法實例化,配置并且通過IOC容器初始化并管理一個新的對象。@Bean注解就等同于XML-Based中的
使用@Configuration 注解的主要作用是作為bean定義的類,進一步來說,@Configuration注解的類允許通過調(diào)用同類中的其他@Bean標(biāo)注的方法來定義bean之間依賴關(guān)系。如下所示:
新建一個maven項目(我一般都直接創(chuàng)建SpringBoot項目,比較省事),創(chuàng)建AppConfig,MyService,MyServiceImpl類,代碼如下:
- @Configuration
- public class AppConfig {
- @Bean
- public MyService myService(){
- return new MyServiceImpl();
- }
- }
- public interface MyService {}
- public class MyServiceImpl implements MyService {}
上述的依賴關(guān)系等同于XML-Based:
- <beans>
- <bean id="myService",class="com.spring.annotation.service.impl.MyServiceImpl"/>
- </beans>
使用AnnotationConfigApplicationContext 實例化Spring容器
AnnotationConfigApplicationContext 基于注解的上下文是Spring3.0 新添加的注解,它是ApplicationContext的一個具體實現(xiàn),它可以接收@Configuration注解的類作為輸入?yún)?shù),還能接收使用JSR-330元注解的普通@Component類。
當(dāng)提供了@Configuration 類作為輸入?yún)?shù)時,@Configuration類就會注冊作為bean的定義信息并且所有聲明@Bean的方法也都會作為bean的定義信息。
當(dāng)提供@Component和JSR-330 聲明的類時,他們都會注冊作為bean的定義信息,并且假設(shè)在必要時在這些類中使用諸如@Autowired或@Inject之類的注解
簡單的構(gòu)造
在某些基于XML-Based的配置,我們想獲取上下文容器使用ClassPathXmlApplicationContext,現(xiàn)在你能夠使用@Configuration 類來實例化AnnotationConfigApplicationContext。
在MyService中添加一個printMessage()方法,實現(xiàn)類實現(xiàn)對應(yīng)的方法。新建測試類進行測試
- public class ApplicationTests {
- public static void main(String[] args) {
- ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
- MyService service = context.getBean(MyService.class);
- // printMessage() 輸出something...
- service.printMessage();
- }
- }
如前所述,AnnotationConfigApplicationContext不僅限于使用@Configuration類。任何@Component或JSR-330帶注釋的類都可以作為輸入提供給構(gòu)造函數(shù),如下例所示
- public class ApplicationTests {
- public static void main(String[] args) {
- ApplicationContext context = new AnnotationConfigApplicationContext(MyServiceImpl.class,Dependency1.class,Dependency2.class);
- MyService myService = context.getBean(MyService.class);
- myService.printMessage();
- }
- }
使用register注冊IOC容器
你可以實例化AnnotationConfigApplicationContext通過使用無參數(shù)的構(gòu)造器并且使用register方法進行注冊,它和AnnotationConfigApplicationContext帶參數(shù)的構(gòu)造器起到的效果相同。
- public class ApplicationTests {
- 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);
- System.out.println(ctx.getBean(OtherConfig.class));
- System.out.println(ctx.getBean(AdditionalConfig.class));
- myService.printMessage();
- }
- }
OtherConfig.class 和 AdditionalConfig.class 是使用@Component 標(biāo)注的類。
允許scan()方法進行組件掃描
為了允許組件進行掃描,需要在@Configuration配置類添加@ComponentScan()注解,改造之前的AdditionalConfig類,如下:
- @Configuration
- @ComponentScan(basePackages = "com.spring.annotation.config")
- public class AdditionalConfig {}
@ComponentScan指定了基礎(chǔ)掃描包位于com.spring.annotation.config下,所有位于該包范圍內(nèi)的bean都會被注冊進來,交由Spring管理。它就等同于基于XML-Based的注解:
- <beans>
- <context:component-scan base-package="com.spring.annotation.config/>
- </beans>
AnnotationConfigApplicationContext中的scan()方法以允許相同的組件掃描功能,如以下示例所示:
- public static void main(String[] args) {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
- ctx.scan("com.spring.annotation");
- ctx.refresh();
- MyService myService = ctx.getBean(MyService.class);
- }
為什么說@Configuration用法和@Component都能夠標(biāo)注配置類?因為@Configuration的元注解就是@Component。
- @Target({ElementType.TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Component
- public @interface Configuration {
- String value() default "";
- }
使用AnnotationConfigWebApplicationContext支持web容器
AnnotationConfigApplicationContext的一個WebApplicationContext的變化是使用AnnotationConfigWebApplicationContext。配置Spring ContextLoaderListener的servlet監(jiān)聽器,Spring MVC的DispatcherServlet等時,可以使用此實現(xiàn)。以下web.xml代碼段配置典型的Spring MVC Web應(yīng)用程序(請注意context-param和init-param的使用)
- <web-app>
- <!-- 配置web上下文監(jiān)聽器使用 AnnotationConfigWebApplicationContext 而不是默認(rèn)的
- XmlWebApplicationContext -->
- <context-param>
- <param-name>contextClass</param-name>
- <param-value>
- org.springframework.web.context.support.AnnotationConfigWebApplicationContext
- </param-value>
- </context-param>
- <!-- 配置位置必須包含一個或多個以逗號或空格分隔的完全限定的@Configuration類。 也可以為組件掃描指定完全 限定的包-->
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>com.spring.annotation.config.AdditionalConfig</param-value>
- </context-param>
- <!--使用ContextLoaderListener像往常一樣引導(dǎo)根應(yīng)用程序上下文-->
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
- </listener>
- <!-- 定義一個SpringMVC 核心控制器 DispatcherServlet-->
- <servlet>
- <servlet-name>dispatcher</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <!-- 配置web上下文監(jiān)聽器使用 AnnotationConfigWebApplicationContext 而不是默認(rèn)的
- 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.spring.annotation.config.MvcConfig</param-value>
- </init-param>
- </servlet>
- <!-- 將/app/* 的所有請求映射到調(diào)度程序servlet -->
- <servlet-mapping>
- <servlet-name>dispatcher</servlet-name>
- <url-pattern>/app/*</url-pattern>
- </servlet-mapping>
- </web-app>
使用@Bean注解
@Bean 注解是一個方法級別的注解,能夠替換XML-Based中的標(biāo)簽,@Bean注解同樣支持標(biāo)簽支持的屬性,像是 init-method, destroy-method, autowiring。
定義一個Bean
與基礎(chǔ)概念中Bean的定義相同,讀者可以參考基礎(chǔ)概念部分進行了解,我們不在此再進行探討。
Bean的依賴
@Bean 注解可以有任意數(shù)量的參數(shù)來構(gòu)建其依賴項,例如
- public class MyService {
- private final MyRepository myRepository;
- public MyService(MyRepository myRepository) {
- this.myRepository = myRepository;
- }
- public String generateSomeString() {
- return myRepository.findString() + "-from-MyService";
- }
- }
- @Configuration
- class MyConfiguration {
- @Bean
- public MyService myService() {
- return new MyService(myRepository());
- }
- @Bean
- public MyRepository myRepository() {
- return new MyRepository();
- }
- }
- public class MyRepository {
- public String findString() {
- return "some-string";
- }
- }
接受生命周期回調(diào)
任何使用@Bean的注解都支持生命周期的回調(diào),使用JSR-220提供的@PostConstruct和@PreDestory注解來實現(xiàn)。如果bean實現(xiàn)了InitializingBean,DisposableBean或者Lifecycle接口,他們的方法會由IOC容器回調(diào)。一些以Aware的實現(xiàn)接口(像是BeanFactoryAware,BeanNameAware, MessageSourceAware, ApplicationContextAware等)也支持回調(diào)。
@Bean注解支持特定的初始化和銷毀方法,就像XML-Based中的init-method和 destory-method中的bean屬性,下面這個例子證實了這一點
- @Configuration
- public class AppConfig {
- @Bean(initMethod = "init")
- public BeanOne beanOne(){
- return new BeanOne();
- }
- @Bean(destroyMethod = "cleanup")
- public BeanTwo beanTwo(){
- return new BeanTwo();
- }
- }
- class BeanOne {
- public void init(){}
- }
- class BeanTwo {
- public void cleanup(){}
- }
對于上面的例子,也可以手動調(diào)用init()方法,與上面的initMethod 方法等效
- @Bean
- public BeanOne beanOne(){
- BeanOne beanOne = new BeanOne();
- beanOne.init();
- return beanOne;
- }
當(dāng)你直接使用Java開發(fā)時,你可以使用對象執(zhí)行任何操作,并且不必總是依賴于容器生命周期。
Bean的作用范圍
Spring包括@Scope注解能夠讓你指定Bean的作用范圍,Bean的Scope默認(rèn)是單例的,也就是說@Bean標(biāo)注的對象在IOC的容器中只有一個。你可以重寫@Scope的作用范圍,下面的例子說明了這一點,修改OtherConfig如下
- @Configuration
- public class OtherConfig {
- @Bean
- @Scope("prototype")
- public Dependency1 dependency1(){
- return new Dependency1();
- }
- }
每次嘗試獲取dependency1這個對象的時候都會重新生成一個新的對象實例。下面是Scope的作用范圍和解釋:
@Scope和Scoped-proxy
Spring提供了一種通過scoped proxies與scoped依賴一起作用的方式。最簡單的在XML環(huán)境中創(chuàng)建代理的方式是通過
自定義Bean名稱
默認(rèn)的情況下,配置類通過@Bean配置的默認(rèn)名稱(方法名***個字母小寫)進行注冊和使用,但是你可以更換@Bean的name為你想指定的名稱。修改AdditionalConfig 類
- @Configuration
- //@ComponentScan(basePackages = "com.spring.annotation.config")
- public class AdditionalConfig {
- @Bean(name = "default")
- public Dependency2 dependency2(){
- return new Dependency2();
- }
- }
Bean的別名
有時候需要為單例的bean提供多個名稱,也叫做Bean的別名。Bean注解的name屬性接收一個Array數(shù)組。下面這個例子證實了這一點:
- @Configuration
- public class OtherConfig {
- // @Bean
- // @Scope("prototype")
- // public Dependency1 dependency1(){
- // return new Dependency1();
- // }
- @Bean({"dataSource", "dataSourceA", "dataSourceB"})
- public DataSource dataSource(){
- return null;
- }
- }
Bean的描述
有時,提供更詳細(xì)的bean描述信息會很有幫助(但是開發(fā)很少使用到)。為了增加一個對@Bean的描述,你需要使用到@Description注解
- @Configuration
- public class OtherConfig {
- // @Bean
- // @Scope("prototype")
- // public Dependency1 dependency1(){
- // return new Dependency1();
- // }
- // @Bean({"dataSource", "dataSourceA", "dataSourceB"})
- // public DataSource dataSource(){
- // return null;
- // }
- @Bean
- @Description("此方法的bean名稱為dependency1")
- public Dependency1 dependency1(){
- return new Dependency1();
- }
- }
使用@Configuration注解
已經(jīng)把@Configuration的注解說明的比較詳細(xì)了。
組成Java-Based環(huán)境配置的條件
Spring基于注解的配置能夠允許你自定義注解,同時能夠降低配置的復(fù)雜性。
使用@Import注解
就像在Spring XML文件中使用元素來幫助模塊化配置一樣,@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();
- }
- }
現(xiàn)在,在實例化上下文時,不需要同時指定ConfigA.class 和 ConfigB.class ,只需要顯示提供ConfigB
- public static void main(String[] args) {
- ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
- A a = ctx.getBean(A.class);
- B b = ctx.getBean(B.class);
- }
這種方法簡化了容器實例化,因為只需要處理一個類,而不是要求你在構(gòu)造期間記住可能大量的@Configuration類
有選擇性的包含@Configuration 類和@Bean 方法
選擇性的允許或者禁止@Configuration注解的類和@Bean注解的方法是很有用的,基于一些任意系統(tǒng)狀態(tài)。一個常見的例子是只有在Spring環(huán)境中啟用了特定的配置文件時才使用@Profile注釋激活bean。
@Profile注解也實現(xiàn)了更靈活的注解@Conditional,@Conditional 注解表明在注冊@Bean 之前應(yīng)參考特定的Condition實現(xiàn)。
實現(xiàn)Condition接口就會提供一個matched方法返回true或者false
更多關(guān)于@Conditional 的示例,請參考https://www.cnblogs.com/cxuanBlog/p/10960575.html
結(jié)合Java與XML配置
Spring @Configuration類能夠100%替換XML配置,但一些工具(如XML命名空間)仍舊是配置容器的***方法,在這種背景下,使用XML使很方便的而且使剛需了。你有兩個選擇:使用以XML配置實例化容器為中心,例如:ClassPathXmlApplicationContext導(dǎo)入XML或者實例化以Java配置為中心的AnnotationConfigApplicationContext并提供ImportResource注解導(dǎo)入需要的XML配置。
將@Configuration聲明為普通的bean元素
請記住,@Configuration類存放的是容器中的bean定義信息,下面的例子中,我們將會創(chuàng)建一個@Configuration類并且加載了外部xml配置。下面展示了一個普通的Java配置類
- @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>
- <!--允許開啟 @Autowired 或者 @Configuration-->
- <context:annotation-config/>
- <!-- 讀取外部屬性文件 -->
- <!-- 更多關(guān)于屬性讀取的資料,參考 https://www.cnblogs.com/cxuanBlog/p/10927819.html -->
- <context:property-placeholder location="classpath:/com/spring/annotation/jdbc.properties"/>
- <bean class="com.spring.annotation.config.AppConfig"/>
- <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
- <property name="driverClassName" value="${jdbc.driverClassName}" />
- <property name="url" value="${jdbc.url}"/>
- <property name="username" value="${jdbc.username}"/>
- <property name="password" value="${jdbc.password}"/>
- </bean>
- </beans>
引入jdbc.properties建立數(shù)據(jù)庫連接
- jdbc.driverClassName=com.mysql.jdbc.Driver
- jdbc.url=jdbc:mysql://localhost:3306/sys
- jdbc.username=root
- jdbc.password=123456
- public static void main(String[] args) {
- ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/spring/annotation/system-test-config.xml");
- TransferService transferService = ctx.getBean(TransferService.class);
- // ...
- }
在system-test-config.xml中,AppConfig 對應(yīng)的標(biāo)簽沒有聲明id屬性,雖然這樣做是可以接受的,但是沒有必要,因為沒有其他bean引用它,并且不太可能通過名稱從容器中獲取它。同樣的,DataSource bean只是按類型自動裝配,因此不嚴(yán)格要求顯式的bean id。
使用<> 挑選指定的@Configuration類
因為@Configuration的原注解是@Component,所以@Configuration注解的類也能用于組件掃描,使用與前一個示例中描述的相同的方案,我們可以重新定義system-test-config.xml以利用組件掃描。請注意,在這種情況下,我們不需要顯式聲明<context:annotation-config />,因為<context:component-scan />啟用相同的功能。
- <beans>
- <context:component-scan base-package="com.spring.annotation"/>
- <context:property-placeholder location="classpath:/com/spring/annotation/jdbc.properties"/>
- <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
- <property name="driverClassName" value="${jdbc.driverClassName}" />
- <property name="url" value="${jdbc.url}"/>
- <property name="username" value="${jdbc.username}"/>
- <property name="password" value="${jdbc.password}"/>
- </bean>
- </beans>
@Configuration 類使用@ImportResource
在基于Java注解的配置類中,仍然可以使用少量的@ImportResource導(dǎo)入外部配置,***的方式就是兩者結(jié)合,下面展示了一下Java注解結(jié)合XML配置的示例
- @Configuration
- @ImportResource("classpath:/com/spring/annotation/properties-config.xml")
- public class AppConfig {
- @Value("${jdbc.driverClassName}")
- private String driver;
- @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/spring/annotation/jdbc.properties"/>
- </beans>
jdbc.properties
- jdbc.driverClassName=com.mysql.jdbc.Driver
- jdbc.url=jdbc:mysql://localhost:3306/sys
- jdbc.username=root
- jdbc.password=123456
- public static void main(String[] args) {
- ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
- TransferService transferService = ctx.getBean(TransferService.class);
- // ...
- }