就是要讓你徹底學會 @Bean 註解
@Bean 註解全解析
隨著SpringBoot的流行,基於註解式開發的熱潮逐漸覆蓋了基於XML純配置的開發,而作為Spring中最核心的bean當然也能夠使用註解的方式進行表示。所以本篇就來詳細的討論一下作為Spring中的Bean到底都有哪些用法。
@Bean 基礎宣告
Spring的@Bean註解用於告訴方法,產生一個Bean物件,然後這個Bean物件交給Spring管理。產生這個Bean物件的方法Spring只會呼叫一次,隨後這個Spring將會將這個Bean物件放在自己的IOC容器中。
SpringIOC 容器管理一個或者多個bean,這些bean都需要在@Configuration註解下進行建立,在一個方法上使用@Bean註解就表明這個方法需要交給Spring進行管理。
快速搭建一個maven專案並配置好所需要的Spring 依賴
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.13.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.3.13.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.3.13.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.3.13.RELEASE</version> </dependency>
在src根目錄下建立一個AppConfig的配置類,這個配置類也就是管理一個或多個bean 的配置類,並在其內部宣告一個myBean的bean,並建立其對應的實體類
@Configuration public class AppConfig { // 使用@Bean 註解表明myBean需要交給Spring進行管理 // 未指定bean 的名稱,預設採用的是 "方法名" + "首字母小寫"的配置方式 @Bean public MyBean myBean(){ return new MyBean(); } } public class MyBean { public MyBean(){ System.out.println("MyBean Initializing"); } }
在對應的test資料夾下建立一個測試類SpringBeanApplicationTests,測試上述程式碼的正確性
public class SpringBeanApplicationTests { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); context.getBean("myBean"); } }
輸出 : MyBean Initializing
隨著SpringBoot的流行,我們現在更多采用基於註解式的配置從而替換掉了基於XML的配置,所以本篇文章我們主要探討基於註解的@Bean以及和其他註解的使用。
@Bean 基本構成及其使用
在簡單介紹了一下如何宣告一個Bean元件,並將其交給Spring進行管理之後,下面我們來介紹一下Spring 的基本構成
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Bean { @AliasFor("name") String[] value() default {}; @AliasFor("value") String[] name() default {}; Autowire autowire() default Autowire.NO; String initMethod() default ""; String destroyMethod() default AbstractBeanDefinition.INFER_METHOD; }
@Bean不僅可以作用在方法上,也可以作用在註解型別上,在執行時提供註冊。
-
value:name屬性的別名,在不需要其他屬性時使用,也就是說value 就是預設值
-
name:此bean 的名稱,或多個名稱,主要的bean的名稱加別名。如果未指定,則bean的名稱是帶註解方法的名稱。如果指定了,方法的名稱就會忽略,如果沒有其他屬性宣告的話,bean的名稱和別名可能通過value屬性配置
-
autowire :此註解的方法表示自動裝配的型別,返回一個Autowire型別的列舉,我們來看一下Autowire列舉型別的概念
// 列舉確定自動裝配狀態:即,bean是否應該使用setter注入由Spring容器自動注入其依賴項。 // 這是Spring DI的核心概念 public enum Autowire { // 常量,表示根本沒有自動裝配。 NO(AutowireCapableBeanFactory.AUTOWIRE_NO), // 常量,通過名稱進行自動裝配 BY_NAME(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME), // 常量,通過型別進行自動裝配 BY_TYPE(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE); private final int value; Autowire(int value) { this.value = value; } public int value() { return this.value; } public boolean isAutowire() { return (this == BY_NAME || this == BY_TYPE); } }
autowire的預設值為No,預設表示不通過自動裝配。
initMethod: 這個可選擇的方法在bean例項化的時候呼叫,InitializationBean介面允許bean在合適的時機通過設定註解的初始化屬性從而呼叫初始化方法,InitializationBean 介面有一個定義好的初始化方法
void afterPropertiesSet() throws Exception;
Spring不推薦使用InitializationBean 來呼叫其初始化方法,因為它不必要地將程式碼耦合到Spring。Spring推薦使用@PostConstruct註解或者為POJO類指定其初始化方法這兩種方式來完成初始化。
不推薦使用:
public class InitBean implements InitializingBean { public void afterPropertiesSet() {} }
destroyMethod: 方法的可選擇名稱在呼叫bean示例在關閉上下文的時候,例如JDBC的close()方法,或者SqlSession的close()方法。DisposableBean 介面的實現允許在bean銷燬的時候進行回撥呼叫,DisposableBean 介面之後一個單個的方法
void destroy() throws Exception;
Spring不推薦使用DisposableBean 的方式來初始化其方法,因為它會將不必要的程式碼耦合到Spring。作為替代性的建議,Spring 推薦使用@PreDestory註解或者為@Bean註解提供 destroyMethod 屬性。
不推薦使用:
public class DestroyBean { public void cleanup() {} }
推薦使用:
public class MyBean { public MyBean(){ System.out.println("MyBean Initializing"); } public void init(){ System.out.println("Bean 初始化方法被呼叫"); } public void destroy(){ System.out.println("Bean 銷燬方法被呼叫"); } } @Configuration public class AppConfig { // @Bean @Bean(initMethod = "init", destroyMethod = "destroy") public MyBean myBean(){ return new MyBean(); } }
修改一下測試類,測試其初始化方法和銷燬方法在何時會被呼叫
public class SpringBeanApplicationTests { public static void main(String[] args) { // ------------------------------ 測試一 ------------------------------ ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); // context.getBean("myBean"); // 變體 context.getBean("myBean"); ((AnnotationConfigApplicationContext) context).destroy(); // ((AnnotationConfigApplicationContext) context).close(); } }
初始化方法在得到Bean的例項的時候就會被呼叫,銷燬方法在容器銷燬或者容器關閉的時候會被呼叫。
@Bean 註解與其他註解產生的火花
在上面的一個小節中我們瞭解到了@Bean註解的幾個屬性,但是對於@Bean註解的功能來講這有點太看不起bean了,@Bean另外一個重要的功能是能夠和其他註解產生化學反應,如果你還不瞭解這些註解的話,那麼請繼續往下讀,你會有收穫的。
這一節我們主要探討@profile,@scope,@lazy,@depends-on @primary等註解
@Profile 註解
@Profile的作用是把一些meta-data進行分類,分成Active和InActive這兩種狀態,然後你可以選擇在active 和在Inactive這兩種狀態下配置bean,在Inactive狀態通常的註解有一個!操作符,通常寫為:@Profile("!p"),這裡的p是Profile的名字。
三種設定方式:
-
可以通過ConfigurableEnvironment.setActiveProfiles()以程式設計的方式啟用
-
可以通過AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME (spring.profiles.active )屬性設定為JVM屬性
-
作為環境變數,或作為web.xml 應用程式的Servlet 上下文引數。也可以通過@ActiveProfiles 註解在整合測試中以宣告方式啟用配置檔案。
作用域
-
作為類級別的註釋在任意類或者直接與@Component 進行關聯,包括@Configuration 類
-
作為原註解,可以自定義註解
-
作為方法的註解作用在任何方法
注意:
如果一個配置類使用了Profile 標籤或者@Profile 作用在任何類中都必須進行啟用才會生效,如果@Profile({"p1","!p2"}) 標識兩個屬性,那麼p1 是啟用狀態 而p2 是非啟用狀態的。
現有一個POJO類為Subject學科類,裡面有兩個屬性,一個是like(理科)屬性,一個是wenke(文科)屬性,分別有兩個配置類:
一個是AppConfigWithActiveProfile ,一個是AppConfigWithInactiveProfile,當系統環境是 "like"的時候就註冊 AppConfigWithActiveProfile ,如果是 "wenke",就註冊 AppConfigWithInactiveProfile,來看一下這個需求如何實現
Subject.java
// 學科 public class Subject { // 理科 private String like; // 文科 private String wenke; get and set ... @Override public String toString() { return "Subject{" + "like='" + like + ''' + ", wenke='" + wenke + ''' + '}'; } }
AppConfigWithActiveProfile.java 註冊Profile 為like 的時候
@Profile("like") @Configuration public class AppConfigWithActiveProfile { @Bean public Subject subject(){ Subject subject = new Subject(); subject.setLike("物理"); return subject; } }
AppConfigWithInactiveProfile.java 註冊Profile 為wenke 的時候
@Profile("wenke") @Configuration public class AppConfigWithInactiveProfile { @Bean public Subject subject(){ Subject subject = new Subject(); subject.setWenke("歷史"); return subject; } }
修改一下對應的測試類,設定系統環境,當Profile 為like 和 wenke 的時候分別註冊各自對應的屬性
// ------------------------------ 測試 profile ------------------------------ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); // 啟用 like 的profile context.getEnvironment().setActiveProfiles("like"); context.register(AppConfigWithActiveProfile.class,AppConfigWithInactiveProfile.class); context.refresh(); Subject subject = (Subject) context.getBean("subject"); System.out.println("subject = " + subject);
把context.getEnvironment().setActiveProfiles("wenke") 設定為wenke,觀察其對應的輸出內容發生了變化,這就是@Profile的作用,有一層可選擇性註冊的意味。
@Scope 註解
在Spring中對於bean的預設處理都是單例的,我們通過上下文容器.getBean方法拿到bean容器,並對其進行例項化,這個例項化的過程其實只進行一次,即多次getBean 獲取的物件都是同一個物件,也就相當於這個bean的例項在IOC容器中是public的,對於所有的bean請求來講都可以共享此bean。
那麼假如我不想把這個bean被所有的請求共享或者說每次呼叫我都想讓它生成一個bean例項該怎麼處理呢?
多例Bean
bean的非單例原型範圍會使每次發出對該特定bean的請求時都建立新的bean例項,也就是說,bean被注入另一個bean,或者通過對容器的getBean()方法呼叫來請求它,可以用如下圖來表示:
通過一個示例來說明bean的多個例項
新建一個AppConfigWithAliasAndScope配置類,用來定義多例的bean,
@Configuration public class AppConfigWithAliasAndScope { /** * 為myBean起兩個名字,b1 和 b2 * @Scope 預設為 singleton,但是可以指定其作用域 * prototype 是多例的,即每一次呼叫都會生成一個新的例項。 */ @Bean({"b1","b2"}) @Scope("prototype") public MyBean myBean(){ return new MyBean(); } }
測試一下多例的情況:
// ------------------------------ 測試scope ------------------------------ ApplicationContext context = new AnnotationConfigApplicationContext(AppConfigWithAliasAndScope.class); MyBean myBean = (MyBean) context.getBean("b1"); MyBean myBean2 = (MyBean) context.getBean("b2"); System.out.println(myBean); System.out.println(myBean2);
除了多例的情況下,Spring還為我們定義了其他情況:
singleton和prototype 一般都用在普通的Java專案中,而request、session、application、websocket都用於web應用中。
request、session、application、websocket的作用範圍
當你使用web-aware的ApplicationContext應用程式上下文的時候,可以體會到 request、session、application、websocket 的作用範圍,比如XmlWebApplicationContext的實現類。
如果你使用了像是ClassPathXmlApplicationContext的上下文環境時,就會丟擲IllegalStateException因為Spring不認識這個作用範圍。
@Lazy 註解
@Lazy : 表明一個bean 是否延遲載入,可以作用在方法上,表示這個方法被延遲載入;可以作用在@Component (或者由@Component 作為原註解) 註釋的類上,表明這個類中所有的bean 都被延遲載入。
如果沒有@Lazy註釋,或者@Lazy 被設定為false,那麼該bean 就會急切渴望被載入;除了上面兩種作用域,@Lazy 還可以作用在@Autowired和@Inject註釋的屬性上,在這種情況下,它將為該欄位建立一個惰性代理,作為使用ObjectFactory或Provider的預設方法。
下面來演示一下:
@Lazy @Configuration @ComponentScan(basePackages = "com.spring.configuration.pojo") public class AppConfigWithLazy { @Bean public MyBean myBean(){ System.out.println("myBean Initialized"); return new MyBean(); } @Bean public MyBean IfLazyInit(){ System.out.println("initialized"); return new MyBean(); } }
修改測試類
public class SpringConfigurationApplication { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfigWithLazy.class); // 獲取啟動過程中的bean 定義的名稱 for(String str : context.getBeanDefinitionNames()){ System.out.println("str = " + str); } } }
輸出你會發現沒有關於bean的定義資訊,但是當把@Lazy 註釋拿掉,你會發現輸出了關於bean的初始化資訊
@DependsOn 註解
指當前bean所依賴的bean。任何指定的bean都能保證在此bean建立之前由IOC容器建立。在bean沒有通過屬性或建構函式引數顯式依賴於另一個bean的情況下很少使用,可能直接使用在任何直接或者間接使用 Component 或者Bean 註解表明的類上。來看一下具體的用法。
新建三個Bean,分別是FirstBean、SecondBean、ThirdBean三個普通的bean,新建AppConfigWithDependsOn並配置它們之間的依賴關係
public class FirstBean { @Autowired private SecondBean secondBean; @Autowired private ThirdBean thirdBean; public FirstBean() { System.out.println("FirstBean Initialized via Constuctor"); } } public class SecondBean { public SecondBean() { System.out.println("SecondBean Initialized via Constuctor"); } } public class ThirdBean { public ThirdBean() { System.out.println("ThirdBean Initialized via Constuctor"); } } @Configuration public class AppConfigWithDependsOn { @Bean("firstBean") @DependsOn(value = { "secondBean", "thirdBean" }) public FirstBean firstBean() { return new FirstBean(); } @Bean("secondBean") public SecondBean secondBean() { return new SecondBean(); } @Bean("thirdBean") public ThirdBean thirdBean() { return new ThirdBean(); } }
使用測試類進行測試,如下
// ------------------------------ 測試 DependsOn ------------------------------ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfigWithDependsOn.class); context.getBean(FirstBean.class); context.close();
輸出 :
SecondBean Initialized via Constuctor
ThirdBean Initialized via Constuctor
FirstBean Initialized via Constuctor
由於firstBean 的建立過程首先需要依賴secondBean 和 thirdBean的建立,所以secondBean 首先被載入其次是thirdBean 最後是firstBean。
如果把@DependsOn 註解加在AppConfigWithDependsOn 類上則它們的初始化順序就會變為 firstBean、secondBean、thirdBean。
@Primary 註解
指示當多個候選者有資格自動裝配依賴項時,應優先考慮bean。此註解在語義上就等同於在Spring XML中定義的bean 元素的primary屬性。
注意:除非使用component-scanning進行元件掃描,否則在類級別上使用@Primary不會有作用。如果@Primary 註解定義在XML中,那麼@Primary 的註解元註解就會忽略,相反使用<bean primary = "true|false"/>
@Primary 的兩種使用方式
-
與@Bean 一起使用,定義在方法上,方法級別的註解
-
與@Component 一起使用,定義在類上,類級別的註解
通過一則示例來演示一下:
新建一個AppConfigWithPrimary類,在方法級別上定義@Primary註解
@Configuration public class AppConfigWithPrimary { @Bean public MyBean myBeanOne(){ return new MyBean(); } @Bean @Primary public MyBean myBeanTwo(){ return new MyBean(); } }
上面程式碼定義了兩個bean ,其中myBeanTwo 由@Primary 進行標註,表示它首先會進行註冊,使用測試類進行測試
// ------------------------------ 測試 Primary ------------------------------ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfigWithPrimary.class); MyBean bean = context.getBean(MyBean.class); System.out.println(bean);
你可以嘗試放開@Primary ,使用測試類測試的話會發現出現報錯資訊,因為你嘗試獲取的是MyBean.class,而我們程式碼中定義了兩個MyBean 的型別,所以需要@Primary 註解表明哪一個bean需要優先被獲取。
文章參考:
spring @Profile的運用示例
https://www.javaguides.net/2018/10/spring-dependson-annotation-example.html