Spring @Bean是一個方法級別的註解,用於產生一個被Spring IoC容器所管理的Bean。通常情況下,@Bean可以與@Configuration和@Component註解一起使用(@Configuration和@Component是方法級別的註解)。在默認情況下@Bean註解所產生的Bean是單例模式的,此外,@Bean還可以與@Scope,@Lazy,@DependOn和@Primary註解一起使用。
1.認識@Bean
在開始深入瞭解@Bean註解的使用方式之前,先了解一些@Bean的內部細節。下面是Spring官方提供的源代碼:
<code>public @interface Bean {
@AliasFor("name")
String[] value() default {};
@AliasFor("value")
String[] name() default {};
/** @deprecated */
@Deprecated
Autowire autowire() default Autowire.NO;
boolean autowireCandidate() default true;
String initMethod() default "";
String destroyMethod() default "(inferred)";
}/<code>
通過源碼可以知道,在使用@Bean註解時,有下列一些可選項供我們選擇:
- autowireCandidate: 布爾類型,用於限定當前的Bean是否可以自動注入到其他Bean中,默認是true
- autowire: 已被廢棄
- initMethod: 在初始化Bean實例時需要調用的方法名稱。默認沒有要調用的方法
- destroyMethod: 在關閉應用上下文時要在Bean中調用的方法名稱,默認不調用任何方法
- name: 用於指定Bean的名稱
- value: name的別名,效果等同於name
注意
initMethod所指定的方法必須時不帶入參的方法,且該方法允許在運行時拋出異常
2.與@Configuration註解搭配使用
在前面的章節中,我們使用@ComponentScan註解完成了Bean的自動注入,現在,將AppConfig.java類中的@ComponentScan註解去掉,通過@Bean註解手動創建Bean。代碼如下:
<code>@Configuration
public class AppConfig {
@Bean
public MonkeyKing monkeyKing(){
return new MonkeyKing();
}
}/<code>
在MonkeyKing類中,不再使用@Component進行註解,此類有兩個屬性:name和alias,代碼如下:
<code>public class MonkeyKing {
private String name;
private String alias;
public MonkeyKing(){
this.name = "孫悟空";
this.alias = "齊天大聖";
}
public void monkeyShowTime(){
System.out.println(this.name+": 我是"+this.alias+" "+this.name);
}
}/<code>
提示
一開始,不提供getter和setter方法,通過構造器完成屬性的複製,並提供一個方法用於輸出bean的信息。
接下來,在main()方法中通過AnnotationConfigApplicationContext獲取應用上下文,然後獲取剛剛注入的Bean並調用monkeyShowTime()方法,輸出屬性信息。
<code>@SpringBootApplication
public class BeanAnnotationApplication {
public static void main(String[] args) {
SpringApplication.run(BeanAnnotationApplication.class, args);
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
MonkeyKing monkeyKing = context.getBean(MonkeyKing.class);
monkeyKing.monkeyShowTime();
context.close();
}
}/<code>
運行結果:
<code>孫悟空: 我是齊天大聖 孫悟空/<code>
3.自定義Bean name
在上面例子中,我們是通過Bean的類型從上下文中獲取Bean的實例的,除此之外,還可以在@Bean註解中使用name來指定Bean的名稱。name接收一個或多個參數。接下來,新創建一個類MarshalCanopy.java
<code>public class MarshalCanopy {
private String name;
private String alias;
public MarshalCanopy(){
this.name = "豬八戒";
this.alias = "天蓬元帥";
}
public void pigShowTime(){
System.out.println(this.name+": 大師兄,師父被妖怪抓走了!");
}
}/<code>
現在,在AppConfig類中定義新的Bean並使用name指定多個名稱:
<code>@Bean(name = {"pig","bajie"})
public MarshalCanopy marshalCanopy(){
return new MarshalCanopy();
}/<code>
最後,在main()中通過name來獲取MarshalCanopy的Bean實例:
<code>MarshalCanopy bajie = (MarshalCanopy) context.getBean("bajie");
MarshalCanopy pig = (MarshalCanopy) context.getBean("pig");
bajie.pigShowTime();
pig.pigShowTime();/<code>
運行結果:
<code>豬八戒: 大師兄,師父被妖怪抓走了!
豬八戒: 大師兄,師父被妖怪抓走了!/<code>
4. initMethod和destroyMethod
initMethod可以指定Bean在實例化過程中需要調用的方法,destroyMethod可以指定在關閉上下文時Bean需要執行的方法。接下來,在MonkeyKing中增加兩個方法birth()和destroy():
<code>public class MonkeyKing {
private String name;
private String alias;
public void birth(){
System.out.println("美猴王出世...");
}
public void destroy(){
System.out.println("孫猴子被如來壓在了五行山下...");
}
public MonkeyKing(){
this.name = "孫悟空";
this.alias = "齊天大聖";
}
public void monkeyShowTime(){
System.out.println(this.name+": 我是"+this.alias+" "+this.name);
}
}/<code>
然後,修改AppConfig中相應的配置:
<code>@Bean(initMethod = "birth",destroyMethod = "destroy")
public MonkeyKing monkeyKing(){
return new MonkeyKing();
}/<code>
最後,再次運行main()方法看看控制檯輸出內容:
<code>美猴王出世...
孫悟空: 我是齊天大聖 孫悟空
孫猴子被如來壓在了五行山下.../<code>
5.Bean之間的引用
在同一個配置中,我們可以在一個Bean的方法中直接調用另外一個Bean的方法。為了演示這一特效,新建一個類Mountain,代碼如下:
<code>public class Mountain {
private String name;
private MonkeyKing king;
public Mountain(MonkeyKing king){
this.name = "花果山";
this.king = king;
}
public void showTime(){
king.monkeyShowTime();
System.out.println("住在"+this.name+"水簾洞");
}
}/<code>
接下來,在AppConfig中配置Mountain並直接調用monkeyKing()方法,代碼如下:
<code>Configuration
public class AppConfig {
...
@Bean(name = "huaguoshan")
public Mountain mountain(){
return new Mountain(monkeyKing());
}
}/<code>
最後,在main()中獲取Mountain的Bean實例,並觀察控制檯輸出:
<code>Mountain mountain = (Mountain) context.getBean("huaguoshan");
mountain.showTime();/<code>
輸出結果:
<code>美猴王出世...
孫悟空: 我是齊天大聖 孫悟空
住在花果山水簾洞
孫猴子被如來壓在了五行山下.../<code>
6. 與@Scope,@Lazy,@Primary和@DependsOn配合使用
@Bean註解可以與Scope,@Lazy,@Primary和@DependsOn註解一起配合,對Bean的創建過程進行更細緻的控制。
6.1 與@Scope一起使用
在默認情況下,@Bean所定義的Bean屬於單例模式(singleton),如果希望修改Bean的這一屬性,可以通過@Scope註解進行修改。@Scope註解支持的參數有prototype,request和session。下面是一個簡單的案例:
<code>@Configuration
public class AppConfig{
@Bean
@Scope("prototype")
public XBean xbean(){
return new XBean();
}
}/<code>
6.2 與@Lazy一起使用
Spring IoC (ApplicationContext) 容器一般都會在啟動的時候實例化所有單實例 bean 。如果我們想要 Spring 在啟動的時候延遲加載 bean,即在調用某個 bean 的時候再去初始化,那麼就可以使用 @Lazy 註解。為此,新創建一個類SandMonk:
<code>public class SandMonk {
private String name;
private String alias;
public SandMonk(){
this.name = "沙悟淨";
this.alias = "沙和尚";
System.out.println(this.alias+": 大師兄,二師兄,師父被妖怪抓走了");
}
}/<code>
首先,不使用@Lazy註解,觀察控制檯輸出情況:
<code>@Bean
public SandMonk sandMonk(){
return new SandMonk();
}/<code>
<code>@SpringBootApplication
public class BeanAnnotationApplication {
public static void main(String[] args) {
SpringApplication.run(BeanAnnotationApplication.class, args);
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
context.close();
}
}/<code>
輸出結果:
<code>2020-03-25 16:08:01.187 INFO 13520 --- [ main] c.r.b.a.BeanAnnotationApplication : Started BeanAnnotationApplication in 1.325 seconds (JVM running for 2.104)
沙和尚: 大師兄,二師兄,師父被妖怪抓走了
Process finished with exit code 0 /<code>
接下來,將@Lazy作用在sandMonk方法上,再次運行main()方法,觀察結果。
<code>@Bean
@Lazy
public SandMonk sandMonk(){
return new SandMonk();
}/<code>
輸出結果:
<code>2020-03-25 16:11:40.658 INFO 15092 --- [ main] c.r.b.a.BeanAnnotationApplication : Started BeanAnnotationApplication in 1.041 seconds (JVM running for 1.813)
Process finished with exit code 0/<code>
提示
@Lazy默認value的值就是true,所以省略不寫,如果設置成false,則直接不使用@Lazy註解
6.3 與@DependsOn一起使用
該註解用於聲明當前bean依賴於另外一個bean。所依賴的bean會被容器確保在當前bean實例化之前被實例化。為了演示,新建一個類TangSeng:
<code>public class TangSeng {
private String name;
private String alias;
public TangSeng(){
this.name = "唐三藏";
this.alias = "唐僧";
System.out.println("name:"+this.name+",alias:"+this.alias);
}
}/<code>
接下來,在AppConfig類中配置此類,首先不使用@DependsOn註解,看看控制檯輸出情況。
<code>@Bean(name = "tangseng")
public TangSeng tangSeng(){
return new TangSeng();
}/<code>
控制檯輸出:
<code>name:唐三藏,alias:唐僧
name:孫悟空,alias:齊天大聖
美猴王出世...
name:豬八戒, alias:天蓬元帥/<code>
因Spring是順序加載AppConfig中定義的Bean,所以唐僧被第一個執行,因沙僧是延遲加載,所以控制檯並沒有輸出沙僧信息(ps:存在感太低,不急於馬上加載)。現在,在tangSeng()方法上使用@DependsOn註解,讓唐僧依賴於悟空,八戒和沙僧,然後再次執行並觀察輸出結果。
<code>@Bean(name = "tangseng")
@DependsOn(value ={"monkeyKing","bajie","sandMonk"} )
public TangSeng tangSeng(){
return new TangSeng();
}/<code>
控制檯輸出結果:
<code>name:孫悟空,alias:齊天大聖
name:豬八戒, alias:天蓬元帥
name:沙悟淨,alias:沙和尚
name:唐三藏,alias:唐僧/<code>
這次我們看到,雖然唐僧是第一個被配置的,但卻最後一個被執行,這是因為它需要等其餘三個弟子都加載完成,才能保護它西去取經。在第一次中,沙僧並未被執行,而第二次被執行,這是因為存在感再怎麼低,但師徒四人在西去的路上一個都不能少。
6.4 與@Primary一起使用
@Primary註解的作用是當應用成存在一個或多個相同類型的Bean時,優先裝配被@Primary註解的bean。為了演示,以真假美猴王為案例,創建一個Monkey接口,並讓MonkeyKing和MonkeyKingCopy兩個類實現該接口。
<code>public interface Monkey {
}/<code>
<code>public class MonkeyKing implements Monkey{
private String name;
private String alias;
public void birth(){
System.out.println("美猴王出世...");
}
public void destroy(){
System.out.println("孫猴子被如來壓在了五行山下...");
}
public MonkeyKing(){
this.name = "孫悟空";
this.alias = "齊天大聖";
System.out.println("name:"+this.name+",alias:"+this.alias);
}
public void monkeyShowTime(){
System.out.println(this.name+": 我是"+this.alias+" "+this.name);
}
}/<code>
<code>public class MonkeyKingCopy implements Monkey {
public MonkeyKingCopy(){
System.out.println("六耳獼猴");
}
}/<code>
首先,在不使用@Primary註解的情況下,配置兩個真假猴王,看控制檯輸出信息。
<code>@Bean(name = "monkey")
public Monkey monkey(){
return new MonkeyKing();
}
@Bean(name = "monkey")
public Monkey monkeyCopy(){
return new MonkeyKingCopy();
}/<code>
<code>@SpringBootApplication
public class BeanAnnotationApplication {
public static void main(String[] args) {
SpringApplication.run(BeanAnnotationApplication.class, args);
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
Monkey monkey = context.getBean(Monkey.class);
context.close();
}
}/<code>
控制檯結果:
<code>No qualifying bean of type 'com.ramostear.bean.annotation.beans.Monkey' available: expected single matching bean but found 2: monkeyKing,monkey/<code>
現在,使用@Primary對monkey()方法進行註解,再次運行main()方法,此時,控制檯將不再報錯,假猴王已經被@Primary(如來)給控制了。
<code>@Bean(name = "monkey")
@Primary
public Monkey monkey(){
return new MonkeyKing();
}/<code>
控制檯輸出:
<code>2020-03-25 17:00:31.811 INFO 14552 --- [ main] c.r.b.a.BeanAnnotationApplication : Started BeanAnnotationApplication in 1.142 seconds (JVM running for 2.011)
name:孫悟空,alias:齊天大聖
美猴王出世.../<code>
7.與@Component一起使用
@Bean同樣可以在被@Component註解的類中使用。在此情況下,@Bean註解的方法將被Spring容器視為普通工廠方法,並支持作用域和生命週期內的回調。值得注意的是,與@Component一起使用時,不能再像@Configuration註解那樣,內部定義的bean方法不能再相互調用。
上述內容為使用@Bean註解的全部內容,需要的小夥伴可點擊下面的鏈接移步到Github進行下載:
https://github.com/ramostear/spring-annotations-daily-example
閱讀更多 ramostear 的文章