İlkbaharda döngüsel bağımlılık


Yanıtlar:


42

Diğer cevapların da söylediği gibi, Spring sadece ilgilenir, fasulyeleri oluşturur ve gerektiği gibi enjekte eder.

Sonuçlardan biri, fasulye enjeksiyonu / özellik ayarının XML kablolama dosyalarınızın ima ettiğinden farklı bir sırada gerçekleşebilmesidir. Bu nedenle, mülk belirleyicilerinizin zaten çağrılmış olan diğer ayarlayıcılara dayanan başlatma yapmamasına dikkat etmeniz gerekir. Bununla başa çıkmanın yolu, fasulyeleri InitializingBeanarayüzü uyguluyor olarak ilan etmektir . Bu, afterPropertiesSet()yöntemi uygulamanızı gerektirir ve kritik başlatmayı burada yaptığınız yer burasıdır. (Ayrıca önemli özelliklerin gerçekten ayarlandığını kontrol etmek için kod ekliyorum.)


76

Bahar referans kılavuzu dairesel bağımlılıkları çözülür açıklar. Çekirdekler önce örneklenir, ardından birbirlerine enjekte edilir.

Bu sınıfı düşünün:

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;
    }

}

Ve benzer bir sınıf 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;
    }

}

Daha sonra bu yapılandırma dosyasına sahipseniz:

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

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

Bu yapılandırmayı kullanarak bir bağlam oluştururken aşağıdaki çıktıyı görürsünüz:

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

Ne zaman geldiğini hatırlatırız aenjekte edilir b, ahenüz tam olarak başlatılmadı.


26
Spring'in argümansız bir kurucuya ihtiyaç duymasının nedeni budur ;-)
Chris Thompson

15
Bean tanımlarınızda yapıcı argümanları kullanırsanız hayır! (Ama bu durumda döngüsel bir bağımlılığa sahip olamazsınız.)
Richard Fearn

1
@Richard Fearn Gönderiniz çözüm sağlamaktan çok sorunun açıklamasıyla ilgili mi?
gstackoverflow

4
Yapıcı enjeksiyonu kullanmaya çalışırsanız, hata mesajı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

Üzerinde çalıştığım kod tabanında (1 milyondan fazla kod satırı) 60 saniye gibi uzun başlatma süreleriyle ilgili bir sorun yaşadık. 12000+ FactoryBeanNotInitializedException alıyorduk .

Yaptığım şey, AbstractBeanFactory # doGetBean'da koşullu bir kesme noktası belirlemekti

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;
}

burada destroySingleton(beanName)istisnayı koşullu kesme noktası koduyla yazdırdım:

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

Görünüşe göre bu, FactoryBean ler döngüsel bir bağımlılık grafiğine dahil . ApplicationContextAware ve InitializingBean uygulayarak ve fasulyeleri manuel olarak enjekte ederek sorunu çözdük .

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();
    }
}

Bu, başlangıç ​​süresini yaklaşık 15 saniyeye düşürdü.

Bu nedenle, her zaman baharın bu referansları sizin için çözmede iyi olabileceğini düşünmeyin.

Bu nedenle, gelecekteki birçok sorunu önlemek için AbstractRefreshableApplicationContext # setAllowCircularReferences (false) ile döngüsel bağımlılık çözümlemesini devre dışı bırakmanızı öneririm .


3
İlginç bir tavsiye. Karşı tavsiyem, yalnızca döngüsel referansların bir performans sorununa neden olduğundan şüpheleniyorsanız bunu yapmak olacaktır . (Düzeltilmesi gerekmeyen bir sorunu çözmeye çalışarak kırılması gerekmeyen bir şeyi kırmak utanç verici olurdu.)
Stephen C

2
Dairesel bağımlılıklara izin vermek için bakım cehennemine doğru kaygan bir eğim var, mimarinizi döngüsel bağımlılıklardan yeniden tasarlamak, bizim durumumuzda olduğu gibi gerçekten zor olabilir. Bizim için kabaca anlamı, başlangıç ​​sırasında oturum fabrikasının döngüsel bağımlılığa dahil olmasına göre iki kat daha fazla veritabanı bağlantısına sahip olmamızdı. Diğer senaryolarda, fasulyenin 12000'den fazla kez somutlaştırılması nedeniyle çok daha feci şeyler meydana gelebilirdi. Elbette, fasulyelerinizi onları yok etmeyi destekleyecek şekilde yazmalısınız, ama neden en başta bu davranışa izin veresiniz?
jontejj

@jontejj, bir kurabiyeyi hak ediyorsun
serprime

14

Sorun ->

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 };
 }

// Sebep: org.springframework.beans.factory.BeanCurrentlyInCreationException: 'A' adında fasulye oluşturulurken hata: İstenen fasulye şu anda oluşturuluyor: Çözümlenemeyen bir döngüsel başvuru var mı?

1.Çözüm ->

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

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

2.Çözüm ->

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

Sadece yapıyor. Somutlaştırır aveb , ve (kendilerine ait ayarlayıcı yöntemleri kullanılarak) diğer içine her biri enjekte eder.

Sorun ne?


9
@javaguy: Hayır, olmayacak.
skaffman

@skaffman yalnızca özelliklerden sonra yöntem kullanımı uygun şekilde ayarla?
gstackoverflow

6

Gönderen Bahar Referans :

Doğru şeyi yapması için genellikle Spring'e güvenebilirsiniz. Var olmayan çekirdeklere ve döngüsel bağımlılıklara referanslar gibi yapılandırma sorunlarını konteyner yükleme zamanında algılar. Fasulye gerçekten oluşturulduğunda, Spring özellikleri ayarlar ve bağımlılıkları olabildiğince geç çözer.


6

Spring kapsayıcı, Setter tabanlı döngüsel bağımlılıkları çözebilir, ancak Oluşturucu tabanlı döngüsel bağımlılıklar durumunda BeanCurrentlyInCreationException bir çalışma zamanı istisnası verir. Setter tabanlı döngüsel bağımlılık durumunda, IOC konteyneri, onu enjekte etmeden önce işbirliği yapan çekirdeği tamamen yapılandıracağı tipik bir senaryodan farklı şekilde ele alır. Örneğin, Bean A, Bean B'ye ve Bean B'ye Bean C'ye bağımlıysa, konteyner C'yi B'ye enjekte etmeden önce tamamen başlatır ve B tamamen başlatıldığında A'ya enjekte edilir. Ancak döngüsel bağımlılık durumunda, bir çekirdeklerden% 100'ü diğerine tam olarak başlatılmadan önce enjekte edilir.


5

A'nın B'ye bağlı olduğunu varsayalım, o zaman Bahar önce A'yı, sonra B'yi, sonra B için özellikleri ayarlayacak, sonra B'yi A'ya ayarlayacaktır.

Peki ya B de A'ya bağlıysa?

Anladığım kadarıyla: Spring, A'nın inşa edildiğini (kurucu yürüttüğünü), ancak tam olarak başlatılmadığını (tüm enjeksiyonlar yapılmadı) buldu, iyi, sorun değil, A'nın tam olarak başlatılmaması tolere edilebilir, sadece şunu ayarla- Şimdilik tamamen başlatılmış A örnekleri B'de. B tamamen başlatıldıktan sonra, A'ya ayarlandı ve son olarak, A şimdi tamamen başlatıldı.

Başka bir deyişle, önceden A'yı B'ye maruz bırakır.

Yapıcı aracılığıyla bağımlılıklar için, Sprint sadece BeanCurrentlyInCreationException'ı atar, bu istisnayı çözmek için, yapıcı-arg yolu aracılığıyla başkalarına bağımlı olan çekirdek için lazy-init'i true olarak ayarlayın.


basit ve en iyi açıklamalardan biri.
Sritam Jagadev

5

Onun açıkça izah burada . Eugen Paraschiv'e teşekkürler.

Döngüsel bağımlılık bir tasarım kokusudur, ya düzeltin ya da bağımlılık için @ Tembel kullanın, bu da sorunun geçici olarak çözülmesine neden olur.



3

Bahar çekirdekleri arasında Dairesel Bağımlılık olduğunda Yapıcı Enjeksiyonu başarısız olur. Bu durumda biz Setter enjeksiyonu sorunu çözmeye yardımcı oluyoruz.

Temel olarak, Constructor Injection, zorunlu bağımlılıklar için kullanışlıdır, isteğe bağlı bağımlılıklar için Setter enjeksiyonunu kullanmak daha iyidir çünkü yeniden enjeksiyon yapabiliriz.


0

İki fasulye birbirine bağımlıysa, her iki fasulye tanımında da Constructor enjeksiyonunu kullanmamalıyız. Bunun yerine, herhangi bir fasulyede setter enjeksiyonu kullanmalıyız. (elbette bean tanımlarının her ikisinde de setter injection kullanabiliriz, ancak her ikisinde de yapıcı enjeksiyonları 'BeanCurrentlyInCreationException' atar.

" Https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#resources-resource " adresindeki Bahar belgesine bakın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.