天天看點

記一次Spring循環依賴問題

作者:JAVA互聯搬磚勞工

Spring 循環依賴問題詳解

什麼是循環依賴

在軟體開發中,循環依賴指的是兩個或多個對象之間互相依賴,形成了一個循環的依賴關系。這種情況下,如果沒有合适的處理方式,就會導緻應用程式無法正常啟動或運作,甚至出現死循環等問題。

Spring 中的循環依賴問題

在 Spring 中,循環依賴問題通常出現在 Bean 之間的依賴注入過程中。具體來說,它可能表現為以下幾種形式:

  1. 構造函數注入的循環依賴:當一個 Bean 的構造函數參數中依賴于另一個 Bean,而另一個 Bean 又依賴于第一個 Bean 時,就會出現構造函數注入的循環依賴問題。
java複制代碼public class A {
    private final B b; // 構造函數注入 B
    public A(B b) {
        this.b = b;
    }
}

public class B {
    private final A a; // 構造函數注入 A
    public B(A a) {
        this.a = a;
    }
}
           
  1. Setter 注入的循環依賴:當一個 Bean 的屬性需要注入另一個 Bean,而另一個 Bean 又依賴于該 Bean 時,就會出現 Setter 注入的循環依賴問題。
java複制代碼public class A {
    private B b; // Setter 注入 B
    public void setB(B b) {
        this.b = b;
    }
}

public class B {
    private A a; // Setter 注入 A
    public void setA(A a) {
        this.a = a;
    }
}
           
  1. Singleton Bean 之間的循環依賴:當多個 Singleton Bean 之間互相依賴時,也可能出現循環依賴問題。
java複制代碼@Service
public class A {
    private final B b; // 構造函數注入 B
    public A(B b) {
        this.b = b;
    }
}

@Service
public class B {
    private final A a; // 構造函數注入 A
    public B(A a) {
        this.a = a;
    }
}
           

如何解決 Spring 中的循環依賴問題

為了避免循環依賴的發生,我們可以采取以下措施:

  1. 使用構造函數注入:使用構造函數注入可以避免 Setter 注入的循環依賴問題,因為這種方式可以保證 Bean 在執行個體化時已經完成了依賴關系的設定。
java複制代碼public class A {
    private final B b; // 構造函數注入 B
    public A(B b) {
        this.b = b;
    }
}

public class B {
    private final A a; // 構造函數注入 A
    public B(A a) {
        this.a = a;
    }
}
           
  1. 使用 @Lazy 注解:使用 @Lazy 注解可以使 Bean 延遲初始化,進而避免循環依賴問題的發生。但是需要注意的是,這種方式可能會對應用程式的性能産生一定的影響。
java複制代碼@Service
@Lazy
public class A {
    private B b; // Setter 注入 B
    public void setB(B b) {
        this.b = b;
    }
}

@Service
@Lazy
public class B {
    private A a; // Setter 注入 A
    public void setA(A a) {
        this.a = a;
    }
}
           

另外,Spring 還提供了一些更進階的解決方案,包括:

  1. 使用代理:使用代理可以解決 Singleton Bean 之間的循環依賴問題,即使它們都是非懶加載的。具體來說,我們可以使用 Spring AOP 中的 AspectJ 代理或 CGLIB 代理來實作 Bean 之間的代理引用。
java複制代碼@Service
public class A {
    private B b; // Setter 注入 B
    public void setB(B b) {
        this.b = b;
    }
}

public class B {
    private A a; // Setter 注入 A
    public void setA(A a) {
        this.a = a;
    }
}

@Configuration
public class AppConfig {
    @Bean(name="a")
    @Scope(value="singleton", proxyMode=ScopedProxyMode.TARGET_CLASS)
    public A a() {
        return new A();
    }
    
    @Bean(name="b")
    @Scope(value="singleton", proxyMode=ScopedProxyMode.TARGET_CLASS)
    public B b() {
        return new B();
    }
}
           
  1. 在不同的作用域中處理循環依賴:在 Spring 中,我們可以使用不同的作用域來處理循環依賴問題。例如,将 Bean 的作用域設定為 prototype,就可以在每次請求 Bean 時建立一個新的執行個體,進而避免循環依賴問題。
java複制代碼@Service
@Scope(value="prototype")
public class A {
    private B b; // Setter 注入 B
    public void setB(B b) {
        this.b = b;
    }
}

@Service
@Scope(value="prototype")
public class B {
    private A a; // Setter 注入 A
    public void setA(A a) {
        this.a = a;
    }
}
           

底層原因

Spring 采用依賴注入的方式來實作元件之間的松耦合,而循環依賴問題是由于元件之間的互相依賴關系導緻的。Spring 設計循環依賴問題主要是為了解決元件之間的互相依賴關系,進而實作更加靈活的元件管理和配置。

在 Spring 中,循環依賴問題主要是由于 Bean 的執行個體化和依賴注入過程中的順序問題。當一個 Bean 的依賴關系需要另一個 Bean 的執行個體時,如果這個 Bean 還沒有被執行個體化,就會導緻循環依賴問題的發生。

Spring 采用了一些解決方案來解決循環依賴問題。例如,使用構造函數注入可以避免 Setter 注入的循環依賴問題,因為這種方式可以保證 Bean 在執行個體化時已經完成了依賴關系的設定。使用 @Lazy 注解可以使 Bean 延遲初始化,進而避免循環依賴問題的發生。使用代理可以解決 Singleton Bean 之間的循環依賴問題,即使它們都是非懶加載的。在不同的作用域中處理循環依賴,例如将 Bean 的作用域設定為 prototype,就可以在每次請求 Bean 時建立一個新的執行個體,進而避免循環依賴問題。

總結和建議

循環依賴是一個常見的問題,特别是在大型的軟體系統中。在 Spring 中,循環依賴問題可能會導緻應用程式無法正常啟動或運作,是以需要采取适當的解決方案來避免或解決這個問題。在實際項目中,我們應該根據具體的情況選擇合适的解決方案,進而保證應用程式的穩定性和性能。

作者:衍志

連結:https://juejin.cn/post/7237174222972092476