在官網中,咱們發現它的核心技術之一:Dependency Injection
,簡稱:DI
,翻譯過來就是依賴注入。今天咱們就來盤一盤它。java
在本文中,咱們將深刻研究 Spring
框架 DI
背後的故事,包括 Spring Inversion of Control
(控制反轉)、 DI
和 ApplicationContext
接口。 基於這些基本概念,咱們將研究如何使用基於 java
和基於 XML
的配置來 建立Spring
應用程序。 最後,咱們將探討在建立 Spring
應用程序時遇到的一些常見問題,包括 bean衝突和循環依賴性。算法
在學習DI
以前,咱們先學習一下 IoC
(控制反轉),接下來的一段可能讀起來會讓你感受比較囉嗦,可是要細細體會每一次改變的意圖,和咱們的解決方案,對於理解控制反轉很是重要。spring
首先來了解下咱們一般實例化一個對象的方式。 在 平時,咱們使用 new
關鍵字實例化一個對象。 例如,若是有一個 Car
類,咱們可使用如下方法實例化一個對象 Car
express
Car car = new Car();
複製代碼
由於汽車有不少零部件組成,咱們定義Engine
接口來模擬汽車引擎,而後將engine
對象做爲成員變量放在Car
類markdown
public interface Engine {
void turnOn();
}
public class Car {
private Engine engine;
public Car() {}
public void start() {
engine.turnOn();
}
}
複製代碼
如今,咱們能夠調用start()方法嗎?顯然是不行的,一眼能夠看出會報NullPointerException (NPE)
,由於咱們沒有在Car
的構造函數中初始化engine
。一般咱們採用的方案就是在Car的構造函數中以爲使用Engine
接口的哪一個實現,並直接將該實現分配給engine
字段;框架
如今,咱們來首先建立Engine
接口的實現類dom
public class ElectricEngine implements Engine {
@Override
public void turnOn() {
System.out.println("電動引擎啓動");
}
}
public class CombustionEngine implements Engine {
@Override
public void turnOn() {
System.out.println("燃油引擎啓動");
}
}
複製代碼
咱們修改Car的構造函數,使用ElectricEngine
實現,將咱們的engine
字段分配給一個實例化的ElectricEngine
對象ide
public class Car {
private Engine engine;
public Car() {
this.engine = new ElectricEngine();
}
public void start() {
engine.turnOn();
}
public static void main(String[] args) {
Car car = new Car();
car.start();
}
}
複製代碼
如今咱們執行start()
方法,咱們會看到以下輸出:函數
大功告成,咱們成功解決了 NPE
(空指針)問題,可是咱們勝利了嗎?哈哈哈,顯然沒有!學習
在解決問題的同時,咱們又引入了另外一個問題。儘管咱們經過抽象Engine
接口,而後經過不一樣的Engine
實現類來負責不一樣類型引擎的業務邏輯,的確是很好的設計策略。可是細心的夥伴可能已經發現了,咱們Car
類的構造函數中將engine
聲明爲CombustionEngine
,這將致使全部車都有一個燃油引擎。假如咱們如今要建立不一樣的汽車對象,它有一個電動引擎,咱們將不得不改變咱們的設計。比較常見的方法是建立兩個獨立裏的類,各司其職,在他們的構造函數中將engine
分配給Engine
接口的不一樣實現;
例如:
public class CombustionCar {
private Engine engine;
public CombustionCar() {
this.engine = new CombustionEngine();
}
public void start() {
engine.turnOn();
}
}
public class ElectricCar {
private Engine engine;
public ElectricCar() {
this.engine = new ElectricEngine();
}
public void start() {
engine.turnOn();
}
}
複製代碼
經過上面的一頓騷操做,咱們成功的解決了咱們引擎的問題。若是是一個平常需求,咱們已經能夠成功交工了。可是這顯然不是我寫這篇文章的目的。
從設計的角度來講,目前的代碼是糟糕的,有如下兩點緣由:
start()
方法;Engine
實現類建立一個新的類;尤爲後一個問題更加難以解決,由於咱們不控制Engine
的實現,隨着開發人員不斷的建立本身的實現類,這個問題會更加惡化;
帶着上面的問題,咱們繼續思考.....................
咱們能夠建立一個父類Car
,將公共代碼抽取到父類中,能夠輕鬆解決第一個問題。因爲Engine
字段是私有的,咱們在父類Car
的構造函數中接收Engine
對象,而且進行賦值。
public class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.turnOn();
}
}
public class CombustionCar extends Car{
public CombustionCar() {
super(new CombustionEngine());
}
}
public class ElectricCar extends Car {
public ElectricCar() {
super(new ElectricEngine());
}
}
複製代碼
經過這種方法,咱們成功的解決了代碼重複的問題,咱們來測試一下:
public class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.turnOn();
}
public static void main(String[] args) {
CombustionCar combustionCar1 = new CombustionCar();
combustionCar1.start();
ElectricCar electricCar1 = new ElectricCar();
electricCar1.start();
}
}
複製代碼
那麼咱們該如何解決咱們提出的第二個問題那?
其實這個問題咱們能夠換個角度看:爲何咱們要去關注CombustionCar
和ElectricCar
,咱們如今將關注點回到咱們的Car
,咱們如今已經容許客戶端實例化Car
對象時候將Engine
對象做爲構造函數的參數傳入,其實已經消除了爲每一個Engine
對象建立新Car
的問題。由於如今Car
類依賴於Engine
接口,並不知道任何Engine
的實現;
經過帶有Engine
參數的構造函數,咱們已將要使用哪一個Engine
實現的決定從Car
類自己(最初由CombustionEngine
決定)更改成實例化Car
類的客戶端。 決策過程的這種逆轉稱爲IoC原則
。 如今,由客戶端控制使用哪一種實現,而不是由Car
類自己控制使用哪一種Engine
實現。
有點繞,你們結合下面的示例代碼,細細琢磨
public class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.turnOn();
}
public static void main(String[] args) {
/** * 老法子 * 爲每一類型發送機的車建立類,而後實現父類car,而後在構造函數傳入本身的引擎,而後調用start() */
CombustionCar combustionCar1 = new CombustionCar();
combustionCar1.start();
ElectricCar electricCar1 = new ElectricCar();
electricCar1.start();
/** * 控制反轉思想 * 把本身看做實例化car的客戶端,須要什麼引擎,直接傳入相關對象 */
CombustionEngine combustionEngine = new CombustionEngine();
Car combustionCar = new Car(combustionEngine);
combustionCar.start();
ElectricEngine electricEngine = new ElectricEngine();
Car electricCar = new Car(electricEngine);
electricCar.start();
}
}
複製代碼
執行上面的代碼,咱們發現均可以得到咱們想要的結果:
從上面的例子咱們能夠看到,實例化Car
類的客戶端能夠控制所使用的Engine
實現,而且取決於將哪一個Engine
實現傳遞給Car
構造函數,Car
對象的行爲發生巨大變化。爲何這麼說,接着看下面
在上面控制反轉的知識點,咱們已經解決了由誰決定使用哪一種Engine
實現的問題,可是不可避免,咱們也更改了實例化一個Car
對象的步驟;
最開始,咱們實例化Car
不須要參數,由於在它的構造函數裏面已經爲咱們new
了Engine
對象。使用IoC
方法以後,咱們要求在實例化一個Car
以前,咱們須要先建立一個Engine
對象,並做爲參數傳遞給Car
構造對象。換句話說,最初,咱們首先實例化Car
對象,而後實例化Engine
對象。可是,使用IoC
以後,咱們首先實例化Engine
對象,而後實例化Car
對象;
所以,咱們在上面的過程當中建立了一個依賴關係。不過這種依賴關係不是指編譯時候Car
類對Engine
接口的依賴關係,相反,咱們引入了一個運行時依賴關係。在運行時,實例化Car
對象以前,必須首先實例化Engine
對象。
某一個具體的依賴對象你們能夠理解爲Spring中的bean,對於兩個有依賴關係的bean,其中被依賴的那個bean,咱們把它稱爲依賴對象
咱們用圖形化的方式來看看它們之間的依賴關係,其中圖形的節點表明對象,箭頭表明依賴關係(箭頭指向依賴對象)。對於咱們個人Car
類,依賴關係樹很是簡單:
若是依賴關係樹的終端結點還有本身的附加依賴關係,那麼這個依賴關係樹將變得更加複雜。如今再看咱們上面的例子,若是CombustionEngine
還有其餘依賴對象,咱們首先須要建立CombustionEngine
的依賴對象,而後才能實例化一個CombustionEngine
對象。這樣在建立Car
對象時候,才能將CombustionEngine
傳遞給Car
的構造函數;
//凸輪軸
public class Camshaft {}
//機軸
public class Crankshaft {}
public class CombustionEngine implements Engine {
//凸輪軸
private Camshaft camshaft;
//機軸
private Crankshaft crankshaft;
public CombustionEngine(Camshaft camshaft, Crankshaft crankshaft) {
this.camshaft = camshaft;
this.crankshaft = crankshaft;
}
@Override
public void turnOn() {
System.out.println("燃油引擎啓動");
}
}
複製代碼
通過咱們改造,咱們如今的依賴關係樹變爲下面的樣子
隨着咱們不斷引入更多的依賴關係,這種複雜性將繼續增加。爲了解決這個複雜問題,咱們須要基於依賴關係樹抽取對象的建立過程。這就是依賴注入框架。
通常來講,咱們能夠把這個過程分爲三個部分:
經過反射,咱們能夠查看 Car
類的構造函數,而且知道它須要一個 Engine
參數。所以爲了建立Car對象,咱們必須建立至少一個Engine
接口的實現類用做依賴項來使用。在這裏,咱們建立一個CombustionEngine
對象(爲了方便,暫時當作只有一個實現類,bean衝突問題待會再說)來聲明它做爲依賴項來使用,就知足Car
對象建立時的需求.
其實,這個過程是遞歸的,由於CombustionEngine
依賴於其餘對象,咱們須要不斷重複第一個過程,直到把全部依賴對象聲明完畢,而後註冊建立這些依賴對象所須要的類。
第三點其實就是將前面兩點思想付諸實施,從而造成一種建立對象的機制
舉個例子:好比咱們須要一個Car
對象,咱們必須遍歷依賴關係樹並檢查是否存在至少一個符合條件的類來知足全部依賴關係。 例如,聲明CombustionEngine
類可知足Engine
節點要求。 若是存在這種依賴關係,咱們將實例化該依賴關係,而後移至下一個節點。
若是有一個以上的類知足所需的依賴關係,那麼咱們必須顯式聲明應該選擇哪種依賴關係。 稍後咱們將討論 Spring 是如何作到這一點的。
一旦咱們肯定全部的依賴關係都準備好了,咱們就能夠從終端節點開始建立依賴對象。 對於 Car
對象,咱們首先實例化 Camshaft
和Crankshaft
ーー由於這些對象沒有依賴關係ーー而後將這些對象傳遞給 CombustionEngine
構造函數,以實例化 CombunstionEngine
對象。 最後,咱們將 CombunstionEngine
對象傳遞給 Car
構造函數,以實例化所需的 Car
對象。
瞭解了 DI
的基本原理以後,咱們如今能夠繼續討論 Spring
如何執行 DI
。
Spring
的核心是一個DI
框架,它能夠將DI
配置轉換爲Java
應用程序。
在這裏咱們要闡述一個問題:那就是庫和框架的區別。庫只是類定義的集合。背後的緣由僅僅是代碼重用,即獲取其餘開發人員已經編寫的代碼。這些類和方法一般在域特定區域中定義特定操做。例如,有一些數學庫可以讓開發人員僅調用函數而無需重作算法工做原理的實現。
框架一般被認爲是一個骨架,咱們在其中插入代碼以建立應用程序。 許多框架保留了特定於應用程序的部分,並要求咱們開發人員提供適合框架的代碼。 在實踐中,這意味着編寫接口的實現,而後在框架中註冊實現。
在 Spring
中,框架圍繞 ApplicationContext
接口實現上一節中概述的三個 DI
職責。一般這個接口表明了一個上下文。 所以,咱們經過基於 java
或基於 xml
的配置向 ApplicationContext
註冊合適的類,並從 ApplicationContext
請求建立 bean
對象。 而後 ApplicationContext
構建一個依賴關係樹並遍歷它以建立所需的 bean
對象
Applicationcontext
中包含的邏輯一般被稱爲 Spring
容器。 一般,一個 Spring
應用程序能夠有多個 ApplicationContext
,每一個 ApplicationContext
能夠有單獨的配置。 例如,一個 ApplicationContext
可能被配置爲使用 CombustionEngine
做爲其引擎實現,而另外一個容器可能被配置爲使用 ElectricEngine
做爲其實現。
在本文中,咱們將重點討論每一個應用程序的單個 ApplicationContext
,可是下面描述的概念即便在一個應用程序有多個 ApplicationContext
實例時也適用。
Spring爲咱們提供了兩種基於 java
的配置方式
基於java
的基本配置的核心,實際上是下面兩個註解:
@Configuration
: 定義配置類@Bean
: 建立一個bean
例如,給出咱們以前定義的 Car
, CombustionEngine
, Camshaft
, 和Crankshaft
類,咱們能夠建立一個下面 的配置類:
/** * @author milogenius * @date 2020/5/17 20:52 */
@Configuration
public class AnnotationConfig {
@Bean
public Car car(Engine engine) {
return new Car(engine);
}
@Bean
public Engine engine(Camshaft camshaft, Crankshaft crankshaft) {
return new CombustionEngine(camshaft, crankshaft);
}
@Bean
public Camshaft camshaft() {
return new Camshaft();
}
@Bean
public Crankshaft crankshaft() {
return new Crankshaft();
}
}
複製代碼
接下來,咱們建立一個 ApplicationContext
對象,從 ApplicationContext
對象獲取一個 Car
對象,而後在建立的 Car
對象上調用 start
方法:
ApplicationContext context =
new AnnotationConfigApplicationContext(AnnotationConfig.class);
Car car = context.getBean(Car.class);
car.start();
複製代碼
執行結果以下:
Started combustion engine
複製代碼
雖然@Configuration
和@Bean
註解的組合爲 Spring
提供了足夠的信息來執行依賴注入,但咱們仍然須要手動手動定義每一個將被注入的 bean
,並顯式地聲明它們的依賴關係。 爲了減小配置 DI
框架所需的開銷,Spring 提供了基於java
的自動配置。
爲了支持基於 java
的自動配置,Spring
提供了額外的註解。 雖然咱們平時可能加過不少這種類型的註解,可是有三個最基本的註解:
@Component
: 註冊爲由 Spring 管理的類@Autowired
: 指示 Spring 注入一個依賴對象@ComponentScan
: 指示Spring在何處查找帶有@Component
註解的類@Autowired
註解用來指導 Spring
,咱們打算在使用註解的位置注入一個依賴對象。 例如,在 Car
構造函數中,咱們指望注入一個 Engine
對象,所以,咱們給 Car
構造函數添加@Autowired
註解。 經過使用@Component
和@Autowired
註解改造咱們Car
類,以下所示:
@Component
public class Car {
private Engine engine;
@Autowired
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.turnOn();
}
}
複製代碼
咱們能夠在其餘類中重複這個過程:
@Component
public class Camshaft {}
@Component
public class Crankshaft {}
@Component
public class CombustionEngine implements Engine {
private Camshaft camshaft;
private Crankshaft crankshaft;
@Autowired
public CombustionEngine(Camshaft camshaft, Crankshaft crankshaft) {
this.camshaft = camshaft;
this.crankshaft = crankshaft;
}
@Override
public void turnOn() {
System.out.println("Started combustion engine");
}
}
複製代碼
改造完成相關類以後,咱們須要建立一個@Configuration
類來指導 Spring
如何自動配置咱們的應用程序。 對於基於 java
的基本配置,咱們明確指示 Spring
如何使用@Bean
註解建立每一個 bean,但在自動配置中,咱們已經經過@Component
和@Autowired
註解提供了足夠的信息,說明如何建立所需的全部 bean。 惟一缺乏的信息是 Spring
應該在哪裏尋找咱們的帶有@Component
註解的 類,並把它註冊爲對應的bean。
@ Componentscan
註釋包含一個參數 basePackages
,它容許咱們將包名稱指定爲一個 String
,Spring
將經過遞歸搜索來查找@Component
類。 在咱們的示例中,包是 com.milo.domain
,所以,咱們獲得的配置類是:
@Configuration
@ComponentScan(basePackages = "com.milo.domain")
public class AutomatedAnnotationConfig {}
複製代碼
ApplicationContext context =
new AnnotationConfigApplicationContext(AutomatedAnnotationConfig.class);
Car car = context.getBean(Car.class);
car.start();
複製代碼
執行結果:
Started combustion engine
複製代碼
經過和基於java
的基礎配置比較,咱們發現基於 java
的自動配置方法有兩個主要優勢:
因此無特殊狀況,自動配置是首選
除了構造函數注入,咱們還能夠經過字段直接注入。 咱們能夠將@Autowired
註解應用到所需的字段來實現這一點:
@Component
public class Car {
@Autowired
private Engine engine;
public void start() {
engine.turnOn();
}
}
複製代碼
這種方法極大地減小了咱們的編碼壓力,可是它也有一個缺點,就是在使用字段以前,咱們將沒法檢查自動注入的對象是否爲空。
構造函數注入的最後一種替代方法是 setter 注入,其中@Autowired
註解應用於與字段關聯的 setter。 例如,咱們能夠改變 Car
類,經過 setter 注入得到 Engine
對象,方法是用@Autowired
註解 setEngine
方法:
@Component
public class Car {
private Engine engine;
public void start() {
engine.turnOn();
}
public Engine getEngine() {
return engine;
}
@Autowired
public void setEngine(Engine engine) {
this.engine = engine;
}
}
複製代碼
Setter 注入相似於字段注入,但它容許咱們與 注入對象交互。 在有些狀況下,setter 注入可能特別有用,例如具備循環依賴關係,但 setter 注入多是三種注入技術中最不常見的,儘量優先使用構造函數注入。
另外一種配置方法是基於 xml
的配置。 咱們在 XML
配置文件中定義 bean
以及它們之間的關係,而後指示 Spring
在哪裏找到咱們的配置文件。
第一步是定義 bean
。 咱們基本遵循與基於 java
的基本配置相同的步驟,但使用 xmlbean
元素代替。 在 XML
的狀況下,咱們還必須顯式地聲明咱們打算使用 constructor-arg
元素注入到其餘構造函數中的 bean
。 結合 bean
和 constructor-arg
元素,咱們獲得如下 XML
配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="car" class="com.milo.domain.Car">
<constructor-arg ref="engine" />
</bean>
<bean id="engine" class="com.milo.CombustionEngine">
<constructor-arg ref="camshaft" />
<constructor-arg ref="crankshaft" />
</bean>
<bean id="camshaft" class="com.milo.Camshaft" />
<bean id="crankshaft" class="com.milo.Crankshaft" />
</beans>
複製代碼
在 bean 元素中,咱們必須指定兩個屬性:
id
: bean 的惟一 ID (至關於帶有@Bean
註解方法名)class
: 類的全路徑(包括包名)對於 constructor-arg
元素,咱們只須要指定 ref
屬性,它是對現有 bean ID
的引用。 例如,元素構造函數 <constructor-arg ref="engine" />
規定,具備 ID engine
(直接定義在 car
bean 之下)的 bean 應該被用做注入 car
bean 構造函數的 bean。
構造函數參數的順序由 constructor-arg
元素的順序決定。 例如,在定義 engine
bean 時,傳遞給 CombustionEngine
構造函數的第一個構造函數參數是 camshaft
bean,而第二個參數是 crankshaft
bean。
獲取ApplicationContext
對象,咱們只需修改 ApplicationContext
實現類型。 由於咱們將 XML
配置文件放在類路徑上,因此咱們使用 ClassPathXmlApplicationContext
:
ApplicationContext context =
new ClassPathXmlApplicationContext("basic-config.xml");
Car car = context.getBean(Car.class);
car.start();
複製代碼
執行結果:
Started combustion engine
複製代碼
如今,咱們已經摸清了Spring框架如何進行DI
,並正確地將全部依賴關係注入到咱們的應用程序中,可是咱們必須處理兩個棘手的問題:
在基於 java
和基於 xml
的方法中,咱們已經指示 Spring
只使用 CombustionEngine
做爲咱們的Engine
實現。 若是咱們將ElectricEngine
註冊爲符合 di
標準的部件會發生什麼? 爲了測試結果,咱們將修改基於 java
的自動配置示例,並用@Component
註解 ElectricEngine
類:
@Component
public class ElectricEngine implements Engine {
@Override
public void turnOn() {
System.out.println("Started electric engine");
}
}
複製代碼
若是咱們從新運行基於 java 的自動配置應用程序,咱們會看到如下錯誤:
No qualifying bean of type 'com.dzone.albanoj2.spring.di.domain.Engine' available: expected single matching bean but found 2: combustionEngine,electricEngine
複製代碼
因爲咱們已經註釋了用@Component
實現 Engine
接口的兩個類ーー即 CombustionEngine
和ElectricEngine
ーー spring
如今沒法肯定在實例化 Car
對象時應該使用這兩個類中的哪個來知足 Engine
依賴性。 爲了解決這個問題,咱們必須明確地指示 Spring
使用這兩個 bean
中的哪個。
一種方法是給咱們的依賴對象命名,並在應用@Autowired
註解的地方使用@Qualifier
註解來肯定注入哪個依賴對象。 因此,@Qualifier
註解限定了自動注入的 bean,從而將知足需求的 bean 數量減小到一個。 例如,咱們能夠命名咱們的CombustionEngine
依賴對象:
@Component("defaultEngine")
public class CombustionEngine implements Engine {
// ...代碼省略,未改變
}
複製代碼
而後咱們能夠添加@Qualifier
註解,其名稱和咱們想要注入的依賴對象的名稱保持一致,這樣,咱們Engine
對象在 Car
構造函數中被自動注入
@Component
public class Car {
@Autowired
public Car(@Qualifier("defaultEngine") Engine engine) {
this.engine = engine;
}
// ...existing implementation unchanged...
}
複製代碼
若是咱們從新運行咱們的應用程序,咱們再也不報之前的錯誤:
Started combustion engine
複製代碼
注意,若是沒有顯式申明bean名稱的類都有一個默認名稱,該默認名稱就是類名首字母小寫。 例如,咱們的 Combusttionengine
類的默認名稱是 combusttionengine
若是咱們知道默認狀況下咱們更喜歡一個實現,那麼咱們能夠放棄@Qualifier
註釋,直接將@Primary
註釋添加到類中。 例如,咱們能夠將咱們的 Combusttionengine
、 ElectricEngine
和 Car
類更改成:
@Component
@Primary
public class CombustionEngine implements Engine {
// ...existing implementation unchanged...
}
@Component
public class ElectricEngine implements Engine {
// ...existing implementation unchanged...
}
@Component
public class Car {
@Autowired
public Car(Engine engine) {
this.engine = engine;
}
// ...existing implementation unchanged...
}
複製代碼
咱們從新運行咱們的應用程序,咱們會獲得如下輸出:
Started combustion engine
複製代碼
這證實,雖然有兩種可能性知足 Engine
依賴性,即 CombustionEngine
和 Electricengine
,但 Spring
可以根據@Primary
註釋決定兩種實現中哪種應該優先使用。
雖然咱們已經深刻討論了 Spring DI
的基礎知識,可是還有一個主要問題沒有解決: 若是依賴關係樹有一個循環引用會發生什麼? 例如,假設咱們建立了一個 Foo
類,它的構造函數須要一個 Bar
對象,可是 Bar
構造函數須要一個 Foo
對象。
咱們可使用代碼實現上面問題:
@Component
public class Foo {
private Bar bar;
@Autowired
public Foo(Bar bar) {
this.bar = bar;
}
}
@Component
public class Bar {
private Foo foo;
@Autowired
public Bar(Foo foo) {
this.foo = foo;
}
}
複製代碼
而後咱們能夠定義如下配置:
@Configuration
@ComponentScan(basePackageClasses = Foo.class)
public class Config {}
複製代碼
最後,咱們能夠建立咱們的 ApplicationContext
:
ApplicationContext context =
new AnnotationConfigApplicationContext(Config.class);
Foo foo = context.getBean(Foo.class);
複製代碼
當咱們執行這個代碼片斷時,咱們看到如下錯誤:
Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bar': Requested bean is currently in creation: Is there an unresolvable circular reference?
複製代碼
首先,Spring
嘗試建立 Foo
對象。 在這個過程當中,Spring
認識到須要一個 Bar
對象。 爲了構造 Bar
對象,須要一個 Foo
對象。 因爲 Foo
對象目前正在構建中(這也是建立 Bar 對象的緣由) ,spring
認識到可能發生了循環引用。
這個問題最簡單的解決方案之一是在一個類和注入點上使用@Lazy
註解。 這指示 Spring 推遲帶註解的 bean 和帶註釋的@Autowired
位置的初始化。 這容許成功地初始化其中一個 bean,從而打破循環依賴鏈。 理解了這一點,咱們能夠改變 Foo
和 Bar
類:
@Component
public class Foo {
private Bar bar;
@Autowired
public Foo(@Lazy Bar bar) {
this.bar = bar;
}
}
@Component
@Lazy
public class Bar {
@Autowired
public Bar(Foo foo) {}
}
複製代碼
若是使用@Lazy
註解後從新運行應用程序,沒有發現報告任何錯誤。
在本文中,咱們探討了 Spring
的基礎知識,包括 IoC
、 DI
和 Spring ApplicationContext
。 而後,咱們介紹了使用基於 java
的配置和基於 xml
的配置建立 Spring
應用程序的基本知識,同時研究了使用 Spring DI
時可能遇到的一些常見問題。 雖然這些概念一開始可能晦澀難懂,與 Spring
代碼脫節,可是咱們能夠從基底層認識Spirng
,但願對你們有所幫助,謝謝你們。