Spring?依賴注入和循環(huán)依賴的實(shí)例解析
一.依賴注入的方式
依賴注入(Dependency Injection,簡(jiǎn)稱DI)是一種軟件設(shè)計(jì)模式和編程技術(shù),用于實(shí)現(xiàn)類(lèi)之間的解耦和依賴關(guān)系的管理。它的核心思想是:在對(duì)象創(chuàng)建時(shí),由外部容器負(fù)責(zé)將該對(duì)象所依賴的其他對(duì)象(依賴)傳遞進(jìn)來(lái),而不是由對(duì)象自己去創(chuàng)建或查找這些依賴對(duì)象。
依賴注入的主要目的是降低類(lèi)之間的耦合度,使得代碼更加靈活、可維護(hù)和可測(cè)試。通過(guò)依賴注入,一個(gè)類(lèi)不需要關(guān)心它所依賴的對(duì)象是如何創(chuàng)建和獲取的,而只需定義需要哪些依賴,并由外部容器負(fù)責(zé)提供這些依賴。這樣可以減少代碼中的硬編碼,使得代碼更加模塊化和可復(fù)用。
不同的注入方式各有優(yōu)劣,選擇合適的方式取決于具體的業(yè)務(wù)場(chǎng)景和設(shè)計(jì)要求。構(gòu)造函數(shù)注入通常用于強(qiáng)制的、不可變的依賴關(guān)系,而 setter 方法注入和成員變量注入則更適合可選的、可變的依賴關(guān)系。使用 Spring 時(shí),可以根據(jù)具體情況選擇最合適的依賴注入方式。
依賴注入通常有以下幾種方式:
1. 構(gòu)造函數(shù)注入(Constructor Injection)
通過(guò)構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)對(duì)依賴對(duì)象的注入。使用構(gòu)造函數(shù)注入時(shí),Spring 容器會(huì)在創(chuàng)建對(duì)象時(shí)自動(dòng)解析構(gòu)造函數(shù)的參數(shù),并將相應(yīng)的依賴對(duì)象傳遞給構(gòu)造函數(shù),從而完成對(duì)象的創(chuàng)建和注入。
構(gòu)造函數(shù)注入是 Spring 中常用的一種依賴注入方式,它使得對(duì)象的創(chuàng)建和依賴注入更加清晰和類(lèi)型安全。相比于其他注入方式(如屬性注入或方法注入),構(gòu)造函數(shù)注入更具有優(yōu)勢(shì),因?yàn)樗_保了在對(duì)象創(chuàng)建時(shí)所有必需的依賴都已經(jīng)得到了滿足。
1. 構(gòu)造函數(shù)注入是通過(guò)在類(lèi)的構(gòu)造函數(shù)中傳入依賴對(duì)象來(lái)實(shí)現(xiàn)的。
2. 構(gòu)造函數(shù)注入強(qiáng)制依賴對(duì)象在創(chuàng)建實(shí)例時(shí)必須提供,是類(lèi)的必要組成部分, 確保了類(lèi)在實(shí)例化時(shí)的完整性。
3. 構(gòu)造函數(shù)注入通常用于表示強(qiáng)制的、必須的依賴關(guān)系,提供了更好的不可變性和線程安全性。
4. 使用構(gòu)造函數(shù)注入時(shí),類(lèi)的依賴關(guān)系在實(shí)例化時(shí)就被解決,對(duì)象的狀態(tài)一經(jīng)創(chuàng)建就不能更改(如果既有構(gòu)造方法注入, 又提供了 setter 方法, 這時(shí)依賴對(duì)象是可以改變的, 如果不想被改變, 需要把注入的對(duì)象設(shè)置 final, 這樣就無(wú)法設(shè)置 setter 方法了, 此時(shí)對(duì)象不可變, 指的是不能變成兩個(gè)不同的對(duì)象, 但是就算加了final, 依賴對(duì)象內(nèi)的一些屬性還是可以改變的, 而 setter 注入和成員變量注入則無(wú)法設(shè)置為final)。
1.1 @Configuration 方式實(shí)現(xiàn)
a. 類(lèi) MyService 需要注入 MyDependency 類(lèi), 在構(gòu)造方法中聲明需要依賴的參數(shù)
public class MyService {
private final MyDependency myDependency;
public MyService(MyDependency myDependency) {
this.myDependency = myDependency;
}
public MyDependency getMyDependency() {
return myDependency;
}b. MyService 類(lèi)中需要注入的依賴類(lèi)
public class MyDependency {
public void test(){
System.out.println("測(cè)試");
}
}c. 定義配置類(lèi)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
// 用于創(chuàng)建并配置 MyDependency 的實(shí)例
@Bean
public MyDependency myDependency() {
return new MyDependency();
}
// 用于創(chuàng)建 MyService 的實(shí)例,并通過(guò)構(gòu)造函數(shù)將 MyDependency 的實(shí)例傳遞進(jìn)去
@Bean
public MyService myService(MyDependency myDependency) {
return new MyService(myDependency);
}
}d. 使用 MyService 的 bean
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = context.getBean(MyService.class);
myService.getMyDependency().test();
}執(zhí)行后會(huì)打印 "測(cè)試", 說(shuō)明 MyService 實(shí)例創(chuàng)建成功, 并且成功注入了 MyDependency
1.2 @Autowired 方式實(shí)現(xiàn)
a. 構(gòu)造函數(shù)用 @Autowired 修飾, 且 MyService 使用 @Component 標(biāo)注為 Bean
@Component
public class MyService {
public final MyDependency myDependency;
@Autowired
public MyService(MyDependency myDependency) {
this.myDependency = myDependency;
}
public void doSomething() {
System.out.println(myDependency.getMessage());
}
}b. 所依賴的 MyDependency 類(lèi)同樣標(biāo)注為 Bean
@Component
public class MyDependency {
private String message = "默認(rèn)輸出";
public void setMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}c. 使用 MyService 的 bean
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
MyService myService = context.getBean(MyService.class);
myService.doSomething();
// 從容器中取出 MyDependency, 改變?cè)撘蕾噷?duì)象的message
MyDependency newDependency = context.getBean(MyDependency.class);
newDependency.setMessage("修改之后的輸出");
myService.doSomething();
}
}輸出:
默認(rèn)輸出
修改之后的輸出
說(shuō)明 MyService 實(shí)例創(chuàng)建成功, 并且成功注入了 MyDependency, 并且雖然加了 final, 但是 message 的屬性還是可以發(fā)生變化
d. 如果不將依賴設(shè)為 final, 然后再為 依賴設(shè)置一個(gè) setter 方法的話
@Component
public class MyService {
public MyDependency myDependency;
@Autowired
public MyService(MyDependency myDependency) {
this.myDependency = myDependency;
}
public void setMyDependency(MyDependency myDependency) {
this.myDependency = myDependency;
}
public void doSomething() {
System.out.println(myDependency.getMessage());
}
}e.進(jìn)行一下測(cè)試
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
MyService myService = context.getBean(MyService.class);
myService.doSomething();
// 改變所依賴的對(duì)象
MyDependency newDependency = new MyDependency();
newDependency.setMessage("修改之后的輸出");
// 設(shè)置為新的依賴對(duì)象
myService.setMyDependency(newDependency);
myService.doSomething();
// 容器中的依賴對(duì)象的輸出
MyDependency oldDependency = context.getBean(MyDependency.class);
System.out.println(oldDependency.getMessage());
}
}輸出:
默認(rèn)輸出
修改之后的輸出
默認(rèn)輸出
可見(jiàn) MyDependency 對(duì)象被改變了, 但是 MyDependency 的bean還是不變的, setter 注入和成員變量注入更是如此
1.3 需要注意的地方
如果該類(lèi)的構(gòu)造方法只有一個(gè), 可以省略 @Autowired 注解。如果該類(lèi)中存在多個(gè)構(gòu)造函數(shù), 則需要加上 @Autowired 注解,以明確指明這個(gè)構(gòu)造函數(shù)負(fù)責(zé)依賴的的注入。
如果需要注入的是一個(gè)接口, 并且有多個(gè)實(shí)現(xiàn)類(lèi), 則我們需要 @Qualifier() 注解標(biāo)注我們需要注入的類(lèi)。
a. 需要注入的接口和實(shí)現(xiàn)類(lèi)
public interface IMyDependency {
void test();
}@Component("myDependency")
public class MyDependency implements IMyDependency{
public void test(){
System.out.println("測(cè)試");
}
}@Component("myDependency2")
public class MyDependency2 implements IMyDependency{
public void test(){
System.out.println("測(cè)試2");
}
}b. 注入 IMyDependency 接口, 并且存在多個(gè)構(gòu)造函數(shù)
@Component
public class MyService {
private IMyDependency myDependency;
public MyService() {
}
@Autowired
public MyService(@Qualifier("myDependency2")IMyDependency myDependency) {
this.myDependency = myDependency;
}
public IMyDependency getMyDependency() {
return myDependency;
}
}c.使用 MyService 的 bean
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
MyService myService = context.getBean(MyService.class);
myService.getMyDependency().test();
}執(zhí)行后會(huì)打印 "測(cè)試2", 說(shuō)明 MyService 實(shí)例創(chuàng)建成功, 并且成功注入了 MyDependency2
2. 屬性注入(Setter Injection)
通過(guò)類(lèi)的屬性(setter 方法)來(lái)傳遞依賴對(duì)象。在類(lèi)的屬性上使用注解或配置來(lái)標(biāo)識(shí)依賴關(guān)系,并由外部容器在創(chuàng)建類(lèi)的實(shí)例后,通過(guò) setter 方法將依賴對(duì)象注入。
1. Setter 方法注入是一種非強(qiáng)制性的注入方式,與構(gòu)造函數(shù)注入相對(duì)。
2. 在 Setter 方法注入中,Spring 容器會(huì)在實(shí)例化 Bean 后,通過(guò)調(diào)用對(duì)象的相應(yīng) setter 方法,將依賴的對(duì)象或值注入到對(duì)象中。
3. 通過(guò) Setter 方法注入,可以在對(duì)象實(shí)例化后動(dòng)態(tài)地設(shè)置其依賴屬性。這種方式相對(duì)靈活,可以允許對(duì)象在運(yùn)行時(shí)更改其依賴,但同時(shí)也可能導(dǎo)致一些問(wèn)題,例如在沒(méi)有正確設(shè)置依賴的情況下調(diào)用對(duì)象的方法可能會(huì)引發(fā)空指針異常。因此,在使用 Setter 方法注入時(shí),需要確保對(duì)象的依賴在調(diào)用其方法前已經(jīng)正確設(shè)置。
2.1 @Configuration 方式實(shí)現(xiàn)
a. 類(lèi) MyService 需要注入 MyDependency 類(lèi), 在 setter 方法中聲明需要依賴的參數(shù)
public class MyService {
private MyDependency myDependency;
public void setMyDependency(MyDependency myDependency) {
this.myDependency = myDependency;
}
public void test() {
myDependency.test();
}
}b. MyService 類(lèi)中需要注入的依賴類(lèi)
public class MyDependency {
public void test(){
System.out.println("測(cè)試");
}
}c. 定義配置類(lèi)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
// 定義 MyDependency Bean
@Bean
public MyDependency myDependency() {
return new MyDependency();
}
// 定義 MyService Bean
@Bean
public MyService myService(MyDependency dependency) {
MyService service = new MyService();
service.setMyDependency(dependency);
return service;
}
}d. 使用 MyService 的 bean
public class Application {
public static void main(String[] args) {
// 創(chuàng)建 Spring 容器并加載配置類(lèi)
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 獲取 MyService Bean
MyService myService = context.getBean(MyService.class);
// 調(diào)用 MyService 的方法
myService.test();
// 關(guān)閉容器
context.close();
}
}執(zhí)行后會(huì)打印 "測(cè)試", 說(shuō)明 MyService 實(shí)例創(chuàng)建成功, 并且成功注入了 MyDependency
2.2 @Autowired 方式實(shí)現(xiàn)
a. setter方法用 @Autowired 修飾, 且 MyService 使用 @Component 標(biāo)注為 Bean
@Component
public class MyService {
private MyDependency myDependency;
// setter 方法上加 @Autowired 注解
@Autowired
public void setMyDependency(MyDependency myDependency) {
this.myDependency = myDependency;
}
public void test() {
myDependency.test();
}
}b. 所依賴的 MyDependency 類(lèi)同樣標(biāo)注為 Bean
@Component
public class MyDependency {
public void test(){
System.out.println("測(cè)試");
}
}c. 使用 MyService 的 bean
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
MyService myService = context.getBean(MyService.class);
myService.test();
}
}執(zhí)行后會(huì)打印 "測(cè)試", 說(shuō)明 MyService 實(shí)例創(chuàng)建成功, 并且成功注入了 MyDependency
2.3 運(yùn)行時(shí)更改其依賴對(duì)象
a. setter方法用 @Autowired 修飾, 且 MyService 使用 @Component 標(biāo)注為 Bean
@Component
public class MyService {
private MyDependency myDependency;
// Setter 方法注入
@Autowired
public void setMyDependency(MyDependency myDependency) {
this.myDependency = myDependency;
}
public void doSomething() {
System.out.println(myDependency.getMessage());
}
}b. 所依賴的 MyDependency 類(lèi)同樣標(biāo)注為 Bean, 并設(shè)置 message 屬性, 來(lái)觀察依賴對(duì)象是否發(fā)生了變化
@Component
public class MyDependency {
private String message = "默認(rèn)輸出";
public void setMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}c.進(jìn)行更改測(cè)試
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
MyService myService = context.getBean(MyService.class);
// 調(diào)用 MyService 的方法,顯示初始注入的消息
myService.doSomething();
// 在運(yùn)行時(shí)進(jìn)行更改
MyDependency newDependency = new MyDependency();
newDependency.setMessage("修改之后的輸出");
// 使用 Setter 方法注入新的 MyDependency 實(shí)例
myService.setMyDependency(newDependency);
// 調(diào)用 MyService 的方法,顯示修改后的消息
myService.doSomething();
}
}輸出:
默認(rèn)輸出
修改之后的輸出
輸出發(fā)生了變化, 說(shuō)明依賴對(duì)象發(fā)生了改變
3. 成員變量注入
Spring 的成員變量注入是一種通過(guò)直接將依賴注入到類(lèi)的成員變量上來(lái)實(shí)現(xiàn)依賴注入的方式。通過(guò)在成員變量上使用 @Autowired 注解,Spring 容器會(huì)自動(dòng)將匹配的依賴注入到該成員變量中。
1. 成員變量注入是通過(guò)直接在類(lèi)的成員變量上添加注解(如 @Autowired)來(lái)實(shí)現(xiàn)的, 更加簡(jiǎn)單。
2. 依賴對(duì)象在對(duì)象創(chuàng)建后直接通過(guò)反射注入到成員變量上。
3. 成員變量注入類(lèi)似于 setter 方法注入,也允許依賴對(duì)象在對(duì)象創(chuàng)建后進(jìn)行變更,對(duì)象的依賴可以在對(duì)象創(chuàng)建后動(dòng)態(tài)更改。
a. 成員變量用 @Autowired 修飾, 且 MyService 使用 @Component 標(biāo)注為 Bean
@Component
public class MyService {
@Autowired
private MyDependency myDependency;
public void doSomething() {
System.out.println(myDependency.getMessage());
}
}b. 所依賴的 MyDependency 類(lèi)同樣標(biāo)注為 Bean, 并設(shè)置 message 屬性, 來(lái)觀察依賴對(duì)象是否發(fā)生了變化
@Component
public class MyDependency {
private String message = "默認(rèn)輸出";
public void setMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}c.測(cè)試
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
MyService myService = context.getBean(MyService.class);
// 調(diào)用 MyService 的方法,顯示初始注入的消息
myService.doSomething();
// 取出 MyDependency 的bean, 在這基礎(chǔ)之上進(jìn)行修改
MyDependency newDependency = context.getBean(MyDependency.class);
newDependency.setMessage("修改之后的輸出");
myService.doSomething();
}
}輸出:
默認(rèn)輸出
修改之后的輸出
輸出發(fā)生了變化, 說(shuō)明依賴對(duì)象發(fā)生了改變
二.循環(huán)依賴注入
在 Spring 框架中,循環(huán)依賴是指兩個(gè)或多個(gè) Bean 之間互相依賴,形成了一個(gè)循環(huán)鏈,導(dǎo)致 Spring 容器無(wú)法正確地完成 Bean 的初始化。
為了解決循環(huán)依賴問(wèn)題,Spring 使用了"提前暴露"和"后期填充"的策略。簡(jiǎn)單來(lái)說(shuō),就是在創(chuàng)建 Bean 的過(guò)程中,如果發(fā)現(xiàn)循環(huán)依賴,Spring 會(huì)盡可能提前暴露一個(gè)未完全初始化的 Bean 實(shí)例,以便在后續(xù)的創(chuàng)建過(guò)程中使用,從而打破循環(huán)依賴。這需要 Spring 容器在創(chuàng)建和管理 Bean 實(shí)例時(shí)進(jìn)行一些復(fù)雜的操作,以確保依賴關(guān)系正確解析。
Spring 在默認(rèn)的情況下主要解決了單例模式下的某些循環(huán)依賴的問(wèn)題,因?yàn)?Spring 的默認(rèn)作用域是單例(Singleton)。在單例作用域下,Spring 可以在容器啟動(dòng)時(shí)創(chuàng)建所有的 Bean,并在創(chuàng)建 Bean 實(shí)例的同時(shí)處理循環(huán)依賴。以下主要在單例模式下進(jìn)行演示。
1.構(gòu)造方法注入
如果兩個(gè)類(lèi)的構(gòu)造方法參數(shù)中存在循環(huán)依賴,Spring 無(wú)法解決這個(gè)問(wèn)題, 因?yàn)闃?gòu)造方法是用來(lái)創(chuàng)建對(duì)象的初始步驟,執(zhí)行構(gòu)造函數(shù)是實(shí)例化階段, 其它注入方式是在初始化階段(構(gòu)造函數(shù)執(zhí)行完成, 將對(duì)象創(chuàng)建出來(lái)后,給對(duì)象的屬性賦值), 如果參數(shù)相互引用,那么就會(huì)形成一個(gè)循環(huán)依賴。
@Component
public class ClassA {
private ClassB classB;
@Autowired
public ClassA(ClassB classB) {
this.classB = classB;
}
public void print() {
System.out.println("ClassA");
}
}
@Component
public class ClassB {
private ClassA classA;
@Autowired
public ClassB(ClassA classA) {
this.classA = classA;
}
public void print() {
System.out.println("ClassB");
}
}
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
// 嘗試獲取 ClassA、ClassB 和 ClassC 的實(shí)例
ClassA classA = context.getBean(ClassA.class);
ClassB classB = context.getBean(ClassB.class);
// 調(diào)用 print 方法,查看輸出
classA.print();
classB.print();
}
}此示例會(huì)發(fā)生循環(huán)依賴
2. setter 方法注入
@Component
public class ClassA {
private ClassB classB;
@Autowired
public void setClassB(ClassB classB) {
this.classB = classB;
}
public void print() {
System.out.println("ClassA");
}
}
@Component
public class ClassB {
private ClassA classA;
@Autowired
public void setClassC(ClassA classA) {
this.classA = classA;
}
public void print() {
System.out.println("ClassB");
}
}
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
// 嘗試獲取 ClassA、ClassB 和 ClassC 的實(shí)例
ClassA classA = context.getBean(ClassA.class);
ClassB classB = context.getBean(ClassB.class);
// 調(diào)用 print 方法,查看輸出
classA.print();
classB.print();
}
}此示例不會(huì)發(fā)生循環(huán)依賴, 因?yàn)?Spring 的 Bean 默認(rèn)為單例模式, Spring解決了單例模式下 setter 注入方式的循環(huán)依賴問(wèn)題, 具體怎么解決的, 下文細(xì)說(shuō)。
3.成員變量注入
@Component
public class ClassA {
@Autowired
private ClassB classB;
public void print() {
System.out.println("ClassA");
}
}
@Component
public class ClassB {
@Autowired
private ClassA classA;
public void print() {
System.out.println("classA");
}
}
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
// 嘗試獲取 ClassA、ClassB 和 ClassC 的實(shí)例
ClassA classA = context.getBean(ClassA.class);
ClassB classB = context.getBean(ClassB.class);
// 調(diào)用 print 方法,查看輸出
classA.print();
classB.print();
}
}此示例不會(huì)發(fā)生循環(huán)依賴, 與 setter 方法注入一樣
三. Spring 三級(jí)緩存
Spring框架中的三級(jí)緩存是用于處理單例 Bean 的循環(huán)依賴問(wèn)題的一種機(jī)制。這三級(jí)緩存包括:
1. singletonObjects:這是Spring容器中單例 Bean 的緩存,它保存了已經(jīng)完全初始化的單例 Bean 的實(shí)例。在這個(gè)緩存中,Bean 的名字作為鍵,Bean 的實(shí)例作為值。
2. earlySingletonObjects:這個(gè)緩存包含了尚未完成構(gòu)造的單例 Bean 的半成品(即尚未執(zhí)行完構(gòu)造函數(shù)的 Bean 實(shí)例)。這些 Bean 是不完整的,但已經(jīng)創(chuàng)建,用于解決循環(huán)依賴問(wèn)題。
3. singletonFactories:這個(gè)緩存用于存儲(chǔ)用于創(chuàng)建單例 Bean 的工廠對(duì)象。這些工廠對(duì)象是 Bean 實(shí)例化的原始來(lái)源。
1. 三級(jí)緩存的工作原理
// 一個(gè) ConcurrentHashMap,用于存儲(chǔ)已經(jīng)完全初始化的單例 Bean 的實(shí)例。
// 在這個(gè)緩存中,Bean 的名字作為鍵,Bean 的實(shí)例作為值。
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 另一個(gè) ConcurrentHashMap,用于存儲(chǔ)尚未完成構(gòu)造的單例 Bean 的半成品(即尚未執(zhí)行完構(gòu)造函數(shù)的 Bean 實(shí)例)。
// 這些 Bean 是不完整的,但已經(jīng)創(chuàng)建,用于解決循環(huán)依賴問(wèn)題。
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 存儲(chǔ)用于創(chuàng)建單例 Bean 的工廠對(duì)象。這些工廠對(duì)象是 Bean 實(shí)例化的原始來(lái)源。
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/**
* 返回指定名稱的單例bean
* 獲取指定名字的單例 Bean 的實(shí)例,同時(shí)處理循環(huán)依賴的情況
* 檢查已經(jīng)實(shí)例化的單例對(duì)象,并允許提前引用當(dāng)前正在創(chuàng)建的單例對(duì)象(解決循環(huán)引用問(wèn)題)。
* @param beanName bean名稱
* @param allowEarlyReference 是否允許提前引用
* @return
*/
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 嘗試從 singletonObjects 緩存中獲取指定名字的 Bean 實(shí)例,如果找到了,就直接返回
Object singletonObject = this.singletonObjects.get(beanName);
// 如果在 singletonObjects 緩存中找不到 Bean 實(shí)例,并且判斷 Bean 當(dāng)前是否正在創(chuàng)建,如果是,
// 那么它會(huì)嘗試從 earlySingletonObjects 緩存中獲取 Bean 的半成品。
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 如果找到了半成品 Bean, 就返回這個(gè)半成品 Bean。
singletonObject = this.earlySingletonObjects.get(beanName);
// 如果半成品 Bean 為null, 并且允許提前引用的情況下執(zhí)行以下代碼
// 這是一個(gè)雙檢鎖(或者多檢鎖模式)
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// 再次嘗試從 singletonObjects 緩存中獲取指定名字的 Bean 實(shí)例
singletonObject = this.singletonObjects.get(beanName);
// 如果仍為空
if (singletonObject == null) {
// 再次嘗試從 earlySingletonObjects 緩存中獲取 Bean 的半成品。
singletonObject = this.earlySingletonObjects.get(beanName);
// 如果還為空
if (singletonObject == null) {
// 那么就查看 singletonFactories 緩存中是否有一個(gè)工廠對(duì)象(ObjectFactory),
// 可以用來(lái)創(chuàng)建指定名字的單例對(duì)象。
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
// 如果工程對(duì)象不為空
if (singletonFactory != null) {
// 使用工廠對(duì)象的 getObject() 方法來(lái)創(chuàng)建單例對(duì)象,并,表示這個(gè)對(duì)象正在創(chuàng)建中。
singletonObject = singletonFactory.getObject();
// 將這個(gè)對(duì)象放入 earlySingletonObjects 緩存中
this.earlySingletonObjects.put(beanName, singletonObject);
// 然后從 singletonFactories 緩存中移除對(duì)應(yīng)的工廠對(duì)象,以避免重復(fù)創(chuàng)建。
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
// 當(dāng)前正在創(chuàng)建中的 Bean 的名字集合
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
/**
* 用于存儲(chǔ)正在創(chuàng)建中的 Bean 的名字。每當(dāng) Spring 容器開(kāi)始創(chuàng)建一個(gè) Bean 時(shí),
* 它會(huì)將該 Bean 的名字添加到這個(gè)集合中,表示這個(gè) Bean 正在創(chuàng)建過(guò)程中。
* @param beanName
* @return
*/
public boolean isSingletonCurrentlyInCreation(String beanName) {
return this.singletonsCurrentlyInCreation.contains(beanName);
}1. 當(dāng)容器需要獲取某個(gè)Bean時(shí),它會(huì)首先查找singletonObjects緩存,如果找到就直接返回Bean的實(shí)例。
2. 如果沒(méi)有找到,容器會(huì)檢查earlySingletonObjects緩存,如果存在半成品Bean,就將其完成初始化并返回。
3. 如果仍然沒(méi)有, 則使用工廠對(duì)象ObjectFactory的 getObject() 方法來(lái)創(chuàng)建單例對(duì)象。放到 earlySingletonObjects 中返回。
三級(jí)緩存的主要作用是解決單例 Bean 的循環(huán)依賴問(wèn)題。通過(guò)這種機(jī)制,Spring 容器能夠在 Bean 的創(chuàng)建和初始化過(guò)程中提前暴露和處理依賴關(guān)系,確保循環(huán)依賴能夠正確解決。
注意: 這個(gè)三級(jí)緩存機(jī)制僅適用于單例 Bean,對(duì)于其他作用域(如原型、會(huì)話、請(qǐng)求等),Spring 采用不同的機(jī)制來(lái)處理依賴。
2. 為什么需要三級(jí)緩存
由上我們可以思考一下, 如果只要一級(jí)緩存和三級(jí)緩存, 而去掉二級(jí)緩存, 是不是也可以實(shí)現(xiàn)解決循環(huán)依賴的需求, 比如在一級(jí)緩存 singletonObjects 中如果找不到, 則直接調(diào)用三級(jí)緩存singletonFactories, 這樣的話是不是也可以?
答案是不可以的:
在 singletonFactories 三級(jí)緩存中, ObjectFactory.getObject() 方法所實(shí)現(xiàn)的功能是, 如果 bean 被 AOP 切面代理則返回的是 beanProxy 對(duì)象,如果未被代理則返回的是原 bean 實(shí)例,這樣我們就能拿到屬性未填充的 Bean 實(shí)例,然后從三級(jí)緩存移除,放到二級(jí)緩存earlySingletonObjects中。
而 Bean 如果被 AOP 代理了, ObjectFactory.getObject() 每次都會(huì)返回一個(gè)新的代理對(duì)象, 這跟我們的單例模式是相悖的, 所以把生成的代理對(duì)象, 放入二級(jí)緩存中, 則可以保證生成的代理對(duì)象唯一。并且二級(jí)緩存 earlySingletonObjects 可以幫助我們更準(zhǔn)確的跟蹤正在創(chuàng)建中的 Bean。
四.解決循環(huán)依賴注入問(wèn)題
由上測(cè)試可知, Spring 自己只能解決單例模式下, setter方式注入和成員變量注入時(shí)的循環(huán)依賴問(wèn)題, 但是沒(méi)有解決構(gòu)造方法注入時(shí)的循環(huán)依賴問(wèn)題, 而且, 在某些過(guò)于復(fù)雜的情況下, 即使是 setter 注入或者成員變量注入的情況下, 比如有些依賴是構(gòu)造方法注入, 有些是成員變量注入, 相互引用時(shí)可能也會(huì)發(fā)生循環(huán)依賴的報(bào)錯(cuò), 我們可以采用一些辦法解決這些問(wèn)題。
@Lazy 注解
Spring 默認(rèn)情況下,Bean 是在容器啟動(dòng)時(shí)加載的。這意味著在 Spring 容器啟動(dòng)時(shí),它會(huì)實(shí)例化和初始化所有單例(Singleton)作用域的 Bean。這樣,在應(yīng)用程序運(yùn)行過(guò)程中,這些 Bean 已經(jīng)準(zhǔn)備好使用。
當(dāng)在一個(gè) Bean 上使用 @Lazy 注解時(shí),這個(gè) Bean 將在首次被訪問(wèn)時(shí)才會(huì)被實(shí)際創(chuàng)建和初始化,而不是在 Spring 容器啟動(dòng)時(shí)立即創(chuàng)建。因此可以解決循環(huán)依賴的問(wèn)題。
工作原理:
1. Lazy 注解的工作原理是通過(guò)創(chuàng)建一個(gè)代理對(duì)象來(lái)實(shí)現(xiàn)延遲初始化。當(dāng)一個(gè)Bean被標(biāo)記為 @Lazy 時(shí),Spring會(huì)創(chuàng)建一個(gè)代理對(duì)象來(lái)代替原始的Bean對(duì)象。當(dāng)其他Bean依賴該Bean時(shí),實(shí)際上是依賴了代理對(duì)象。代理對(duì)象會(huì)在第一次被訪問(wèn)時(shí),才真正初始化原始的 Bean 對(duì)象。
2. 初始化時(shí)注入代理對(duì)象,真實(shí)調(diào)用時(shí)使用 Spring AOP 動(dòng)態(tài)代理去關(guān)聯(lián)真實(shí)對(duì)象,然后通過(guò)反射完成調(diào)用。
3. 加在構(gòu)造器上,作用域?yàn)闃?gòu)造器所有參數(shù),加在構(gòu)造器某個(gè)參數(shù)上,作用域?yàn)樵搮?shù)。
4. 作用在接口上,使用 JDK動(dòng)態(tài)代理,作用在類(lèi)上,使用 CGLib動(dòng)態(tài)代理。
@Component
public class ClassA {
private ClassB classB;
@Lazy
@Autowired
public ClassA(ClassB classB) {
this.classB = classB;
}
public void print() {
System.out.println("ClassA");
}
}
@Component
public class ClassB {
private ClassA classA;
@Lazy
@Autowired
public ClassB(ClassA classA) {
this.classA = classA;
}
public void print() {
System.out.println("ClassB");
}
}五.多例(prototype)模式下的循環(huán)依賴
原型(Prototype)作用域的 Bean, Spring 不會(huì)解決循環(huán)依賴問(wèn)題, 這是因?yàn)樵趩卫J较拢琒pring 會(huì)在容器啟動(dòng)時(shí)創(chuàng)建所有的 Bean 實(shí)例,并且可以提前暴露未完全初始化的實(shí)例來(lái)解決循環(huán)依賴。而多例模式的 Bean 是每次請(qǐng)求時(shí)都會(huì)創(chuàng)建一個(gè)新的實(shí)例,而不是在容器啟動(dòng)時(shí)就創(chuàng)建。因此,在原型作用域下,如果存在循環(huán)依賴,Spring 將無(wú)法提前暴露一個(gè)未完全初始化的 Bean 實(shí)例。這意味著在多例模式下,Spring 不會(huì)保留 Bean 實(shí)例之間的狀態(tài)或引用關(guān)系,因?yàn)樗鼈兪仟?dú)立的。
現(xiàn)在有兩個(gè)多例 Bean,A 和 B,它們相互依賴,即 A 需要引用 B,同時(shí) B 也需要引用 A。會(huì)發(fā)生如下情況:
當(dāng)你請(qǐng)求 A 時(shí),Spring 創(chuàng)建了一個(gè)新的 A 實(shí)例,并嘗試注入 B。
當(dāng)你請(qǐng)求 B 時(shí),Spring 創(chuàng)建了一個(gè)新的 B 實(shí)例,并嘗試注入 A。
此時(shí),A 和 B 都是全新的實(shí)例,它們互相不知道對(duì)方的存在,因此無(wú)法建立循環(huán)依賴關(guān)系。
@Scope("prototype")
@Component
public class ClassA {
private ClassB classB;
@Autowired
public void setClassB(ClassB classB) {
this.classB = classB;
}
public void print() {
System.out.println("ClassA");
}
}
@Scope("prototype")
@Component
public class ClassB {
private ClassA classA;
@Autowired
public void setClassC(ClassA classA) {
this.classA = classA;
}
public void print() {
System.out.println("ClassB");
}
}
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
// 嘗試獲取 ClassA、ClassB 和 ClassC 的實(shí)例
ClassA classA = context.getBean(ClassA.class);
ClassB classB = context.getBean(ClassB.class);
// 調(diào)用 print 方法,查看輸出
classA.print();
classB.print();
}
}程序發(fā)生了循環(huán)引用報(bào)錯(cuò)
解決方法:
1. 重新設(shè)計(jì)架構(gòu):避免多例 Bean 之間的循環(huán)依賴。可能需要重新組織類(lèi)的職責(zé),將功能劃分為更小的單元,以減少依賴關(guān)系的復(fù)雜性。
2. 引入中介對(duì)象:引入一個(gè)中介對(duì)象,將循環(huán)依賴關(guān)系切斷。中介對(duì)象可以是單例模式的,用于協(xié)調(diào)多例 Bean 之間的交互。
3. 手動(dòng)管理依賴解析:不依賴 Spring 的自動(dòng)注入。在每個(gè)多例 Bean 內(nèi)部,可以使用工廠方法或者手動(dòng)創(chuàng)建實(shí)例,然后在需要的時(shí)候?qū)⒁蕾囎⑷搿?/p>
到此這篇關(guān)于Spring 依賴注入和循環(huán)依賴的文章就介紹到這了,更多相關(guān)Spring 依賴注入和循環(huán)依賴內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis實(shí)現(xiàn)自定義的typehandler三步曲
這篇文章主要介紹了Mybatis實(shí)現(xiàn)自定義的typehandler三步曲的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07
詳解Spring-Cloud2.0之Feign調(diào)用遠(yuǎn)程服務(wù)指南
這篇文章主要介紹了詳解Spring-Cloud2.0之Feign調(diào)用遠(yuǎn)程服務(wù)指南,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01
關(guān)于maven依賴 ${xxx.version}報(bào)錯(cuò)問(wèn)題
這篇文章主要介紹了關(guān)于maven依賴 ${xxx.version}報(bào)錯(cuò)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
Netty分布式pipeline管道Handler的添加代碼跟蹤解析
這篇文章主要介紹了Netty分布式pipeline管道Handler的添加代碼跟蹤解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03
詳解Java中String,StringBuffer和StringBuilder的使用
這篇文章主要為大家詳細(xì)介紹了Java中String,StringBuffer和StringBuilder三者的區(qū)別以及使用,文中的少了講解詳細(xì),感興趣的可以了解一下2022-07-07
使用Java代碼來(lái)比較Android客戶端版本號(hào)
這篇文章主要介紹了使用Java代碼來(lái)比較Android客戶端版本號(hào),Java是目前安卓程序唯一的開(kāi)發(fā)語(yǔ)言,需要的朋友可以參考下2015-07-07
java 過(guò)濾器filter防sql注入的實(shí)現(xiàn)代碼
下面小編就為大家?guī)?lái)一篇java 過(guò)濾器filter防sql注入的實(shí)現(xiàn)代碼。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-08-08

