[Spring] Las 3 formas de Spring para resolver dependencias circulares

Inserte la descripción de la imagen aquí

Original: http://www.javastack.cn/article/2020/spring-cycle-depends-on-three-ways/

La dependencia circular es una referencia anidada circular en clases N. Si esta dependencia circular ocurre en la forma de nuevos objetos en el desarrollo diario, el programa se llamará repetidamente en tiempo de ejecución hasta que la memoria se desborde y se informe un error.

Permítanme hablar sobre cómo Spring resuelve las dependencias circulares.

El primer tipo: dependencia cíclica de los parámetros del constructor

El contenedor Spring colocará cada identificador de Bean que se cree en un "grupo de Bean creado actualmente", y el identificador de Bean permanecerá en este grupo durante el proceso de creación.

Por lo tanto, si encuentra que ya está en el "grupo de Bean creado actualmente" durante el proceso de creación de un Bean, se lanzará una excepción BeanCurrentlyInCreationException para indicar una dependencia circular; y el Bean creado se eliminará del "Grupo de Bean actualmente creado".

Primero, inicializamos tres Beans.

public class StudentA {

    private StudentB studentB ;

    public void setStudentB(StudentB studentB) {
        this.studentB = studentB;
    }

    public StudentA() {
    }

    public StudentA(StudentB studentB) {
        this.studentB = studentB;
    }
}
public class StudentB {

    private StudentC studentC ;

    public void setStudentC(StudentC studentC) {
        this.studentC = studentC;
    }

    public StudentB() {
    }

    public StudentB(StudentC studentC) {
        this.studentC = studentC;
    }
}
public class StudentC {

    private StudentA studentA ;

    public void setStudentA(StudentA studentA) {
        this.studentA = studentA;
    }

    public StudentC() {
    }

    public StudentC(StudentA studentA) {
        this.studentA = studentA;
    }
}

De acuerdo, las anteriores son tres clases muy básicas, y la estructura de parámetros de StudentA es StudentB. La estructura parametrizada de StudentB es StudentC, y la estructura parametrizada de StudentC es StudentA, que crea una dependencia circular.

Todos entregamos estos tres Beans a Spring para que los administre y los instanciamos con una construcción parametrizada.

<bean id="a" class="com.zfx.student.StudentA">
  <constructor-arg index="0" ref="b"></constructor-arg>
</bean>
<bean id="b" class="com.zfx.student.StudentB">
  <constructor-arg index="0" ref="c"></constructor-arg>
</bean>
<bean id="c" class="com.zfx.student.StudentC">
  <constructor-arg index="0" ref="a"></constructor-arg>
</bean>

La siguiente es la clase de prueba:

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("com/zfx/student/applicationContext.xml");
        //System.out.println(context.getBean("a", StudentA.class));
    }
}

El mensaje de error del resultado de la ejecución es:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
  Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

Si comprende la oración inicial, este error no debería sorprenderle. El contenedor Spring primero crea un StudentA singleton, y StudentA depende de StudentB, y luego coloca A en el "grupo de Bean creado actualmente".

StudentB se crea en este momento, StudentB depende de StudentC, y luego B se coloca en el "grupo de Bean actualmente creado", StudentC se crea en este momento y StudentC depende de StudentA.

Sin embargo, en este momento, el estudiante ya está en el grupo, por lo que se informará un error, porque los beans en el grupo no están inicializados, por lo que dependerán del error y el Bean inicializado se eliminará del grupo.

El segundo tipo: modo setter singleton, modo predeterminado

Si desea hablar sobre la inyección de setter, será mejor que miremos una imagen de la instanciación de Bean en Spring

Inserte la descripción de la imagen aquí

Como se muestra en los dos primeros pasos de la figura: Spring primero crea una instancia del objeto Bean y luego establece las propiedades del objeto. ¿Por qué el bean en Spring es un singleton predeterminado? Le sugiero que eche un vistazo a este artículo.

Preste atención a la cuenta pública de WeChat: pila de tecnología Java, y responda en segundo plano: primavera, puede obtener los últimos N tutoriales de Spring que he compilado, todos los cuales son productos secos.

Modificar el archivo de configuración para la inyección de conjuntos

<!--scope="singleton"(默认就是单例方式) -->
<bean id="a" class="com.zfx.student.StudentA" scope="singleton">
  <property name="studentB" ref="b"></property>
</bean>
<bean id="b" class="com.zfx.student.StudentB" scope="singleton">
  <property name="studentC" ref="c"></property>
</bean>
<bean id="c" class="com.zfx.student.StudentC" scope="singleton">
  <property name="studentA" ref="a"></property>
</bean>

La siguiente es la clase de prueba:

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("com/zfx/student/applicationContext.xml");
        System.out.println(context.getBean("a", StudentA.class));
    }
}

El resultado de la impresión es:

com.zfx.student.StudentA@1fbfd6

¿Por qué no hay ningún error al utilizar el método set?

Combinando la imagen anterior, Spring primero instancia el objeto Bean con la construcción. En este momento, Spring colocará el objeto que ha sido instanciado en un Mapa, y Spring proporciona un método para obtener la referencia de este objeto instanciado sin atributos establecidos. .

Según nuestro ejemplo, cuando Spring crea una instancia de StudentA, StudentB y StudentC, establecerá las propiedades del objeto de inmediato. En este momento, StudentA depende de StudentB e irá al mapa para eliminar el objeto StudentB singleton que existe en él. Por analogía, no habrá problema de bucle.

El siguiente es el método de implementación en el código fuente de Spring. El siguiente código fuente está en la clase DefaultSingletonBeanRegistry.java en el paquete Spring's Bean

/** Cache of singleton objects: bean name --> bean instance(缓存单例实例化对象的Map集合) */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);

/** Cache of singleton factories: bean name --> ObjectFactory(单例的工厂Bean缓存集合) */
private final Map<String, ObjectFactory> singletonFactories = new HashMap<String, ObjectFactory>(16);

/** Cache of early singleton objects: bean name --> bean instance(早期的单身对象缓存集合) */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

/** Set of registered singletons, containing the bean names in registration order(单例的实例化对象名称集合) */
private final Set<String> registeredSingletons = new LinkedHashSet<String>(64);
/**
 * 添加单例实例
 * 解决循环引用的问题
 * Add the given singleton factory for building the specified singleton
 * if necessary.
 * <p>To be called for eager registration of singletons, e.g. to be able to
 * resolve circular references.
 * @param beanName the name of the bean
 * @param singletonFactory the factory for the singleton object
 */
protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) {
  Assert.notNull(singletonFactory, "Singleton factory must not be null");
  synchronized (this.singletonObjects) {
    if (!this.singletonObjects.containsKey(beanName)) {
      this.singletonFactories.put(beanName, singletonFactory);
      this.earlySingletonObjects.remove(beanName);
      this.registeredSingletons.add(beanName);
    }
  }
}

El tercer tipo: prototipo de modo setter, prototipo

Modifique el archivo de configuración como:

<bean id="a" class="com.zfx.student.StudentA" scope="prototype">
  <property name="studentB" ref="b"></property>
</bean>
<bean id="b" class="com.zfx.student.StudentB" scope="prototype">
  <property name="studentC" ref="c"></property>
</bean>
<bean id="c" class="com.zfx.student.StudentC" scope="prototype">
  <property name="studentA" ref="a"></property>
</bean>

alcance = "prototipo" significa que cada solicitud creará un objeto de instancia.

La diferencia entre los dos es: los beans con estado usan un alcance de prototipo y los beans sin estado generalmente usan un alcance de singleton.

Caso de prueba:

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("com/zfx/student/applicationContext.xml");
        //此时必须要获取Spring管理的实例,因为现在scope="prototype" 只有请求获取的时候才会实例化对象
        System.out.println(context.getBean("a", StudentA.class));
    }
}

Resultado de la impresión:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
    Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

¿Por qué el modo de prototipo informó un error?
Para los beans en el alcance "prototype", el contenedor Spring no puede completar la inyección de dependencia, porque los beans en el alcance "prototype" no son almacenados en caché por el contenedor Spring, por lo que un bean creado no puede exponerse por adelantado.

Supongo que te gusta

Origin blog.csdn.net/qq_21383435/article/details/108491324
Recomendado
Clasificación