Análisis en profundidad de las dependencias circulares de Spring desde el nivel del código fuente | Equipo técnico de JD Cloud

**以下举例皆针对单例模式讨论**

Referencia gráfica https://www.processon.com/view/link/60e3b0ae0e3e74200e2478ce

1. ¿Cómo crea Spring beans?

Para un bean singleton, hay un único objeto en todo el ciclo de vida del contenedor Spring.

En el proceso de creación de beans, Spring utiliza la memoria caché de tres niveles, que se define en DefaultSingletonBeanRegistry.java:

    /** Cache of singleton objects: bean name to bean instance. */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
​
    /** Cache of singleton factories: bean name to ObjectFactory. */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
​
    /** Cache of early singleton objects: bean name to bean instance. */
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

Tome OneBean en el paquete com.gyh.general como ejemplo, depure el proceso de inicio de Springboot y analice cómo Spring crea beans.

Consulte el proceso de creación de frijoles en primavera en la figura . Los pasos más críticos son:

1. getSingleton(beanName, true)Busque objetos de bean de los cachés de primer, segundo y tercer nivel por turnos y regrese directamente (antes) si hay objetos en el caché;

2. createBeanInstance(beanName, mbd, args)Elija un constructor adecuado, un nuevo objeto de instancia (instancia), en este momento, los atributos dependientes en la instancia aún son nulos, que es un producto semiacabado;

3. singletonFactories.put(beanName, oneSingletonFactory)Utilice la instancia del paso anterior para crear una fábrica singleton y colocarla en la memoria caché de tercer nivel;

4. populateBean(beanName, mbd, instanceWrapper)Rellene el bean: cree un objeto o asigne un valor a la propiedad definida por el bean;

5. initializeBean("one",oneInstance, mbd)Inicializar el bean: inicializar o procesar de otra manera el bean, como generar un objeto proxy (proxy);

6. getSingleton(beanName, false)Buscar en las cachés primaria y secundaria a su vez para verificar si hay objetos generados con anticipación debido a dependencias circulares y, de ser así, si son consistentes con los objetos inicializados;

2. ¿Cómo resuelve Spring las dependencias circulares?

Tome OneBean y TwoBean bajo el paquete com.gyh.circular.threeCache como ejemplo, los dos Beans dependen uno del otro (es decir, forman un ciclo cerrado).

Refiriéndose al proceso de Spring para resolver la dependencia circular en la figura , podemos ver que Spring usa objectFactory en el caché de tres niveles para generar y devolver un objeto temprano, y exponer la dirección temprana por adelantado para la inyección de dependencia de otro objeto para resolver el problema. problema de dependencia circular.

3. ¿Qué dependencias circulares no puede resolver Spring?

3.1 La anotación @Async se usa en el ciclo

3.1.1 ¿Por qué se informa un error cuando se usa @Async en el bucle ?

Tome OneBean y TwoBean bajo el paquete com.gyh.circular.async como ejemplo. Los dos beans dependen uno del otro, y el método en oneBean usa la anotación @Async . En este momento, el inicio de Spring falla y el error el mensaje es:org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a.one': Bean with name 'a.one' has been injected into other beans [a.two] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.

Y a través del código de depuración, se encuentra que la ubicación del informe de errores está en el método AbstractAutowireCapableBeanFactory#doCreateBean, porque earlySingletonReference != null y exposedObject != bean, lo que genera un error.

Combinado con spring en el diagrama de flujo para resolver dependencias circulares y la imagen de arriba, podemos ver:

1. El bean en la línea 1 es la instancia creada por createBeanInstance (address1)

2. El objeto expuesto en la línea 2 es el objeto proxy (address2) generado después de initializeBean

3. EarlySingletonReference en la línea 3 es el objeto creado cuando getEarlyBeanReference [la dirección aquí es la misma que bean (dirección1)]

La razón subyacente es: anteriormente, TwoBean ya se había basado en el objeto earlySingletonReference cuya dirección era address1 cuando populateBean, pero en este momento, OneBean devolvió un nuevo objeto cuya dirección era address2 después de initializeBean, lo que provocó que Spring no supiera cuál es la versión final de el bean, por lo que informó un error.

Cómo se genera earlySingletonReference, consulte el proceso getSingleton("one", true).

3.1.2 ¿Se informará un error si se usa @Async en el bucle?

Todavía tome OneBean y TwoBean bajo el paquete com.gyh.circular.async como ejemplo. Los dos beans dependen uno del otro, por lo que el método en TwoBean (no OneBean) usa la anotación @Async . En este momento, el resorte es se inició con éxito y no se informa ningún error.

El código de depuración muestra que aunque TwoBean usa la anotación @Async, su earlySingletonReference = null, por lo que no causará un error.

La razón subyacente es: OneBean se crea primero y TwoBean se crea después. En todo el enlace, no se ha buscado la fábrica de objetos de TwoBean en el caché de tercer nivel. (OneBean se encontró dos veces durante el proceso de creación, es decir, uno-> dos -> uno; durante la creación de TwoBean, solo se encontró una vez, es decir, dos -> uno).

Se puede obtener de esto: @Async provoca condiciones de informe de error de dependencia circular:

1. Los beans en la dependencia circular usan la anotación @Async

2. Y este bean se crea antes que otros beans en el ciclo.

3. Nota: Un Bean puede existir en varios ciclos al mismo tiempo, siempre que sea el primer Bean creado en un determinado ciclo, se informará un error.

3.1.3 ¿Por qué se usa @Transactional en el ciclo y no se informa ningún error?

Se sabe que Spring también generará un objeto proxy para un bean anotado con @Transactional, pero ¿por qué este tipo de bean no genera un error cuando está en un bucle?

Tome OneBean y TwoBean bajo el paquete com.gyh.circular.transactional como ejemplo. Los dos Beans dependen el uno del otro, y el método en OneBean usa la anotación @Transactional. Spring se inicia correctamente y no se informa ningún error.

El código de depuración muestra que en el proceso de generación de OneBean, aunque earlySingletonReference != null, el objeto expuesto después de initializeBean tiene la misma dirección que la instancia original (es decir, no se genera ningún proxy para la instancia en el paso initializeBean), por lo que no hay error será informado.

3.1.4 ¿Por qué los mismos agentes producen dos fenómenos diferentes?

También se generan objetos proxy, que también participan en dependencias circulares, la razón de los diferentes fenómenos es que cuando están en dependencias circulares, los nodos que generan proxys son diferentes:

1. @Transactional genera un proxy cuando getEarlyBeanReference y expone la dirección después del proxy (es decir, la dirección final) por adelantado;

2. @Async genera un proxy cuando initializeBean, lo que da como resultado que la dirección expuesta de antemano no sea la dirección final, lo que genera un error.

¿Por qué @Async no puede generar un proxy cuando getEarlyBeanReference? Comparando el proceso de código ejecutado por los dos, se encuentra que:

Ambos envuelven el objeto de instancia original en el método de AbstractAutoProxyCreator#getEarlyBeanReference, como se muestra en la figura a continuación.

Cuando el Bean que usa @Transactional recibe un consejo al crear un proxy, el proxy del objeto proxy se genera inmediatamente.

Sin embargo, el Bean que usa @Async no recibe consejos al crear un proxy y no puede ser enviado por proxy.

3.1.5 ¿Por qué @Async no puede devolver un aviso cuando getEarlyBeanReference?

En el método AbstractAutoProxyCreator#getAdvicesAndAdvisorsForBean, lo principal que se debe hacer es:

1. Encuentra todos los asesores en el contenedor de primavera actual

2. Devolver todos los Advisors que se adapten al bean actual

Los asesores devueltos en el primer paso incluyen BeanFactoryCacheOperationSourceAdvisor y BeanFactoryTransactionAttributeSourceAdvisor, y no hay ningún asesor que se ocupe de Async.

Llegue al fondo y descubra por qué el primer paso no vuelve a tratar con el asesor relacionado con Async.

Se sabe que el uso de @Async @Transactional @Cacheable debe habilitarse con anticipación, es decir, @EnableAsync, @EnableTransactionManagement, @EnableCaching deben marcarse con anticipación.

Tomando @EnableTransactionManagement y @EnableCaching como ejemplos, la clase Selector se introduce en la definición de la anotación y la clase Configuration se introduce en el Selector. En la clase Configuration, el Advisor correspondiente se crea y se coloca en el contenedor de primavera, por lo que el primer paso Se pueden obtener estos dos asesores.

La clase de configuración introducida en la definición de @EnableAsync crea AsyncAnnotationBeanPostProcessor en lugar de un asesor, por lo que no se obtendrá en el primer paso, por lo que el bean de @Async no se redireccionará en este paso.

3.2 Dependencias circulares causadas por constructores

Tome OneBean y TwoBean bajo el paquete com.gyh.circular.constructor como ejemplo. Los constructores de las dos clases dependen el uno del otro. Cuando se inicia Spring, se informa un error:org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'c.one': Requested bean is currently in creation: Is there an unresolvable circular reference?

El código de depuración muestra que los dos beans han caído en un bucle infinito cuando se basan en la nueva instancia del constructor y no pueden exponer las direcciones disponibles por adelantado, por lo que solo pueden informar un error.

4. ¿Cómo resolver el error de dependencia circular anterior?

1. En lugar de @Async, coloque los métodos que requieren operaciones asincrónicas en el grupo de subprocesos para su ejecución. (recomendar)

2. Proponga el método marcado con @Async. (recomendar)

3. Proponga el método que usa @Async en una clase separada, que solo realiza el procesamiento asíncrono y no crea otras dependencias comerciales, es decir, para evitar la formación de dependencias circulares, resolviendo así el problema del informe de errores. Consulte el paquete com.gyh.circular.async.extract.

4. Trate de no usar objetos dependientes del constructor. (recomendar)

5. Romper el ciclo (no recomendado) no forma un ciclo cerrado Antes del desarrollo, planifique las dependencias de objetos y las cadenas de llamadas a métodos, y trate de no usar dependencias circulares. (difícil, ya que el desarrollo iterativo continúa cambiando, es probable que genere ciclos)

6. Romper el orden de creación (no recomendado)

7. Debido a que la clase anotada con @Async informará un error cuando se crea antes que otras clases en la dependencia circular, busque una manera de hacer que esta clase no se cree antes que otras clases, y este problema también se puede resolver, como : @DependsOn, @ perezoso

Autor: Guo Yanhong, Tecnología Jingdong

Fuente: Comunidad de desarrolladores de JD Cloud

Huawei lanzó oficialmente HarmonyOS 4 miniblink compilado con éxito, versión 108. Bram Moolenaar, el padre de Vim, el kernel de Chromium más pequeño del mundo, falleció debido a una enfermedad ChromeOS dividió el navegador y el sistema operativo en una estación independiente Bilibili (Bilibili) y colapsó nuevamente . HarmonyOS NEXT: uso completo Se lanza oficialmente el kernel de desarrollo propio Nim v2.0 y se lanza el lenguaje de programación imperativo Visual Studio Code 1.81. La capacidad de producción mensual de Raspberry Pi ha alcanzado 1 millón de unidades. Babitang lanzó el primer teclado mecánico retro
{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/u/4090830/blog/10093714
Recomendado
Clasificación