Dependência circular na primavera


Respostas:


42

Como as outras respostas disseram, o Spring apenas cuida disso, criando os beans e injetando-os conforme necessário.

Uma das consequências é que a injeção de bean / configuração de propriedade pode ocorrer em uma ordem diferente daquela que seus arquivos de conexão XML parecem implicar. Portanto, você precisa ter cuidado para que seus configuradores de propriedade não façam a inicialização que depende de outros configuradores já chamados. A maneira de lidar com isso é declarar os beans como implementadores da InitializingBeaninterface. Isso requer que você implemente o afterPropertiesSet()método, e é aqui que você faz a inicialização crítica. (Também incluo o código para verificar se as propriedades importantes foram realmente definidas.)


76

O manual de referência do Spring explica como as dependências circulares são resolvidas. Os grãos são instanciados primeiro e, em seguida, injetados uns nos outros.

Considere esta classe:

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

}

E uma classe semelhante 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;
    }

}

Se você tinha este arquivo de configuração:

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

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

Você veria a seguinte saída ao criar um contexto usando esta configuração:

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

Observe que quando aé injetado no b, aainda não foi totalmente inicializado.


26
É por isso que o Spring requer um construtor sem argumentos ;-)
Chris Thompson

15
Não se você usar argumentos de construtor em suas definições de bean! (Mas, nesse caso, você não pode ter uma dependência circular.)
Richard Fearn

1
@Richard Fearn Sua postagem é sobre a explicação do problema em vez de fornecer a solução?
gstackoverflow

4
Se você tentar usar injeção de construtor, a mensagem de erro será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

Na base de código com a qual estou trabalhando (mais de 1 milhão de linhas de código), tivemos um problema com tempos de inicialização longos, em torno de 60 segundos. Estávamos recebendo mais de 12.000 FactoryBeanNotInitializedException .

O que fiz foi definir um ponto de interrupção condicional em 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;
}

onde isso acontece destroySingleton(beanName), imprimi a exceção com o código de ponto de interrupção condicional:

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

Aparentemente, isso acontece quando os FactoryBean s estão envolvidos em um gráfico de dependência cíclico. Resolvemos isso implementando ApplicationContextAware e InitializingBean e injetando manualmente os beans.

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

Isso reduziu o tempo de inicialização para cerca de 15 segundos.

Portanto, nem sempre presuma que a primavera pode ser boa para resolver essas referências para você.

Por esse motivo, eu recomendo desativar a resolução de dependência cíclica com AbstractRefreshableApplicationContext # setAllowCircularReferences (false) para evitar muitos problemas futuros.


3
Recomendação interessante. Minha contra recomendação seria apenas fazer isso se você suspeitar que referências circulares estão causando um problema de desempenho. (Seria uma pena quebrar algo que não precisava ser quebrado tentando consertar um problema que não precisava ser consertado.)
Stephen C

2
É uma ladeira escorregadia para o inferno da manutenção para permitir dependências circulares, redesenhar sua arquitetura a partir de dependências circulares pode ser realmente complicado, como foi em nosso caso. O que significava aproximadamente para nós é que obtivemos o dobro de conexões de banco de dados durante a inicialização do que a sessionfactory estava envolvida na dependência circular. Em outros cenários, coisas muito mais desastrosas poderiam ter acontecido devido ao bean ter sido instanciado mais de 12.000 vezes. Claro que você deve escrever seus grãos para que eles possam destruí-los, mas por que permitir esse comportamento em primeiro lugar?
jontejj

@jontejj, você merece um cookie
serprime

14

Problema ->

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

// Causado por: org.springframework.beans.factory.BeanCurrentlyInCreationException: Erro ao criar bean com o nome 'A': O bean solicitado está atualmente em criação: Existe uma referência circular não resolvível?

Solução 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
}

Solução 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

Simplesmente faz isso. Ele instancia ae b, e injeta um no outro (usando seus métodos setter).

Qual é o problema?


9
@javaguy: Não, não vai.
Skaffman,

@skaffman só forma com o uso do método after propertiesSet adequado?
gstackoverflow

6

Da Referência Spring :

Em geral, você pode confiar que o Spring fará a coisa certa. Ele detecta problemas de configuração, como referências a beans inexistentes e dependências circulares, no tempo de carregamento do contêiner. Spring define propriedades e resolve dependências o mais tarde possível, quando o bean é realmente criado.


6

O contêiner Spring é capaz de resolver dependências circulares baseadas em Setter, mas oferece uma exceção de tempo de execução BeanCurrentlyInCreationException no caso de dependências circulares baseadas em Construtor. No caso de dependência circular baseada em Setter, o contêiner IOC o trata de maneira diferente de um cenário típico em que configuraria totalmente o bean de colaboração antes de injetá-lo. Por exemplo, se o Bean A tem uma dependência do Bean B e o Bean B do Bean C, o contêiner inicializa totalmente C antes de injetá-lo em B e uma vez que B é totalmente inicializado, ele é injetado em A. Mas no caso de dependência circular, um dos beans é injetado no outro antes de ser totalmente inicializado.


5

Digamos que A dependa de B, então Spring irá primeiro instanciar A, então B, então definir propriedades para B e então definir B para A.

Mas e se B também depender de A?

Meu entendimento é: Spring acabou de descobrir que A foi construído (construtor executado), mas não totalmente inicializado (nem todas as injeções feitas), bem, ele pensou, está tudo bem, é tolerável que A não esteja totalmente inicializado, basta definir isso não- instâncias A totalmente inicializadas em B por enquanto. Depois que B foi totalmente inicializado, ele foi definido como A e, finalmente, A foi totalmente iniciado agora.

Em outras palavras, ele apenas expõe A a B com antecedência.

Para dependências via construtor, Sprint apenas lança BeanCurrentlyInCreationException, para resolver esta exceção, defina lazy-init como true para o bean que depende de outros via construtor-arg.


simples e uma das melhores explicações.
Sritam Jagadev

5

Está claramente explicado aqui . Obrigado a Eugen Paraschiv.

Dependência circular é um cheiro de design, corrija-o ou use @Lazy para a dependência que causa problemas para contorná-la.



3

A injeção de construtor falha quando há dependência circular entre grãos de primavera. Portanto, neste caso, a injeção de Setter ajuda a resolver o problema.

Basicamente, a injeção de construtor é útil para dependências obrigatórias, para dependências opcionais é melhor usar a injeção de Setter porque podemos fazer a reinjeção.


0

Se dois beans são dependentes um do outro, não devemos usar injeção de Construtor em ambas as definições de bean. Em vez disso, temos que usar injeção setter em qualquer um dos beans. (é claro que podemos usar injeção de setter em ambas as definições de bean, mas injeções de construtor em ambas jogam 'BeanCurrentlyInCreationException'

Consulte o documento Spring em " https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#resources-resource "

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.