每日一文快速入門Spring @Bean Annotaion


每日一文快速入門Spring @Bean Annotaion

細說框架每日一解


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方法不能再相互調用。


每日一文快速入門Spring @Bean Annotaion


上述內容為使用@Bean註解的全部內容,需要的小夥伴可點擊下面的鏈接移步到Github進行下載:

https://github.com/ramostear/spring-annotations-daily-example

每日一文快速入門Spring @Bean Annotaion

https://github.com/ramostear/spring-annotations-daily-example


分享到:


相關文章: