欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring?依賴注入和循環(huán)依賴的實(shí)例解析

 更新時(shí)間:2023年09月21日 09:41:47   作者:純潔的小魔鬼  
依賴注入的主要目的是降低類(lèi)之間的耦合度,使得代碼更加靈活、可維護(hù)和可測(cè)試,這篇文章主要介紹了Spring?依賴注入和循環(huán)依賴的相關(guā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)文章

  • java編程Reference核心原理示例源碼分析

    java編程Reference核心原理示例源碼分析

    這篇文章主要為大家介紹了java編程Reference的核心原理以及示例源碼分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-01-01
  • Java 中的偽共享詳解及解決方案

    Java 中的偽共享詳解及解決方案

    這篇文章主要介紹了Java 中的偽共享詳解及解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02
  • Mybatis實(shí)現(xiàn)自定義的typehandler三步曲

    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ù)指南

    這篇文章主要介紹了詳解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)題

    這篇文章主要介紹了關(guān)于maven依賴 ${xxx.version}報(bào)錯(cuò)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Netty分布式pipeline管道Handler的添加代碼跟蹤解析

    Netty分布式pipeline管道Handler的添加代碼跟蹤解析

    這篇文章主要介紹了Netty分布式pipeline管道Handler的添加代碼跟蹤解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-03-03
  • 詳解Java中String,StringBuffer和StringBuilder的使用

    詳解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代碼來(lái)比較Android客戶端版本號(hào),Java是目前安卓程序唯一的開(kāi)發(fā)語(yǔ)言,需要的朋友可以參考下
    2015-07-07
  • Java13 明天發(fā)布(最新最全新特性解讀)

    Java13 明天發(fā)布(最新最全新特性解讀)

    這篇文章主要介紹了Java13 明天發(fā)布,最新最全新特性解讀,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • java 過(guò)濾器filter防sql注入的實(shí)現(xiàn)代碼

    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

最新評(píng)論