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

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

 更新時間:2023年09月21日 09:41:47   作者:純潔的小魔鬼  
依賴注入的主要目的是降低類之間的耦合度,使得代碼更加靈活、可維護和可測試,這篇文章主要介紹了Spring?依賴注入和循環(huán)依賴的相關知識,需要的朋友可以參考下

一.依賴注入的方式

       依賴注入(Dependency Injection,簡稱DI)是一種軟件設計模式和編程技術,用于實現(xiàn)類之間的解耦和依賴關系的管理。它的核心思想是:在對象創(chuàng)建時,由外部容器負責將該對象所依賴的其他對象(依賴)傳遞進來,而不是由對象自己去創(chuàng)建或查找這些依賴對象。

        依賴注入的主要目的是降低類之間的耦合度,使得代碼更加靈活、可維護和可測試。通過依賴注入,一個類不需要關心它所依賴的對象是如何創(chuàng)建和獲取的,而只需定義需要哪些依賴,并由外部容器負責提供這些依賴。這樣可以減少代碼中的硬編碼,使得代碼更加模塊化和可復用。

        不同的注入方式各有優(yōu)劣,選擇合適的方式取決于具體的業(yè)務場景和設計要求。構造函數(shù)注入通常用于強制的、不可變的依賴關系,而 setter 方法注入和成員變量注入則更適合可選的、可變的依賴關系。使用 Spring 時,可以根據(jù)具體情況選擇最合適的依賴注入方式。

依賴注入通常有以下幾種方式:

1. 構造函數(shù)注入(Constructor Injection)

        通過構造函數(shù)來實現(xiàn)對依賴對象的注入。使用構造函數(shù)注入時,Spring 容器會在創(chuàng)建對象時自動解析構造函數(shù)的參數(shù),并將相應的依賴對象傳遞給構造函數(shù),從而完成對象的創(chuàng)建和注入。

        構造函數(shù)注入是 Spring 中常用的一種依賴注入方式,它使得對象的創(chuàng)建和依賴注入更加清晰和類型安全。相比于其他注入方式(如屬性注入或方法注入),構造函數(shù)注入更具有優(yōu)勢,因為它確保了在對象創(chuàng)建時所有必需的依賴都已經(jīng)得到了滿足。

1. 構造函數(shù)注入是通過在類的構造函數(shù)中傳入依賴對象來實現(xiàn)的。

2. 構造函數(shù)注入強制依賴對象在創(chuàng)建實例時必須提供,是類的必要組成部分, 確保了類在實例化時的完整性。

3. 構造函數(shù)注入通常用于表示強制的、必須的依賴關系,提供了更好的不可變性和線程安全性。

4. 使用構造函數(shù)注入時,類的依賴關系在實例化時就被解決,對象的狀態(tài)一經(jīng)創(chuàng)建就不能更改(如果既有構造方法注入, 又提供了 setter 方法, 這時依賴對象是可以改變的, 如果不想被改變, 需要把注入的對象設置 final, 這樣就無法設置 setter 方法了, 此時對象不可變, 指的是不能變成兩個不同的對象, 但是就算加了final, 依賴對象內(nèi)的一些屬性還是可以改變的, 而 setter 注入和成員變量注入則無法設置為final)。

1.1 @Configuration 方式實現(xiàn)

a. 類 MyService 需要注入 MyDependency 類, 在構造方法中聲明需要依賴的參數(shù)

public class MyService {
    private final MyDependency myDependency;
    public MyService(MyDependency myDependency) {
        this.myDependency = myDependency;
    }
    public MyDependency getMyDependency() {
        return myDependency;
    }

b. MyService 類中需要注入的依賴類

public class MyDependency {
    public void test(){
        System.out.println("測試");
    }
}

c. 定義配置類

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
    // 用于創(chuàng)建并配置 MyDependency 的實例
    @Bean
    public MyDependency myDependency() {
        return new MyDependency();
    }
    // 用于創(chuàng)建 MyService 的實例,并通過構造函數(shù)將 MyDependency 的實例傳遞進去
    @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í)行后會打印 "測試", 說明 MyService 實例創(chuàng)建成功, 并且成功注入了 MyDependency

1.2 @Autowired 方式實現(xiàn)

a. 構造函數(shù)用 @Autowired 修飾, 且 MyService 使用 @Component 標注為 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 類同樣標注為 Bean

@Component
public class MyDependency {
    private String message = "默認輸出";
    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, 改變該依賴對象的message
		MyDependency newDependency = context.getBean(MyDependency.class);
		newDependency.setMessage("修改之后的輸出");
		myService.doSomething();
	}
}

輸出:

默認輸出
修改之后的輸出

說明 MyService 實例創(chuàng)建成功, 并且成功注入了 MyDependency, 并且雖然加了 final, 但是 message 的屬性還是可以發(fā)生變化

d. 如果不將依賴設為 final, 然后再為 依賴設置一個 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.進行一下測試

@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 newDependency = new MyDependency();
		newDependency.setMessage("修改之后的輸出");
		// 設置為新的依賴對象
		myService.setMyDependency(newDependency);
		myService.doSomething();
		// 容器中的依賴對象的輸出
		MyDependency oldDependency = context.getBean(MyDependency.class);
		System.out.println(oldDependency.getMessage());
	}
}

輸出:

默認輸出
修改之后的輸出 
默認輸出

可見 MyDependency 對象被改變了, 但是 MyDependency 的bean還是不變的, setter 注入和成員變量注入更是如此

1.3 需要注意的地方

        如果該類的構造方法只有一個, 可以省略 @Autowired 注解。如果該類中存在多個構造函數(shù), 則需要加上 @Autowired 注解,以明確指明這個構造函數(shù)負責依賴的的注入。

        如果需要注入的是一個接口, 并且有多個實現(xiàn)類, 則我們需要 @Qualifier() 注解標注我們需要注入的類。

a. 需要注入的接口和實現(xiàn)類

public interface IMyDependency {
    void test();
}
@Component("myDependency")
public class MyDependency implements IMyDependency{
    public void test(){
        System.out.println("測試");
    }
}
@Component("myDependency2")
public class MyDependency2  implements IMyDependency{
    public void test(){
        System.out.println("測試2");
    }
}

b. 注入 IMyDependency 接口, 并且存在多個構造函數(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í)行后會打印 "測試2", 說明 MyService 實例創(chuàng)建成功, 并且成功注入了 MyDependency2

2. 屬性注入(Setter Injection)

        通過類的屬性(setter 方法)來傳遞依賴對象。在類的屬性上使用注解或配置來標識依賴關系,并由外部容器在創(chuàng)建類的實例后,通過 setter 方法將依賴對象注入。

1. Setter 方法注入是一種非強制性的注入方式,與構造函數(shù)注入相對。

2. 在 Setter 方法注入中,Spring 容器會在實例化 Bean 后,通過調用對象的相應 setter 方法,將依賴的對象或值注入到對象中。

3. 通過 Setter 方法注入,可以在對象實例化后動態(tài)地設置其依賴屬性。這種方式相對靈活,可以允許對象在運行時更改其依賴,但同時也可能導致一些問題,例如在沒有正確設置依賴的情況下調用對象的方法可能會引發(fā)空指針異常。因此,在使用 Setter 方法注入時,需要確保對象的依賴在調用其方法前已經(jīng)正確設置。

2.1 @Configuration 方式實現(xiàn)

a. 類 MyService 需要注入 MyDependency 類, 在 setter 方法中聲明需要依賴的參數(shù)

public class MyService {
    private MyDependency myDependency;
    public void setMyDependency(MyDependency myDependency) {
        this.myDependency = myDependency;
    }
    public void test() {
        myDependency.test();
    }
}

b. MyService 類中需要注入的依賴類

public class MyDependency {
    public void test(){
        System.out.println("測試");
    }
}

c. 定義配置類

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 容器并加載配置類
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
		// 獲取 MyService Bean
		MyService myService = context.getBean(MyService.class);
		// 調用 MyService 的方法
		myService.test();
		// 關閉容器
		context.close();
	}
}

執(zhí)行后會打印 "測試", 說明 MyService 實例創(chuàng)建成功, 并且成功注入了 MyDependency

2.2 @Autowired 方式實現(xiàn)

a. setter方法用 @Autowired 修飾, 且 MyService 使用 @Component 標注為 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 類同樣標注為 Bean

@Component
public class MyDependency {
    public void test(){
        System.out.println("測試");
    }
}

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í)行后會打印 "測試", 說明 MyService 實例創(chuàng)建成功, 并且成功注入了 MyDependency

2.3 運行時更改其依賴對象

a. setter方法用 @Autowired 修飾, 且 MyService 使用 @Component 標注為 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 類同樣標注為 Bean, 并設置 message 屬性, 來觀察依賴對象是否發(fā)生了變化

@Component
public class MyDependency {
    private String message = "默認輸出";
    public void setMessage(String message) {
        this.message = message;
    }
    public String getMessage() {
        return message;
    }
}

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 的方法,顯示初始注入的消息
		myService.doSomething();
		// 在運行時進行更改
		MyDependency newDependency = new MyDependency();
		newDependency.setMessage("修改之后的輸出");
		// 使用 Setter 方法注入新的 MyDependency 實例
		myService.setMyDependency(newDependency);
		// 調用 MyService 的方法,顯示修改后的消息
		myService.doSomething();
	}
}

輸出:

默認輸出
修改之后的輸出

輸出發(fā)生了變化, 說明依賴對象發(fā)生了改變

3. 成員變量注入

Spring 的成員變量注入是一種通過直接將依賴注入到類的成員變量上來實現(xiàn)依賴注入的方式。通過在成員變量上使用 @Autowired 注解,Spring 容器會自動將匹配的依賴注入到該成員變量中。

1. 成員變量注入是通過直接在類的成員變量上添加注解(如 @Autowired)來實現(xiàn)的, 更加簡單。

2. 依賴對象在對象創(chuàng)建后直接通過反射注入到成員變量上。

3. 成員變量注入類似于 setter 方法注入,也允許依賴對象在對象創(chuàng)建后進行變更,對象的依賴可以在對象創(chuàng)建后動態(tài)更改。

a. 成員變量用 @Autowired 修飾, 且 MyService 使用 @Component 標注為 Bean

@Component
public class MyService {
    @Autowired
    private MyDependency myDependency;
    public void doSomething() {
        System.out.println(myDependency.getMessage());
    }
}

b. 所依賴的 MyDependency 類同樣標注為 Bean, 并設置 message 屬性, 來觀察依賴對象是否發(fā)生了變化

@Component
public class MyDependency {
    private String message = "默認輸出";
    public void setMessage(String message) {
        this.message = message;
    }
    public String getMessage() {
        return message;
    }
}

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 的方法,顯示初始注入的消息
		myService.doSomething();
        // 取出 MyDependency 的bean, 在這基礎之上進行修改
		MyDependency newDependency = context.getBean(MyDependency.class);
		newDependency.setMessage("修改之后的輸出");
		myService.doSomething();
	}
}

輸出:

默認輸出
修改之后的輸出

輸出發(fā)生了變化, 說明依賴對象發(fā)生了改變

二.循環(huán)依賴注入

       在 Spring 框架中,循環(huán)依賴是指兩個或多個 Bean 之間互相依賴,形成了一個循環(huán)鏈,導致 Spring 容器無法正確地完成 Bean 的初始化。

         為了解決循環(huán)依賴問題,Spring 使用了"提前暴露"和"后期填充"的策略。簡單來說,就是在創(chuàng)建 Bean 的過程中,如果發(fā)現(xiàn)循環(huán)依賴,Spring 會盡可能提前暴露一個未完全初始化的 Bean 實例,以便在后續(xù)的創(chuàng)建過程中使用,從而打破循環(huán)依賴。這需要 Spring 容器在創(chuàng)建和管理 Bean 實例時進行一些復雜的操作,以確保依賴關系正確解析。

        Spring 在默認的情況下主要解決了單例模式下的某些循環(huán)依賴的問題,因為 Spring 的默認作用域是單例(Singleton)。在單例作用域下,Spring 可以在容器啟動時創(chuàng)建所有的 Bean,并在創(chuàng)建 Bean 實例的同時處理循環(huán)依賴。以下主要在單例模式下進行演示。

1.構造方法注入

如果兩個類的構造方法參數(shù)中存在循環(huán)依賴,Spring 無法解決這個問題, 因為構造方法是用來創(chuàng)建對象的初始步驟,執(zhí)行構造函數(shù)是實例化階段, 其它注入方式是在初始化階段(構造函數(shù)執(zhí)行完成, 將對象創(chuàng)建出來后,給對象的屬性賦值), 如果參數(shù)相互引用,那么就會形成一個循環(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 的實例
		ClassA classA = context.getBean(ClassA.class);
		ClassB classB = context.getBean(ClassB.class);
		// 調用 print 方法,查看輸出
		classA.print();
		classB.print();
	}
}

此示例會發(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 的實例
		ClassA classA = context.getBean(ClassA.class);
		ClassB classB = context.getBean(ClassB.class);
		// 調用 print 方法,查看輸出
		classA.print();
		classB.print();
	}
}

此示例不會發(fā)生循環(huán)依賴, 因為 Spring 的 Bean 默認為單例模式, Spring解決了單例模式下 setter 注入方式的循環(huán)依賴問題, 具體怎么解決的, 下文細說。

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 的實例
		ClassA classA = context.getBean(ClassA.class);
		ClassB classB = context.getBean(ClassB.class);
		// 調用 print 方法,查看輸出
		classA.print();
		classB.print();
	}
}

此示例不會發(fā)生循環(huán)依賴, 與 setter 方法注入一樣

三. Spring 三級緩存

Spring框架中的三級緩存是用于處理單例 Bean 的循環(huán)依賴問題的一種機制。這三級緩存包括:

1. singletonObjects:這是Spring容器中單例 Bean 的緩存,它保存了已經(jīng)完全初始化的單例 Bean 的實例。在這個緩存中,Bean 的名字作為鍵,Bean 的實例作為值。

2. earlySingletonObjects:這個緩存包含了尚未完成構造的單例 Bean 的半成品(即尚未執(zhí)行完構造函數(shù)的 Bean 實例)。這些 Bean 是不完整的,但已經(jīng)創(chuàng)建,用于解決循環(huán)依賴問題。

3. singletonFactories:這個緩存用于存儲用于創(chuàng)建單例 Bean 的工廠對象。這些工廠對象是 Bean 實例化的原始來源。

1. 三級緩存的工作原理

// 一個 ConcurrentHashMap,用于存儲已經(jīng)完全初始化的單例 Bean 的實例。
	// 在這個緩存中,Bean 的名字作為鍵,Bean 的實例作為值。
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
	// 另一個 ConcurrentHashMap,用于存儲尚未完成構造的單例 Bean 的半成品(即尚未執(zhí)行完構造函數(shù)的 Bean 實例)。
	// 這些 Bean 是不完整的,但已經(jīng)創(chuàng)建,用于解決循環(huán)依賴問題。
	private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
	// 存儲用于創(chuàng)建單例 Bean 的工廠對象。這些工廠對象是 Bean 實例化的原始來源。
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
	/**
	 * 返回指定名稱的單例bean
	 * 獲取指定名字的單例 Bean 的實例,同時處理循環(huán)依賴的情況
	 * 檢查已經(jīng)實例化的單例對象,并允許提前引用當前正在創(chuàng)建的單例對象(解決循環(huán)引用問題)。
	 * @param beanName bean名稱
	 * @param allowEarlyReference 是否允許提前引用
	 * @return
	 */
	@Nullable
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// 嘗試從 singletonObjects 緩存中獲取指定名字的 Bean 實例,如果找到了,就直接返回
		Object singletonObject = this.singletonObjects.get(beanName);
		// 如果在 singletonObjects 緩存中找不到 Bean 實例,并且判斷 Bean 當前是否正在創(chuàng)建,如果是,
		// 那么它會嘗試從 earlySingletonObjects 緩存中獲取 Bean 的半成品。
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			// 如果找到了半成品 Bean, 就返回這個半成品 Bean。
			singletonObject = this.earlySingletonObjects.get(beanName);
			// 如果半成品 Bean 為null, 并且允許提前引用的情況下執(zhí)行以下代碼
			// 這是一個雙檢鎖(或者多檢鎖模式)
			if (singletonObject == null && allowEarlyReference) {
				synchronized (this.singletonObjects) {
					// 再次嘗試從 singletonObjects 緩存中獲取指定名字的 Bean 實例
					singletonObject = this.singletonObjects.get(beanName);
					// 如果仍為空
					if (singletonObject == null) {
						// 再次嘗試從 earlySingletonObjects 緩存中獲取 Bean 的半成品。
						singletonObject = this.earlySingletonObjects.get(beanName);
						// 如果還為空
						if (singletonObject == null) {
							// 那么就查看 singletonFactories 緩存中是否有一個工廠對象(ObjectFactory),
							// 可以用來創(chuàng)建指定名字的單例對象。
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
							// 如果工程對象不為空
							if (singletonFactory != null) {
								// 使用工廠對象的 getObject() 方法來創(chuàng)建單例對象,并,表示這個對象正在創(chuàng)建中。
								singletonObject = singletonFactory.getObject();
								// 將這個對象放入 earlySingletonObjects 緩存中
								this.earlySingletonObjects.put(beanName, singletonObject);
								// 然后從 singletonFactories 緩存中移除對應的工廠對象,以避免重復創(chuàng)建。
								this.singletonFactories.remove(beanName);
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}
	// 當前正在創(chuàng)建中的 Bean 的名字集合
	private final Set<String> singletonsCurrentlyInCreation =
			Collections.newSetFromMap(new ConcurrentHashMap<>(16));
	/**
	 * 用于存儲正在創(chuàng)建中的 Bean 的名字。每當 Spring 容器開始創(chuàng)建一個 Bean 時,
	 * 它會將該 Bean 的名字添加到這個集合中,表示這個 Bean 正在創(chuàng)建過程中。
	 * @param beanName
	 * @return
	 */
	public boolean isSingletonCurrentlyInCreation(String beanName) {
		return this.singletonsCurrentlyInCreation.contains(beanName);
	}

1. 當容器需要獲取某個Bean時,它會首先查找singletonObjects緩存,如果找到就直接返回Bean的實例。

2. 如果沒有找到,容器會檢查earlySingletonObjects緩存,如果存在半成品Bean,就將其完成初始化并返回。

3. 如果仍然沒有, 則使用工廠對象ObjectFactory的 getObject() 方法來創(chuàng)建單例對象。放到 earlySingletonObjects 中返回。

三級緩存的主要作用是解決單例 Bean 的循環(huán)依賴問題。通過這種機制,Spring 容器能夠在 Bean 的創(chuàng)建和初始化過程中提前暴露和處理依賴關系,確保循環(huán)依賴能夠正確解決。

注意:  這個三級緩存機制僅適用于單例 Bean,對于其他作用域(如原型、會話、請求等),Spring 采用不同的機制來處理依賴。

2. 為什么需要三級緩存

由上我們可以思考一下, 如果只要一級緩存和三級緩存, 而去掉二級緩存, 是不是也可以實現(xiàn)解決循環(huán)依賴的需求, 比如在一級緩存 singletonObjects 中如果找不到, 則直接調用三級緩存singletonFactories, 這樣的話是不是也可以?

答案是不可以的:

        在 singletonFactories 三級緩存中, ObjectFactory.getObject() 方法所實現(xiàn)的功能是, 如果 bean 被 AOP 切面代理則返回的是 beanProxy 對象,如果未被代理則返回的是原 bean 實例,這樣我們就能拿到屬性未填充的 Bean 實例,然后從三級緩存移除,放到二級緩存earlySingletonObjects中。

       而 Bean 如果被 AOP 代理了, ObjectFactory.getObject() 每次都會返回一個新的代理對象, 這跟我們的單例模式是相悖的, 所以把生成的代理對象, 放入二級緩存中, 則可以保證生成的代理對象唯一。并且二級緩存 earlySingletonObjects 可以幫助我們更準確的跟蹤正在創(chuàng)建中的 Bean。

四.解決循環(huán)依賴注入問題

       由上測試可知, Spring 自己只能解決單例模式下, setter方式注入和成員變量注入時的循環(huán)依賴問題, 但是沒有解決構造方法注入時的循環(huán)依賴問題, 而且, 在某些過于復雜的情況下, 即使是 setter 注入或者成員變量注入的情況下, 比如有些依賴是構造方法注入, 有些是成員變量注入, 相互引用時可能也會發(fā)生循環(huán)依賴的報錯, 我們可以采用一些辦法解決這些問題。

@Lazy 注解

       Spring 默認情況下,Bean 是在容器啟動時加載的。這意味著在 Spring 容器啟動時,它會實例化和初始化所有單例(Singleton)作用域的 Bean。這樣,在應用程序運行過程中,這些 Bean 已經(jīng)準備好使用。

        當在一個 Bean 上使用 @Lazy 注解時,這個 Bean 將在首次被訪問時才會被實際創(chuàng)建和初始化,而不是在 Spring 容器啟動時立即創(chuàng)建。因此可以解決循環(huán)依賴的問題。

工作原理:

1. Lazy 注解的工作原理是通過創(chuàng)建一個代理對象來實現(xiàn)延遲初始化。當一個Bean被標記為 @Lazy 時,Spring會創(chuàng)建一個代理對象來代替原始的Bean對象。當其他Bean依賴該Bean時,實際上是依賴了代理對象。代理對象會在第一次被訪問時,才真正初始化原始的 Bean 對象。

2. 初始化時注入代理對象,真實調用時使用 Spring AOP 動態(tài)代理去關聯(lián)真實對象,然后通過反射完成調用。

3. 加在構造器上,作用域為構造器所有參數(shù),加在構造器某個參數(shù)上,作用域為該參數(shù)。

4. 作用在接口上,使用 JDK動態(tài)代理,作用在類上,使用 CGLib動態(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án)依賴問題, 這是因為在單例模式下,Spring 會在容器啟動時創(chuàng)建所有的 Bean 實例,并且可以提前暴露未完全初始化的實例來解決循環(huán)依賴。而多例模式的 Bean 是每次請求時都會創(chuàng)建一個新的實例,而不是在容器啟動時就創(chuàng)建。因此,在原型作用域下,如果存在循環(huán)依賴,Spring 將無法提前暴露一個未完全初始化的 Bean 實例。這意味著在多例模式下,Spring 不會保留 Bean 實例之間的狀態(tài)或引用關系,因為它們是獨立的。

現(xiàn)在有兩個多例 Bean,A 和 B,它們相互依賴,即 A 需要引用 B,同時 B 也需要引用 A。會發(fā)生如下情況:

當你請求 A 時,Spring 創(chuàng)建了一個新的 A 實例,并嘗試注入 B。

當你請求 B 時,Spring 創(chuàng)建了一個新的 B 實例,并嘗試注入 A。

此時,A 和 B 都是全新的實例,它們互相不知道對方的存在,因此無法建立循環(huá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 的實例
		ClassA classA = context.getBean(ClassA.class);
		ClassB classB = context.getBean(ClassB.class);
		// 調用 print 方法,查看輸出
		classA.print();
		classB.print();
	}
}

程序發(fā)生了循環(huán)引用報錯

解決方法:

1. 重新設計架構:避免多例 Bean 之間的循環(huán)依賴??赡苄枰匦陆M織類的職責,將功能劃分為更小的單元,以減少依賴關系的復雜性。

2. 引入中介對象:引入一個中介對象,將循環(huán)依賴關系切斷。中介對象可以是單例模式的,用于協(xié)調多例 Bean 之間的交互。

3. 手動管理依賴解析:不依賴 Spring 的自動注入。在每個多例 Bean 內(nèi)部,可以使用工廠方法或者手動創(chuàng)建實例,然后在需要的時候將依賴注入。

到此這篇關于Spring 依賴注入和循環(huán)依賴的文章就介紹到這了,更多相關Spring 依賴注入和循環(huán)依賴內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

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

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

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

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

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

    Mybatis實現(xiàn)自定義的typehandler三步曲

    這篇文章主要介紹了Mybatis實現(xiàn)自定義的typehandler三步曲的相關資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-07-07
  • 詳解Spring-Cloud2.0之Feign調用遠程服務指南

    詳解Spring-Cloud2.0之Feign調用遠程服務指南

    這篇文章主要介紹了詳解Spring-Cloud2.0之Feign調用遠程服務指南,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-01-01
  • 關于maven依賴 ${xxx.version}報錯問題

    關于maven依賴 ${xxx.version}報錯問題

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

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

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

    詳解Java中String,StringBuffer和StringBuilder的使用

    這篇文章主要為大家詳細介紹了Java中String,StringBuffer和StringBuilder三者的區(qū)別以及使用,文中的少了講解詳細,感興趣的可以了解一下
    2022-07-07
  • 使用Java代碼來比較Android客戶端版本號

    使用Java代碼來比較Android客戶端版本號

    這篇文章主要介紹了使用Java代碼來比較Android客戶端版本號,Java是目前安卓程序唯一的開發(fā)語言,需要的朋友可以參考下
    2015-07-07
  • Java13 明天發(fā)布(最新最全新特性解讀)

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

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

    java 過濾器filter防sql注入的實現(xiàn)代碼

    下面小編就為大家?guī)硪黄猨ava 過濾器filter防sql注入的實現(xiàn)代碼。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-08-08

最新評論