Cómo resolver la dependencia circular de Spring a través de caché de tres niveles

El siguiente contenido se basa en Spring6.0.4.

En realidad, esta es una pregunta de entrevista muy frecuente, y siempre he querido hablar sobre este tema con usted en detalle. Hay muchos artículos sobre este tema en Internet, pero siempre siento que todavía es un poco difícil de explicar. esta pregunta claramente Hoy vendré a darle una oportunidad y ver si puedes resolver este problema con tus amigos.

1. Dependencias circulares

1.1 ¿Qué es una dependencia circular?

Primero, ¿qué es una dependencia circular? Esto es realmente fácil de entender, es decir, dos beans dependen uno del otro, similar a lo siguiente:

@Service
public class AService {
    @Autowired
    BService bService;
}
@Service
public class BService {
    @Autowired
    AService aService;
}

AService y BService dependen el uno del otro:

Esto debe entenderse bien.

1.2 Tipos de dependencias circulares

En términos generales, hay tres formas diferentes de dependencias circulares, y la sección 1.1 anterior es una de ellas.

Los otros dos son las tres dependencias, como se muestra en la siguiente figura:

Este tipo de dependencia circular generalmente está profundamente oculta y no es fácil de detectar.

También existe la autosuficiencia, como se muestra a continuación:

En términos generales, si hay dependencias circulares en nuestro código, significa que puede haber problemas en el proceso de diseño de nuestro código, y debemos tratar de evitar que ocurran dependencias circulares. Sin embargo, una vez que ocurre una dependencia circular, Spring la manejará por nosotros por defecto. Por supuesto, esto no significa que el código de dependencia circular esté bien. De hecho, en la última versión de Spring, la dependencia circular está adicionalmente habilitada, si no hay una configuración adicional, se informará directamente de un error si se produce la dependencia circular.
Además, Spring no puede manejar todas las dependencias circulares, Song Ge lo analizará con usted más adelante.

2. Ideas de solución de dependencia circular

2.1 Soluciones

Entonces, ¿cómo resolver la dependencia circular? De hecho, es muy simple, solo agregue un caché a China Plus, echemos un vistazo a la siguiente imagen:

Hemos introducido un grupo de caché aquí.

Cuando necesitemos crear una instancia de AService, primero crearemos un AService original a través de la reflexión de Java. Este AService original puede entenderse simplemente como un AService que acaba de ser nuevo (en realidad, acaba de crearse a través de la reflexión) y no ha establecido ningún atributo. , primero almacenamos este AService en un grupo de búfer.

A continuación, debemos establecer valores para las propiedades de AService y, al mismo tiempo, tratar con las dependencias de AService. En este momento, encontramos que AService depende de BService, por lo que creamos un objeto BService. Cuando creamos BService , encontramos que BService depende de AService, luego en este momento Primero saque AService del grupo de búfer y utilícelo primero, y luego continúe el proceso posterior de creación de BService hasta que se cree BService, luego asígnelo a AService, y luego ambos Se crean AService y BService.

Algunos amigos pueden decir que el AService obtenido por BService del grupo de búfer es un producto semiacabado, no el AService final real, pero amigos, debemos saber que nuestro Java se pasa por referencia (también se puede considerar como pasado por valor , pero este valor es la dirección de memoria), lo que obtuvo BService en ese momento fue la referencia de AService, para decirlo sin rodeos, era solo una parte de la dirección de memoria, y AService se encontró de acuerdo con esta dirección, por lo tanto, si AService se crea más tarde, el AService obtenido por BService está completo El AService tiene.

Luego, el grupo de caché mencionado anteriormente tiene un nombre especial en el contenedor de Spring, que se llama earlySingletonObjects.Sin pasar por el ciclo de vida completo, es posible que las propiedades del Bean no se hayan establecido y las dependencias requeridas por el Bean aún no se hayan establecido. inyectado. Los otros dos niveles de caché son:

  • singletonObjects: Este es el caché de primer nivel. El caché de primer nivel almacena todos los Beans que han pasado por un ciclo de vida completo, es decir, un Bean ha experimentado todo, desde la creación, asignación de atributos y ejecución de varios procesadores. Almacenado en singletonObjects , cuando necesitemos obtener un Bean, primero iremos al caché de primer nivel para encontrarlo, y cuando no haya nadie en el caché de primer nivel, consideraremos ir al caché de segundo nivel.
  • singletonFactories: Este es el caché de tercer nivel. En la memoria caché de primer nivel y la memoria caché de segundo nivel, la clave de la memoria caché es beanName y el valor de la memoria caché es un objeto Bean, pero en la memoria caché de tercer nivel, el valor de la memoria caché es una expresión Lambda, a través del cual se puede crear el destino Un objeto proxy para el objeto.

Algunos amigos pueden encontrar extraño que de acuerdo con la introducción anterior, el caché de primer nivel y el caché de segundo nivel son suficientes para resolver la dependencia circular, ¿por qué hay un caché de tercer nivel? ¡Entonces tienes que considerar la situación de AOP!

2.2 Qué hacer si hay AOP

Lo que les presenté anteriormente es la creación ordinaria de Bean, por lo que realmente no hay problema. Pero hay otra capacidad muy importante en Spring, que es AOP.

Dicho esto, tengo que hablar con mis amigos sobre el proceso de creación de AOP en Spring.

Normalmente, primero obtenemos una instancia de Bean a través de la reflexión, y luego completamos los atributos para el Bean. Después de completar los atributos, el siguiente paso es ejecutar varios BeanPostProcessors. Si hay métodos en el Bean que necesitan ser proxy, el El post-procesador correspondiente se configurará automáticamente. Songge dará un ejemplo simple. Supongamos que tengo el siguiente servicio:

@Service
public class UserService {

    @Async
    public void hello() {
        System.out.println("hello>>>"+Thread.currentThread().getName());
    }
}

Luego, el sistema proporcionará automáticamente un
procesador llamado AsyncAnnotationBeanPostProcessor, en este procesador, el sistema generará un objeto UserService proxy y usará este objeto para reemplazar el UserService original.

Entonces, amigos, lo que deben averiguar es que el UserService original y el UserService proxy recién generado son dos objetos diferentes, que ocupan dos direcciones de memoria diferentes. ! !

Volvamos a mirar la siguiente imagen:

Si AService va a generar un objeto proxy al final, entonces el AService original en realidad se almacena en el grupo de caché, porque aún no ha llegado al paso de procesar AOP (primero debe asignar valores a cada atributo, y luego el procesamiento de AOP). Como resultado, el AService obtenido por BService del grupo de caché es el AService original. Después de que se crea el BService, se completa la asignación de atributos de AService. Luego, en el proceso de creación posterior de AService, AService convertirse en un objeto proxy, no El AService en el grupo de caché se rompe y, finalmente, el AService del que depende el BService no es el mismo que el AService que finalmente se crea.

Para resolver este problema, Spring presenta singletonFactories de caché de tres niveles.

El mecanismo de trabajo de singletonFactories es el siguiente (suponiendo que AService sea, en última instancia, un objeto proxy):

Cuando creamos un AService, después de que se crea el AService original a través de la reflexión, primero juzgue si el Bean actual existe en el caché de nivel 1 actual, si no, entonces:

  1. Primero agregue un registro al caché de tercer nivel. La clave del registro es el beanName del Bean actual, y el valor es una expresión Lambda ObjectFactory. Al ejecutar este Lambda, se puede generar un objeto proxy para el AService actual.
  2. Luego, si el AService Bean actual existe en el caché de segundo nivel, elimínelo.

Ahora continúe asignando valores a los atributos de AService. Resulta que AService necesita BService y luego crea BService. Al crear BService, se encuentra que BService necesita AService, así que primero vaya al caché de primer nivel para averiguarlo. si hay AService.Si es así, solo utilícelo, si no, vaya al caché de segundo nivel para averiguar si hay AService, si lo hay, utilícelo, si no, vaya al caché de tercer nivel para encontrar el ObjectFactory, y luego ejecute el método getObject aquí, este método está en proceso de ejecución, juzgará si es necesario generar un objeto proxy, si es necesario, generar un objeto proxy y devolverlo, si no necesita generar un objeto proxy, luego devuelva el objeto original. Finalmente, almacene el objeto obtenido en el caché de segundo nivel para su próximo uso y elimine los datos correspondientes en el caché de tercer nivel. De esta forma, se crea el BService del que depende AService.

Luego, continúe mejorando AService y ejecute varios posprocesadores. En este momento, algunos posprocesadores desean generar objetos proxy para AService y descubren que AService ya es un objeto proxy, por lo que no es necesario generarlo y directamente use el objeto proxy existente Se puede usar en lugar de AService.

Hasta ahora, tanto AService como BService están listos.

En esencia, singletonFactories avanza en el proceso AOP.

3. Resumen

En general, Spring resuelve las dependencias circulares mediante la comprensión de dos puntos clave:

  • Exposición temprana : cuando al objeto recién creado no se le ha asignado ningún valor, se expondrá y se colocará en el caché para que otros beans lo consulten antes (caché secundario).
  • AOP por adelantado : cuando A depende de B, verifique si se ha producido una dependencia circular (la forma de verificar es marcar A que se está creando, y luego B necesita A, y cuando B crea A, encuentra que A se está creando, lo que significa que ha ocurrido Dependencia circular), si ocurre una dependencia circular, el procesamiento AOP se realizará por adelantado y se utilizará después del procesamiento (caché de tres niveles).
Originalmente, el proceso de AOP es procesar AOP (AbstractAutoProxyCreator) por varios posprocesadores (AbstractAutoProxyCreator) después de que se asignan valores a los atributos. Tiempo, ya no se procesa AOP.

Sin embargo, debe tenerse en cuenta que la memoria caché de tres niveles no puede resolver todas las dependencias circulares, por lo que continuaremos discutiendo esto en detalle más adelante en el artículo.

Supongo que te gusta

Origin blog.csdn.net/mxt51220/article/details/131785210
Recomendado
Clasificación