1. 程式人生 > 其它 >基於Java的顯式配置

基於Java的顯式配置

經過前文的學習,我們知道基於XML的顯式配置就是採用XML顯式配置Spring容器。自然而然的,基於Java的顯式配置則是採用Java這種程式語言顯式配置Spring容器。至於能用Java怎麼配置,讓我們趁熱打鐵,緊接前文,看看同樣的專案能用基於Java的顯式配置怎麼實現,進而學習基於Java的顯式配置的基礎知識。為此,請開啟前文實現的music-player專案,新建AppConfig類,修改如下:

1 package com.dream;
2 
3 import org.springframework.context.annotation.*;
4 
5 @Configuration
6
public class AppConfig { 7 }

乍眼一瞧,大家肯定得犯嘀咕:“咋寫了個啥也沒有的類咧!”可是當我們靜下心來仔細瞧上一瞧,就會驚人地發現這個荒蕪的類上有個神祕的@Configuration註解。

這是怎麼回事呢?

原來,@Configuration是Spring提供的一個註解。這個註解可以把某個類標為配置類,使之具有向Spring容器提供配置資訊的能力。由此可見,AppConfig既不是一個啥也沒有的類,也不是一個普通的類,而是一個具有@Configuration註解的配置類。只因我們尚未往類裡新增配置資訊,所以空落落的。既然這樣,就讓我們敲些程式碼充實一下該類。如下所示:

 1 package com.dream;
 2 
 3 import org.springframework.context.annotation.*;
 4 
 5 @Configuration
 6 public class AppConfig {
 7     @Bean(name = "music")
 8     public Music produceMusic() {
 9         var music = new Music();
10         music.setMusicName("執著");
11         return music;
12     }
13 14 @Bean(name = "player") 15 public Player producePlayer() { 16 var music = this.produceMusic(); 17 var player = new Player(); 18 player.setMusic(music); 19 return player; 20 } 21 }

程式碼非常簡單,就實現了兩個方法:一個方法能夠建立Music物件;一個方法能夠建立Player物件。需要特別留意的是,這兩個方法無一例外,都帶有一個神祕的@Bean註解。

這是怎麼回事呢?

原來,@Bean也是Spring提供的一個註解。這個註解可以把某個方法標為配置方法,使之能被Spring應用上下文發現之後進行呼叫,從而建立Bean。@Bean註解有個常用的name屬性,用於指定Bean的id。其值預設是與之相關的方法的方法名。

於是我們知道了,Spring應用上下文載入AppConfig配置類之後,發現produceMusic方法帶有@Bean(name="music")註解。於是呼叫produceMusic方法建立一個型別為Music,id為music的Bean。發現producePlayer方法帶有@Bean(name="player")註解,於是呼叫producePlayer方法建立一個型別為Player,id為player的Bean。至於Spring應用上下文怎樣載入配置類,請看以下程式碼:

 1 package com.dream;
 2 
 3 import org.springframework.context.annotation.*;
 4 
 5 public class Main {
 6     public static void main(String[] args) {
 7         try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
 8             var player = context.getBean("player", Player.class);
 9             player.play();
10             player.pause();
11         }
12     }
13 }

這裡用到AnnotationConfigApplicationContext。AnnotationConfigApplicationContext是Spring實現的另外一種Spring應用上下文,能夠載入配置類,呼叫配置方法建立Bean。其建構函式簽名如下:

public AnnotationConfigApplicationContext(Class<?>... componentClasses)

這個構建函式接受一個Class<?>... 型別的引數,用於告訴Spring應用上下文能從哪些配置類里加載配置資訊。於是,我們建立AnnotationConfigApplicationContext物件的時候傳入AppConfig.class,告訴AnnotationConfigApplicationContext載入AppConfig配置類,呼叫AppConfig配置類裡的配置方法建立Bean。

於是,Spring應用上下文載入配置類之後順利完成了Bean的建立。這些Bean存在Spring應用上下文中,由Spring應用上下文管理著。我們只需呼叫getBean方法獲取id為player,型別為Player的Bean。隨後呼叫Bean的play方法播放音樂,pause方法暫停音樂即可實現音樂播放器。現在執行一下程式,輸出如下:

程式是順利跑起來了,同時我們也開始困惑了。前文曾經提及,Spring應用上下文建立的Bean預設是單例的。可是,Spring應用上下文呼叫配置方法建立Bean時,不是呼叫一次方法就建立一個物件,呼叫兩次方法就建立兩個物件嗎?如此,Spring容器建立的Bean怎麼可能還是單例的呢?

為了解開這個迷題,讓我們修改一下AppConfig類,新增producePlayer_2方法如下:

 1 package com.dream;
 2 
 3 import org.springframework.context.annotation.*;
 4 
 5 @Configuration
 6 public class AppConfig {
 7     @Bean(name = "music")
 8     public Music produceMusic() {
 9         var music = new Music();
10         music.setMusicName("執著");
11         return music;
12     }
13 
14     @Bean(name = "player")
15     public Player producePlayer() {
16         var music = this.produceMusic();
17         var player = new Player();
18         player.setMusic(music);
19         return player;
20     }
21 
22     @Bean(name="player_2")
23     public Player producePlayer_2() {
24         Music music = this.produceMusic();
25         Player player = new Player();
26         player.setMusic(music);
27         return player;
28     }
29 }

現在,producePlayer和producePlayer_2方法都能建立player物件,並且建立player物件時都會呼叫produceMusic方法建立music物件進行音樂的注入。於是問題來了,producePlayer方法呼叫produceMusic方法建立的music物件和producePlayer_2方法呼叫produceMusic方法建立的music物件是同一個嗎?

當然是的。實際上,Spring應用上下文瞧見@Configuration註解之後並不會直接載入AppConfig配置類,而是基於AppConfig配置類生成一個代理類;之後又把帶有@Bean註解的配置方法生成代理方法。因此,每次呼叫produceMusic方法建立music物件的時候,並不是直接呼叫produceMusic方法建立music物件,而是呼叫Spring應用上下文生成的代理類裡的代理方法進行建立。代理方法建立Bean之前會先判斷一下即將建立的物件Spring應用上下文裡是不是已經有了。如果已經有了,則直接返回Spring應用上下文裡的物件,不再建立。如果Spring應用上下文裡還沒有這個物件,則呼叫配置方法進行建立。由是我們的困惑解開了,Spring應用上下文載入配置類之後建立的Bean預設還是單例的。

那麼,producePlayer方法建立的player物件和producePlayer_2方法建立的player物件也是同一個物件嗎?

當然不是。為什麼呢?因為這裡定義了兩個方法,不同的方法執行不同的程式碼建立的Bean當然是不同的。至於代理,則是Java這門程式語言的一個高階特性,超出本書的討論範圍。如果大家對此不太瞭解又想深入學習的話,建議大家閱讀一下關於代理的Java書籍或文章。這裡不作介紹。

至此,基於Java的顯式配置的基礎知識介紹完了。前文曾經提及,除了顯式配置,Spring還提供了自動配置。令人興奮的是,自動配置還是一種遠比顯式配置更為迷人的配置方式。至於自動配置有多迷人,我們將在下一章進行介紹。歡迎大家繼續閱讀,謝謝大家!

返回目錄