本節書摘來自異步社群《spring實戰(第4版)》一書中的第2章,第2.3節,作者: 【美】craig walls(沃爾斯)著,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視
盡管在很多場景下通過元件掃描和自動裝配實作spring的自動化配置是更為推薦的方式,但有時候自動化配置的方案行不通,是以需要明确配置spring。比如說,你想要将第三方庫中的元件裝配到你的應用中,在這種情況下,是沒有辦法在它的類上添加@component和@autowired注解的,是以就不能使用自動化裝配的方案了。
在這種情況下,你必須要采用顯式裝配的方式。在進行顯式配置的時候,有兩種可選方案:java和xml。在這節中,我們将會學習如何使用java配置,接下來的一節中将會繼續學習spring的xml配置。
就像我之前所說的,在進行顯式配置時,javaconfig是更好的方案,因為它更為強大、類型安全并且對重構友好。因為它就是java代碼,就像應用程式中的其他java代碼一樣。
同時,javaconfig與其他的java代碼又有所差別,在概念上,它與應用程式中的業務邏輯和領域代碼是不同的。盡管它與其他的元件一樣都使用相同的語言進行表述,但javaconfig是配置代碼。這意味着它不應該包含任何業務邏輯,javaconfig也不應該侵入到業務邏輯代碼之中。盡管不是必須的,但通常會将javaconfig放到單獨的包中,使它與其他的應用程式邏輯分離開來,這樣對于它的意圖就不會産生困惑了。
接下來,讓我們看一下如何通過javaconfig顯式配置spring。
2.3.1 建立配置類
在本章前面的程式清單2.3中,我們第一次見識到javaconfig。讓我們重溫一下那個樣例中的cdplayerconfig:

建立javaconfig類的關鍵在于為其添加@configuration注解,@configuration注解表明這個類是一個配置類,該類應該包含在spring應用上下文中如何建立bean的細節。
到此為止,我們都是依賴元件掃描來發現spring應該建立的bean。盡管我們可以同時使用元件掃描和顯式配置,但是在本節中,我們更加關注于顯式配置,是以我将cdplayerconfig的@componentscan注解移除掉了。
移除了@componentscan注解,此時的cdplayerconfig類就沒有任何作用了。如果你現在運作cdplayertest的話,測試會失敗,并且會出現beancreation- exception異常。測試期望被注入cdplayer和compactdisc,但是這些bean根本就沒有建立,因為元件掃描不會發現它們。
為了再次讓測試通過,你可以将@componentscan注解添加回去,但是我們這一節關注顯式配置,是以讓我們看一下如何使用javaconfig裝配cdplayer和compactdisc。
2.3.2 聲明簡單的bean
要在javaconfig中聲明bean,我們需要編寫一個方法,這個方法會建立所需類型的執行個體,然後給這個方法添加@bean注解。比方說,下面的代碼聲明了compactdisc bean:
@bean注解會告訴spring這個方法将會傳回一個對象,該對象要注冊為spring應用上下文中的bean。方法體中包含了最終産生bean執行個體的邏輯。
預設情況下,bean的id與帶有@bean注解的方法名是一樣的。在本例中,bean的名字将會是sgtpeppers。如果你想為其設定成一個不同的名字的話,那麼可以重命名該方法,也可以通過name屬性指定一個不同的名字:
不管你采用什麼方法來為bean命名,bean聲明都是非常簡單的。方法體傳回了一個新的sgtpeppers執行個體。這裡是使用java來進行描述的,是以我們可以發揮java提供的所有功能,隻要最終生成一個compactdisc執行個體即可。
請稍微發揮一下你的想象力,我們可能希望做一點稍微瘋狂的事情,比如說,在一組cd中随機選擇一個compactdisc來播放:
現在,你可以自己想象一下,借助@bean注解方法的形式,我們該如何發揮出java的全部威力來産生bean。當你想完之後,我們要回過頭來看一下在javaconfig中,如何将compactdisc注入到cdplayer之中。
2.3.3 借助javaconfig實作注入
我們前面所聲明的compactdisc bean是非常簡單的,它自身沒有其他的依賴。但現在,我們需要聲明cdplayerbean,它依賴于compactdisc。在javaconfig中,要如何将它們裝配在一起呢?
在javaconfig中裝配bean的最簡單方式就是引用建立bean的方法。例如,下面就是一種聲明cdplayer的可行方案:
cdplayer()方法像sgtpeppers()方法一樣,同樣使用了@bean注解,這表明這個方法會建立一個bean執行個體并将其注冊到spring應用上下文中。所建立的bean id為cdplayer,與方法的名字相同。
cdplayer()的方法體與sgtpeppers()稍微有些差別。在這裡并沒有使用預設的構造器建構執行個體,而是調用了需要傳入compactdisc對象的構造器來建立cdplayer執行個體。
看起來,compactdisc是通過調用sgtpeppers()得到的,但情況并非完全如此。因為sgtpeppers()方法上添加了@bean注解,spring将會攔截所有對它的調用,并確定直接傳回該方法所建立的bean,而不是每次都對其進行實際的調用。
比如說,假設你引入了一個其他的cdplayerbean,它和之前的那個bean完全一樣:
假如對sgtpeppers()的調用就像其他的java方法調用一樣的話,那麼每個cdplayer執行個體都會有一個自己特有的sgtpeppers執行個體。如果我們讨論的是實際的cd播放器和cdCD光牒的話,這麼做是有意義的。如果你有兩台cd播放器,在實體上并沒有辦法将同一張cdCD光牒放到兩個cd播放器中。
但是,在軟體領域中,我們完全可以将同一個sgtpeppers執行個體注入到任意數量的其他bean之中。預設情況下,spring中的bean都是單例的,我們并沒有必要為第二個cdplayer bean建立完全相同的sgtpeppers執行個體。是以,spring會攔截對sgtpeppers()的調用并確定傳回的是spring所建立的bean,也就是spring本身在調用sgtpeppers()時所建立的compactdiscbean。是以,兩個cdplayer bean會得到相同的sgtpeppers執行個體。
可以看到,通過調用方法來引用bean的方式有點令人困惑。其實還有一種了解起來更為簡單的方式:
在這裡,cdplayer()方法請求一個compactdisc作為參數。當spring調用cdplayer()建立cdplayerbean的時候,它會自動裝配一個compactdisc到配置方法之中。然後,方法體就可以按照合适的方式來使用它。借助這種技術,cdplayer()方法也能夠将compactdisc注入到cdplayer的構造器中,而且不用明确引用compactdisc的@bean方法。
通過這種方式引用其他的bean通常是最佳的選擇,因為它不會要求将compactdisc聲明到同一個配置類之中。在這裡甚至沒有要求compactdisc必須要在javaconfig中聲明,實際上它可以通過元件掃描功能自動發現或者通過xml來進行配置。你可以将配置分散到多個配置類、xml檔案以及自動掃描和裝配bean之中,隻要功能完整健全即可。不管compactdisc是采用什麼方式建立出來的,spring都會将其傳入到配置方法中,并用來建立cdplayer bean。
另外,需要提醒的是,我們在這裡使用cdplayer的構造器實作了di功能,但是我們完全可以采用其他風格的di配置。比如說,如果你想通過setter方法注入compactdisc的話,那麼代碼看起來應該是這樣的:
再次強調一遍,帶有@bean注解的方法可以采用任何必要的java功能來産生bean執行個體。構造器和setter方法隻是@bean方法的兩個簡單樣例。這裡所存在的可能性僅僅受到java語言的限制。