[Pregunta de la entrevista] Finalmente, alguien puede aclarar la dependencia circular de Spring

Tabla de contenido

¿Qué es la dependencia circular?

Ciclo de vida del frijol

Caché de nivel 3

Análisis de la idea de resolver dependencia circular

¿Qué tipo de dependencia circular resuelve Spring?

para resumir


¿Qué es la dependencia circular?

Simplemente, el objeto A depende del objeto B y el objeto B depende del objeto A.

como:

// A依赖了B
class A{
    public B b;
}

// B依赖了A
class B{
    public A a;
}

Entonces, ¿la dependencia circular es un problema?

Si no considera Spring, las dependencias circulares no son un problema , porque es normal que los objetos dependan unos de otros.

como

A a = new A();
B b = new B();

a.b = b;
b.a = a;

De esta forma, A y B dependen de él.

Sin embargo, las dependencias circulares son un problema en Spring. ¿Por qué?

Porque, en Spring, un objeto no es simplemente nuevo, sino que pasará por una serie de ciclos de vida de Bean, es debido al ciclo de vida de Bean que aparecerá el problema de dependencia circular . Por supuesto, en Spring, hay muchos escenarios donde hay dependencias circulares, en algunos escenarios Spring los resuelve automáticamente por nosotros, y algunos escenarios requieren que los programadores los resuelvan, como se describe en detalle a continuación.

Para comprender la dependencia circular en Spring, primero debe comprender el ciclo de vida del Bean en Spring.

 

Ciclo de vida del frijol

Aquí no habrá una descripción detallada del ciclo de vida del Bean, solo describa el proceso general.

El ciclo de vida de Bean se refiere a: ¿Cómo se genera Bean en Spring?

El objeto administrado por Spring se llama Bean . Los pasos de generación de frijoles son los siguientes:

class -> BeanDefinition -> nuevo objeto (objeto original) -> relleno de propiedad -> inicialización -> bean join singleton pool

  1. Spring escanea la clase para obtener BeanDefinition
  2. Generar frijoles según la definición de frijol obtenida
  3. Primero infiera el método de construcción según la clase
  4. Según el método de construcción inferido, reflexión, obtener un objeto (llamado temporalmente el objeto original)
  5. Complete las propiedades en el objeto original (inyección de dependencia)
  6. Si un método en el objeto original es AOP, entonces necesita generar un objeto proxy basado en el objeto original
  7. Coloque el objeto proxy generado finalmente en el grupo de singleton (llamado singletonObjects en el código fuente), y consígalo directamente del grupo de singleton la próxima vez que obtengaBean

Se puede ver que todavía hay muchos pasos para el proceso de generación de Bean en Spring, y no solo los 7 pasos anteriores, sino también muchos, como la devolución de llamada Aware, la inicialización, etc., que no se tratan en detalle aquí.

Se puede encontrar que en Spring, la construcción de un Bean incluye el nuevo paso (el cuarto paso de la construcción de la reflexión del método).

Después de obtener un objeto primitivo , Spring necesita realizar una inyección de dependencia en las propiedades del objeto , entonces, ¿cuál es el proceso de inyección?

Por ejemplo, en la clase A mencionada anteriormente, hay un atributo b de la clase B en la clase A. Por lo tanto, cuando la clase A genera un objeto primitivo, asignará un valor al atributo b, que se basará en el tipo y El nombre del atributo va a BeanFactory para obtener el bean singleton correspondiente a la clase B. Si hay un Bean correspondiente a B en BeanFactory en este momento, entonces asígnelo directamente al atributo b; si no hay ningún Bean correspondiente a B en BeanFactory en este momento, se debe generar un Bean correspondiente a B y luego asignarlo al atributo b.

El problema surge en el segundo caso: si el Bean correspondiente de clase B no se ha generado en BeanFactory en este momento, entonces es necesario generarlo y pasará por el ciclo de vida del Bean B.

Luego, en el proceso de creación de un Bean de clase B, si hay un atributo de clase A en la clase B, entonces se requiere el correspondiente Bean de clase A en el proceso de creación de un Bean de B, pero las condiciones que desencadenan la creación de un Bean de clase B Es la inyección de dependencia de Type A Bean durante el proceso de creación, por lo que aquí hay una dependencia circular:

Creación de ABean -> depende del atributo B -> desencadena la creación de BBean ---> B depende del atributo A ---> Se requiere ABean (pero ABean aún está en proceso de creación)

Como resultado, no se puede crear ABean y tampoco se puede crear BBean.

Este es un escenario de dependencia cíclica, pero como se mencionó anteriormente, en Spring, algunos mecanismos ayudan a los desarrolladores a resolver el problema de la dependencia cíclica parcial Este mecanismo es el caché de tres niveles .

 

Caché de nivel 3

La caché de tres niveles es un término general.

La caché de primer nivel es: singletonObjects

La caché secundaria es: earlySingletonObjects

La caché de tres niveles es : singletonFactories

Permítanme explicar brevemente el papel de estos tres cachés y analizarlos en detalle más adelante:

  • SingletonObjects ->  objetos bean en caché que han pasado por un ciclo de vida completo.
  • earlySingletonObjects ->  uno más temprano que singletonObjects, lo que indica que los objetos bean en caché son primeros. ¿Qué significa temprano? Indica que el ciclo de vida del Bean no se ha completado antes de que el Bean se coloque en earlySingletonObjects.
  • SingletonFactories ->  almacenado en caché en ObjectFactory, representa una fábrica de objetos, que se utiliza para crear un objeto.

 

Análisis de la idea de resolver dependencia circular

Primero analicemos por qué la caché puede resolver la dependencia circular.

Según el análisis anterior, la principal razón del problema de la dependencia circular es:

Cuando se crea A ---> necesita B ---> B para crear ---> necesita A, creando así un bucle

image.png

Entonces, ¿cómo romper este ciclo y agregar un intermediario (caché)?

image.png

En el proceso de creación del Bean de A, antes de la inyección de dependencia, el Bean original de A se coloca en la caché (exponer antes, siempre que se coloque en la caché, se pueden tomar otros Beans de la caché cuando sea necesario), y luego se coloca en la caché , Y luego realizar la inyección de dependencia. En este momento, el Bean de A depende del Bean de B. Si el Bean de B no existe, necesita crear el Bean de B. El proceso de creación de Bean de B es el mismo que el de A. También crea un objeto primitivo de B. , Y luego exponer el objeto original de B en la caché antes, y luego realizar la inyección de dependencia A en el objeto original de B. En este momento, el objeto original de A se puede obtener de la caché (aunque es el objeto original de A, no es el final Bean), después de inyectar la dependencia del objeto original de B, el ciclo de vida de B termina, y luego el ciclo de vida de A también puede terminar.

Debido a que solo hay un objeto original de A en todo el proceso, para B, incluso si el objeto original de A se inyecta durante la inyección de propiedad, no importa, porque el objeto original de A no ocurre en el montón en el ciclo de vida subsiguiente. Cambiar ( paso de Java por referencia ).

Esto puede extraerse del análisis anterior, sólo una caché puede resolver dependencias circulares, así que por qué también se requiere la primavera singletonFactories it ?

Este es un punto difícil. Basado en el escenario anterior, piense en una pregunta: si el objeto original de A se inyecta en el atributo de B, el objeto original de A se somete a AOP para generar un objeto proxy , y luego aparecerá. Para A, su El objeto Bean debería ser en realidad un objeto proxy después de AOP, y el atributo a de B no corresponde a un objeto proxy después de AOP, lo que crea un conflicto.

 

La A de la que depende B y la A final no son el mismo objeto .

Entonces, ¿cómo solucionar este problema? Se puede decir que no hay solución a este problema.

Porque al final del ciclo de vida de un Bean, Spring proporciona  BeanPostProcessor  para procesar el Bean. Este procesamiento no solo puede modificar el valor de atributo del Bean, sino también reemplazar el Bean actual .

por ejemplo:

@Component
public class User {
}
@Component
public class LubanBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        // 注意这里,生成了一个新的User对象
        if (beanName.equals("user")) {
            System.out.println(bean);
            User user = new User();
            return user;
        }

        return bean;
    }
}


public class Test {
    public static void main(String[] args) {

        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(AppConfig.class);
        
        User user = context.getBean("user", User.class);
        System.out.println(user);

    }
}

Ejecute el método principal, y la impresión obtenida es la siguiente:

com.luban.service.User@5e025e70 
com.luban.service.User@1b0375b3

Por lo tanto, el objeto bean correspondiente a un determinado beanName se puede reemplazar completamente en BeanPostProcessor.

BeanPostProcessor realizado en el ciclo de vida de Bean está en después de inyectar la propiedad de , la dependencia cíclica ocurrió en el proceso de inyección de propiedades , es probable que cause, inyecte un objeto A al objeto B y después de experimentar el ciclo de vida completo A Objeto, no objeto . Esto es problemático.

Por lo tanto, la dependencia circular en este caso no puede ser resuelto por la primavera, debido a que durante la inyección de la propiedad, la primavera no sabe qué BeanPostProcessor el objeto A pasará a través y qué hacer con el objeto A .

 

¿Qué tipo de dependencia circular resuelve Spring?

Aunque la situación anterior puede suceder, debe suceder muy raramente. Normalmente no hacemos esto durante el proceso de desarrollo. Sin embargo, el objeto final correspondiente a un determinado beanName y el objeto original no son el mismo pero aparecen a menudo. Esto es AOP .

AOP se implementa a través de un BeanPostProcessor , este BeanPostProcessor es AnnotationAwareAspectJAutoProxyCreator , su clase principal es AbstractAutoProxyCreator, y en Spring AOP usa un proxy dinámico JDK o un proxy dinámico CGLib , así que si le da un método en una clase Establezca el aspecto, luego esta clase eventualmente necesita generar un objeto proxy.

El proceso general es: Clase A ---> Generar un objeto común -> Inyección de propiedad -> Generar un objeto proxy basado en el aspecto -> Colocar el objeto proxy en el grupo singletonObjects singleton .

Y se puede decir que AOP es otra función importante de Spring además de IOC, y la dependencia circular pertenece a la categoría de IOC, por lo que si las dos funciones principales quieren coexistir, Spring necesita un tratamiento especial.

La forma de solucionarlo es utilizar la caché de tercer nivel singletonFactories .

En primer lugar, singletonFactories almacena el ObjectFactory correspondiente a un determinado beanName . En el ciclo de vida del bean, después de que se genera el objeto original, se construirá un ObjectFactory y se almacenará en singletonFactories . Esta ObjectFactory es una interfaz funcional , por lo que admite expresiones Lambda: () -> getEarlyBeanReference ( beanName , mbd , bean )

La expresión Lambda anterior es una ObjectFactory. La ejecución de la expresión Lambda ejecutará el método getEarlyBeanReference, que es el siguiente:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

Este método ejecutará el método getEarlyBeanReference en SmartInstantiationAwareBeanPostProcessor, y solo dos de las clases de implementación bajo esta interfaz implementan este método, una es AbstractAutoProxyCreator y la otra es InstantiationAwareBeanPostProcessorAdapter. Su implementación es la siguiente:

// InstantiationAwareBeanPostProcessorAdapter
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
    return bean;
}


// AbstractAutoProxyCreator
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    return wrapIfNecessary(bean, beanName, cacheKey);
}

Entonces, obviamente, en toda la primavera, solo AbstractAutoProxyCreator implementa el método getEarlyBeanReference en el verdadero sentido por defecto , y esta clase se usa para AOP . La clase principal de AnnotationAwareAspectJAutoProxyCreator mencionada anteriormente es AbstractAutoProxyCreator.

Entonces, ¿qué está haciendo el método getEarlyBeanReference?

Primero obtenga una clave de caché, la clave de caché es beanName.

Luego almacene beanName y bean (este es el objeto original) en earlyProxyReferences

Llame a wrapIfNesential para AOP y obtenga un objeto proxy.

Entonces, ¿cuándo se llamará al método getEarlyBeanReference? De vuelta al escenario de dependencia cíclica

image.png

Texto a la izquierda :

Esta ObjectFactory es la expresión labmda mencionada anteriormente. Hay un método getEarlyBeanReference en el medio. Tenga en cuenta que las expresiones lambda no se ejecutarán cuando se almacenen en singletonFactories, es decir, el método getEarlyBeanReference no se ejecutará.

Texto a la derecha :

Obtenga un ObjectFactory de singletonFactories según beanName, y luego ejecute ObjectFactory, es decir, ejecute el método getEarlyBeanReference. En este momento, obtendrá un objeto proxy de A primitive object después de AOP, y luego colocará el objeto proxy en earlySingletonObjects. Tenga en cuenta que no hay proxy en este momento Los objetos se colocan en singletonObjects, ¿cuándo se colocarán en singletonObjects?

En este momento, tenemos que entender el papel de earlySingletonObjects. En este momento, solo obtenemos el objeto proxy del objeto original de A. Este objeto no está completo, porque el objeto original de A no se ha llenado con atributos, por lo que no podemos poner directamente el objeto proxy de A en este momento. Colóquelo en singletonObjects, para que solo pueda colocar el objeto proxy en earlySingletonObjects. Suponiendo que otros objetos dependen de A, puede obtener el objeto proxy del objeto original de A de earlySingletonObjects, y es el mismo objeto proxy de A.

Cuando se crea B, A continúa llevando a cabo el ciclo de vida, y después de que A completa la inyección de atributos, realizará AOP de acuerdo con su propia lógica. En este momento, sabemos que el objeto original de A ha pasado por AOP, por lo que para A sí mismo , Ya no irá a AOP, entonces, ¿cómo juzgar si un objeto ha experimentado AOP? Se utilizarán las earlyProxyReferences mencionadas anteriormente. En el método postProcessAfterInitialization de AbstractAutoProxyCreator, se determinará si el beanName actual está en earlyProxyReferences. Si es así, significa que AOP se ha realizado con anticipación y no hay necesidad de realizar AOP nuevamente.

Para A, después del juicio de AOP y la ejecución de BeanPostProcessor, el objeto correspondiente a A debe colocarse en singletonObjects, pero sabemos que el objeto proxy de A debe colocarse en singletonObjects, por lo que en este momento Necesita obtener el objeto proxy de earlySingletonObjects y luego en singletonObjects.

Se resuelve toda la dependencia circular.

 

para resumir

Hasta ahora, resuma el caché de tres niveles:

  1. singletonObjects : almacena en caché los beans correspondientes a un determinado beanName que han pasado el ciclo de vida completo
  2. earlySingletonObjects : almacene en caché el objeto proxy obtenido después de que se realiza AOP en el objeto original por adelantado , el objeto original aún no ha sufrido una inyección de propiedad y ciclos de vida posteriores como BeanPostProcessor
  3. singletonFactories : La caché es una ObjectFactory, que se utiliza principalmente para generar el objeto proxy obtenido después de que el objeto original es AOP . Durante la generación de cada Bean, se expone una fábrica de antemano. Esta fábrica puede o no ser utilizada Si no hay una dependencia circular en el bean, entonces esta fábrica es inútil. El bean se ejecuta de acuerdo con su propio ciclo de vida. Después de la ejecución, el bean se puede poner directamente en singletonObjects. Si hay una dependencia circular que depende del bean, entonces Ese bean ejecuta el envío de ObjectFactory para obtener un objeto proxy después de AOP (si hay AOP, si no se necesita AOP, obtenga un objeto primitivo directamente).
  4. De hecho, también hay un caché, earlyProxyReferences , que se utiliza para registrar si un objeto original se ha sometido a AOP.

Original: https://www.yuque.com/renyong-jmovm/kb/dpzl6u

Luban College-Teacher Zhou Yu

● La optimización de rendimiento de Tomcat8 más sólida de la historia

¿Por qué Alibaba puede resistir 10 mil millones en 90 segundos? - La evolución de la arquitectura distribuida de alta concurrencia del lado del servidor

Plataforma de comercio electrónico B2B: función de pago electrónico ChinaPay UnionPay

Aprenda el candado distribuido de Zookeeper, deje que los entrevistadores lo miren con admiración

Solución de bloqueo distribuido de Redisson con microservicio de pico de comercio electrónico de SpringCloud

Vea más artículos buenos, ingrese a la cuenta oficial, por favor, excelente en el pasado

Una cuenta pública profunda y conmovedora 0.0

Supongo que te gusta

Origin blog.csdn.net/a1036645146/article/details/109515701
Recomendado
Clasificación