Phụ thuộc vòng tròn trong mùa xuân


Câu trả lời:


42

Như các câu trả lời khác đã nói, Spring chỉ việc chăm sóc, tạo hạt đậu và tiêm theo yêu cầu.

Một trong những hậu quả là cài đặt đặc tính / chèn thêm bean có thể xảy ra theo một thứ tự khác với những gì mà các tệp nối dây XML của bạn dường như ngụ ý. Vì vậy, bạn cần phải cẩn thận rằng các bộ thiết lập thuộc tính của bạn không thực hiện khởi tạo mà dựa vào các bộ thiết lập khác đã được gọi. Cách để giải quyết vấn đề này là khai báo bean như việc triển khai InitializingBeangiao diện. Điều này yêu cầu bạn triển khai afterPropertiesSet()phương thức và đây là nơi bạn thực hiện quá trình khởi tạo quan trọng. (Tôi cũng bao gồm mã để kiểm tra xem các thuộc tính quan trọng đã thực sự được thiết lập chưa.)


76

Các tài liệu tham khảo của nhãn hiệu Xuân giải thích như thế nào phụ thuộc vòng tròn được giải quyết. Các hạt đậu được làm liền trước, sau đó được tiêm vào nhau.

Hãy xem xét lớp học này:

package mypackage;

public class A {

    public A() {
        System.out.println("Creating instance of A");
    }

    private B b;

    public void setB(B b) {
        System.out.println("Setting property b of A instance");
        this.b = b;
    }

}

Và một lớp tương tự B:

package mypackage;

public class B {

    public B() {
        System.out.println("Creating instance of B");
    }

    private A a;

    public void setA(A a) {
        System.out.println("Setting property a of B instance");
        this.a = a;
    }

}

Nếu sau đó bạn có tệp cấu hình này:

<bean id="a" class="mypackage.A">
    <property name="b" ref="b" />
</bean>

<bean id="b" class="mypackage.B">
    <property name="a" ref="a" />
</bean>

Bạn sẽ thấy kết quả sau khi tạo ngữ cảnh bằng cấu hình này:

Creating instance of A
Creating instance of B
Setting property a of B instance
Setting property b of A instance

Lưu ý rằng khi ađược tiêm vào b, avẫn chưa được khởi tạo hoàn toàn.


26
Đây là lý do tại sao mùa xuân đòi hỏi một constructor không có đối số ;-)
Chris Thompson

15
Không nếu bạn sử dụng các đối số hàm tạo trong các định nghĩa bean của mình! (Nhưng trong trường hợp đó, bạn không thể có sự phụ thuộc vòng tròn.)
Richard Fearn

1
@Richard Fearn Có bài đăng của bạn về giải thích vấn đề hơn là cung cấp giải pháp không?
gstackoverflow

4
Nếu bạn cố gắng sử dụng constructor injection, thông báo lỗi làorg.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
X. Wo Satuk

19

Trong cơ sở mã mà tôi đang làm việc (hơn 1 triệu dòng mã), chúng tôi gặp sự cố với thời gian khởi động lâu, khoảng 60 giây. Chúng tôi đã nhận được hơn 12000 FactoryBeanNotInitializedException .

Những gì tôi đã làm là đặt một điểm ngắt có điều kiện trong AbstractBeanFactory # doGetBean

catch (BeansException ex) {
   // Explicitly remove instance from singleton cache: It might have been put there
   // eagerly by the creation process, to allow for circular reference resolution.
   // Also remove any beans that received a temporary reference to the bean.
   destroySingleton(beanName);
   throw ex;
}

nơi destroySingleton(beanName)tôi đã in ngoại lệ với mã điểm ngắt có điều kiện:

   System.out.println(ex);
   return false;

Rõ ràng điều này xảy ra khi các FactoryBean tham gia vào một đồ thị phụ thuộc theo chu kỳ. Chúng tôi đã giải quyết nó bằng cách triển khai ApplicationContextAwareInitializingBean và tiêm các bean theo cách thủ công.

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class A implements ApplicationContextAware, InitializingBean{

    private B cyclicDepenency;
    private ApplicationContext ctx;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        ctx = applicationContext;
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        cyclicDepenency = ctx.getBean(B.class);
    }

    public void useCyclicDependency()
    {
        cyclicDepenency.doSomething();
    }
}

Điều này làm giảm thời gian khởi động xuống còn khoảng 15 giây.

Vì vậy, đừng luôn cho rằng mùa xuân có thể giải quyết tốt các tài liệu tham khảo này cho bạn.

Vì lý do này, tôi khuyên bạn nên tắt tính năng phân giải phụ thuộc theo chu kỳ với AbstractRefreshableApplicationContext # setAllowCircularRefferences (false) để tránh nhiều sự cố trong tương lai.


3
Khuyến nghị thú vị. Đề xuất phản đối của tôi sẽ chỉ để làm điều đó nếu bạn nghi ngờ rằng các tham chiếu vòng tròn đang gây ra sự cố về hiệu suất. (Nó sẽ là một sự xấu hổ đến một cái gì đó phá vỡ mà không cần phải bị phá vỡ bằng cách cố gắng để sửa chữa một vấn đề mà không cần sửa chữa.)
Stephen C

2
Đó là một con dốc trơn trượt xuống địa ngục bảo trì để cho phép các phụ thuộc vòng tròn, việc thiết kế lại kiến ​​trúc của bạn từ các phụ thuộc hình tròn có thể thực sự khó khăn, như trong trường hợp của chúng tôi. Điều đó đại khái có ý nghĩa đối với chúng tôi là chúng tôi có số lượng kết nối cơ sở dữ liệu trong quá trình khởi động nhiều gấp đôi so với phiên giao dịch tham gia vào sự phụ thuộc vòng tròn. Trong các tình huống khác, điều thảm khốc hơn nhiều có thể đã xảy ra do hạt đậu được khởi tạo hơn 12000 lần. Chắc chắn bạn nên viết các bean của mình để họ hỗ trợ tiêu diệt chúng nhưng tại sao lại cho phép hành vi này ngay từ đầu?
jontejj

@jontejj, bạn xứng đáng một cookie
serprime

14

Vấn đề ->

Class A {
    private final B b; // must initialize in ctor/instance block
    public A(B b) { this.b = b };
}


Class B {
    private final A a; // must initialize in ctor/instance block
    public B(A a) { this.a = a };
 }

// Gây ra bởi: org.springframework.beans.factory.BeanCurrentlyInCreationException: Lỗi khi tạo bean với tên 'A': bean được yêu cầu hiện đang được tạo: Có tham chiếu vòng tròn không thể giải quyết được không?

Giải pháp 1 ->

Class A {
    private B b; 
    public A( ) {  };
    //getter-setter for B b
}

Class B {
    private A a;
    public B( ) {  };
    //getter-setter for A a
}

Giải pháp 2 ->

Class A {
    private final B b; // must initialize in ctor/instance block
    public A(@Lazy B b) { this.b = b };
}

Class B {
    private final A a; // must initialize in ctor/instance block
    public B(A a) { this.a = a };
}

12

Nó chỉ làm điều đó. Nó khởi tạo abvà đưa từng cái vào cái kia (sử dụng các phương thức setter của chúng).

Vấn đề là gì?


9
@javaguy: Không, sẽ không.
skaffman

@skaffman chỉ cách với sau khi thuộc tínhSet sử dụng phương thức thích hợp?
gstackoverflow

6

Từ tham khảo mùa xuân :

Nhìn chung, bạn có thể tin tưởng Spring sẽ làm điều đúng đắn. Nó phát hiện các vấn đề về cấu hình, chẳng hạn như các tham chiếu đến các bean không tồn tại và các phụ thuộc vòng tròn, tại thời gian tải của vùng chứa. Spring thiết lập các thuộc tính và giải quyết các phụ thuộc càng muộn càng tốt, khi bean thực sự được tạo.


6

Vùng chứa Spring có thể giải quyết các phụ thuộc vòng tròn dựa trên Setter nhưng đưa ra một ngoại lệ thời gian chạy BeanCurrentlyInCreationException trong trường hợp phụ thuộc vòng tròn dựa trên Constructor. Trong trường hợp phụ thuộc vòng tròn dựa trên Setter, vùng chứa IOC xử lý nó khác với một tình huống điển hình trong đó nó sẽ cấu hình đầy đủ bean cộng tác trước khi đưa nó vào. Ví dụ: nếu Bean A có phụ thuộc vào Bean B và Bean B trên Bean C, vùng chứa khởi tạo đầy đủ C trước khi tiêm nó vào B và khi B được khởi tạo hoàn toàn, nó sẽ được đưa vào A. Nhưng trong trường hợp phụ thuộc vòng tròn, một của hạt đậu được tiêm cho hạt đậu kia trước khi nó được khởi chạy hoàn toàn.


5

Giả sử A phụ thuộc vào B, thì trước tiên Spring sẽ khởi tạo A, sau đó là B, sau đó đặt thuộc tính cho B, sau đó đặt B thành A.

Nhưng nếu B cũng phụ thuộc vào A thì sao?

Sự hiểu biết của tôi là: Spring chỉ thấy rằng A đã được xây dựng (phương thức khởi tạo được thực thi), nhưng chưa được khởi tạo hoàn toàn (không phải tất cả các lần tiêm đều được thực hiện), tốt, nó nghĩ, không sao cả, có thể chấp nhận được rằng A chưa được khởi tạo hoàn toàn, chỉ cần đặt điều này không- các cá thể A được khởi tạo đầy đủ thành B ngay bây giờ. Sau khi B được khởi tạo hoàn toàn, nó được đặt thành A, và cuối cùng, A đã được khởi tạo hoàn toàn ngay bây giờ.

Nói cách khác, nó chỉ phơi bày trước A với B.

Đối với các phụ thuộc thông qua phương thức khởi dựng, Sprint chỉ cần ném BeanCurrentlyInCreationException, để giải quyết ngoại lệ này, hãy đặt lazy-init thành true cho bean phụ thuộc vào những người khác thông qua cách khởi tạo-arg.


đơn giản và là một trong những lời giải thích tốt nhất.
Sritam Jagadev,

5

Nó được giải thích rõ ràng ở đây . Cảm ơn Eugen Paraschiv.

Sự phụ thuộc tròn là một mùi thiết kế, hãy sửa nó hoặc sử dụng @Lazy cho phần phụ thuộc gây ra sự cố để giải quyết nó.



3

Constructor Injection không thành công khi có Sự phụ thuộc vòng giữa các hạt đậu lò xo. Vì vậy, trong trường hợp này, chúng tôi chèn Setter giúp giải quyết vấn đề.

Về cơ bản, Constructor Injection hữu ích cho các phụ thuộc Bắt buộc, đối với các phụ thuộc tùy chọn tốt hơn nên sử dụng Setter injection vì chúng ta có thể thực hiện tiêm lại.


0

Nếu hai bean phụ thuộc vào nhau thì chúng ta không nên sử dụng Constructor injection trong cả hai định nghĩa bean. Thay vào đó, chúng ta phải sử dụng tiêm setter vào bất kỳ hạt đậu nào. (tất nhiên chúng ta có thể sử dụng setter injection n cả hai định nghĩa bean, nhưng phương thức chèn constructor trong cả ném 'BeanCurrentlyInCreationException'

Tham khảo tài liệu Spring tại " https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#resources-resource "

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.