ioc的核心是將對(duì)象創(chuàng)建和依賴管理交給外部容器,di通過構(gòu)造器、setter或接口注入實(shí)現(xiàn)。spring實(shí)現(xiàn)di需配置容器并定義bean,使用@autowired進(jìn)行注入,可通過構(gòu)造器(推薦)、setter(可選)或字段(不推薦)完成。Java配置用@configuration和@bean定義bean。啟動(dòng)流程包括定位資源、加載解析為beandefinition、注冊(cè)、實(shí)例化、注入、初始化至就緒狀態(tài)。循環(huán)依賴通過三級(jí)緩存解決:一級(jí)存完整bean,二級(jí)存早期bean,三級(jí)存objectfactory,僅支持單例bean。構(gòu)造器注入保證必需依賴,setter用于可選依賴,字段注入破壞封裝應(yīng)避免。
IoC(控制反轉(zhuǎn))的核心在于將對(duì)象的創(chuàng)建和依賴關(guān)系的管理權(quán)從對(duì)象自身轉(zhuǎn)移到外部容器。依賴注入(DI)則是實(shí)現(xiàn)IoC的一種常用方式,它通過構(gòu)造器注入、Setter方法注入或接口注入,將依賴對(duì)象“注入”到目標(biāo)對(duì)象中,而非由目標(biāo)對(duì)象主動(dòng)創(chuàng)建。簡單來說,就是讓容器幫你new對(duì)象,并把對(duì)象需要的“零件”也給你裝好。
解決方案
Java中實(shí)現(xiàn)IoC/DI,通常會(huì)借助第三方框架,例如Spring、Guice、Dagger等。這里以Spring為例,講解如何實(shí)現(xiàn)依賴注入:
立即學(xué)習(xí)“Java免費(fèi)學(xué)習(xí)筆記(深入)”;
-
使用spring容器: 首先,你需要配置Spring容器。這通常通過xml配置、注解配置或者Java配置來實(shí)現(xiàn)。
-
定義Bean: 你需要定義哪些類由Spring容器來管理,這些類被稱為Bean。可以使用@Component、@Service、@Repository、@Controller等注解來標(biāo)記Bean,或者在XML配置中聲明Bean。
-
依賴注入: Spring提供了三種主要的依賴注入方式:
-
構(gòu)造器注入: 通過類的構(gòu)造器來注入依賴。使用@Autowired注解在構(gòu)造器上,Spring會(huì)自動(dòng)找到匹配的Bean并注入。
@Component public class MyService { private final MyRepository myRepository; @Autowired public MyService(MyRepository myRepository) { this.myRepository = myRepository; } // ... }
-
Setter方法注入: 通過Setter方法來注入依賴。使用@Autowired注解在Setter方法上。
@Component public class MyService { private MyRepository myRepository; @Autowired public void setMyRepository(MyRepository myRepository) { this.myRepository = myRepository; } // ... }
-
字段注入: 直接在字段上使用@Autowired注解。雖然方便,但不太推薦,因?yàn)樗茐牧祟惖?a >封裝性,并且在單元測試時(shí)可能會(huì)遇到問題。
@Component public class MyService { @Autowired private MyRepository myRepository; // ... }
-
-
使用@Qualifier解決歧義: 如果存在多個(gè)相同類型的Bean,Spring不知道應(yīng)該注入哪個(gè),這時(shí)可以使用@Qualifier注解來指定具體的Bean名稱。
```java @Component public class MyService { private final MyRepository myRepository; @Autowired public MyService(@Qualifier("myRepositoryImpl1") MyRepository myRepository) { this.myRepository = myRepository; } // ... } ```
-
Java配置: 除了XML和注解,還可以使用Java配置來定義Bean和依賴關(guān)系。使用@Configuration注解標(biāo)記配置類,使用@Bean注解標(biāo)記Bean的創(chuàng)建方法。
```java @Configuration public class AppConfig { @Bean public MyRepository myRepository() { return new MyRepositoryImpl(); } @Bean public MyService myService(MyRepository myRepository) { return new MyService(myRepository); } } ```
Spring IoC容器啟動(dòng)流程是怎樣的?
Spring IoC容器的啟動(dòng)流程可以大致分為以下幾個(gè)步驟:
- 定位資源: 容器首先需要定位到Bean定義的資源,例如XML配置文件、注解標(biāo)記的類等。
- 加載資源: 容器加載這些資源,并將Bean定義解析成Spring內(nèi)部的BeanDefinition對(duì)象。
- 注冊(cè)BeanDefinition: 容器將BeanDefinition注冊(cè)到BeanDefinitionRegistry中,這是一個(gè)Bean定義的注冊(cè)中心。
- 實(shí)例化Bean: 容器根據(jù)BeanDefinition創(chuàng)建Bean實(shí)例。這通常發(fā)生在第一次請(qǐng)求Bean時(shí),也可以配置成在容器啟動(dòng)時(shí)就預(yù)先實(shí)例化。
- 依賴注入: 容器將Bean所需的依賴注入到Bean實(shí)例中。
- 初始化Bean: 容器調(diào)用Bean的初始化方法(如果配置了),例如實(shí)現(xiàn)了InitializingBean接口的afterPropertiesSet()方法,或者使用@PostConstruct注解標(biāo)記的方法。
- Bean準(zhǔn)備就緒: Bean實(shí)例創(chuàng)建完成,可以被應(yīng)用程序使用了。
構(gòu)造器注入、Setter注入和字段注入,應(yīng)該選擇哪種?
這三種注入方式各有優(yōu)缺點(diǎn):
- 構(gòu)造器注入: 強(qiáng)制依賴,保證對(duì)象創(chuàng)建時(shí)所有必需的依賴都已就緒。有利于對(duì)象的不可變性。推薦使用。
- Setter注入: 允許可選依賴,可以在對(duì)象創(chuàng)建后動(dòng)態(tài)設(shè)置依賴。但容易出現(xiàn)依賴未設(shè)置的情況。
- 字段注入: 最簡單,但破壞了封裝性,不利于單元測試。不推薦使用。
總體來說,構(gòu)造器注入是最佳選擇,因?yàn)樗軌虮WC對(duì)象的完整性和不可變性。如果存在可選依賴,可以考慮Setter注入。盡量避免字段注入。
循環(huán)依賴問題如何解決?
循環(huán)依賴是指兩個(gè)或多個(gè)Bean之間相互依賴,形成一個(gè)循環(huán)引用。例如,A依賴B,B依賴C,C又依賴A。Spring IoC容器可以通過以下方式解決循環(huán)依賴問題:
- 提前暴露Bean: Spring在創(chuàng)建Bean時(shí),會(huì)提前將Bean的ObjectFactory暴露出來,以便其他Bean可以引用。這樣,即使Bean還沒有完全創(chuàng)建完成,也可以被其他Bean引用。
- 三級(jí)緩存: Spring使用三級(jí)緩存來解決循環(huán)依賴問題。
- 一級(jí)緩存 (singletonObjects): 存放完全初始化好的Bean。
- 二級(jí)緩存 (earlySingletonObjects): 存放提前暴露的Bean,這些Bean可能還沒有完全初始化完成。
- 三級(jí)緩存 (singletonFactories): 存放Bean的ObjectFactory,用于創(chuàng)建Bean實(shí)例。
當(dāng)出現(xiàn)循環(huán)依賴時(shí),Spring會(huì)首先嘗試從一級(jí)緩存中獲取Bean,如果獲取不到,則嘗試從二級(jí)緩存中獲取,如果還獲取不到,則從三級(jí)緩存中獲取ObjectFactory,并使用ObjectFactory創(chuàng)建Bean實(shí)例。創(chuàng)建Bean實(shí)例后,將Bean放入二級(jí)緩存,并移除三級(jí)緩存中的ObjectFactory。當(dāng)Bean完全初始化完成后,將Bean從二級(jí)緩存移動(dòng)到一級(jí)緩存。
需要注意的是,Spring只能解決單例Bean的循環(huán)依賴問題,對(duì)于原型Bean,由于每次獲取Bean都是一個(gè)新的實(shí)例,因此無法解決循環(huán)依賴問題。