Dependencia circular de Spring Bean

para resumir

A través del caché de tres niveles y el mecanismo de "pre-exposición", Spring trabaja con el principio de referencia de objetos de Java para resolver perfectamente el problema de dependencia circular en algunos casos.

No use la inyección de dependencia basada en constructor, se puede resolver de las siguientes maneras:

  • Use la anotación @Autowired en el campo para permitir que Spring decida inyectarse en el momento adecuado
  • Inyección de dependencia basada en el método de incubadora

Prefacio

En el trabajo real, a menudo debido a un diseño deficiente o varios factores, a menudo se produce la interdependencia entre clases. Estas clases pueden no causar problemas cuando se usan solas, pero pueden generar excepciones como BeanCurrentlyInCreationException cuando se usa Spring para la administración . Cuando se lanza esta excepción, significa que Spring no puede resolver la dependencia circular.Este artículo explicará brevemente la solución de Spring a las dependencias circulares.

¿Qué es la dependencia circular?

La dependencia circular es en realidad una referencia circular, es decir, dos o más beans se sostienen entre sí y finalmente forman un ciclo cerrado. Por ejemplo, A depende de B, B depende de C y C depende de A. Como se muestra abajo:

Escriba la descripción de la imagen aquí

Requisitos previos para la generación y resolución de dependencias circulares

Puede haber muchas situaciones para la generación de dependencias circulares, por ejemplo:

  • El método de construcción de A se basa en el objeto de instancia de B, y el método de construcción de B se basa en el objeto de instancia de A
  • El método de construcción de A se basa en el objeto de instancia de B y, al mismo tiempo, un determinado campo o definidor de B necesita el objeto de instancia de A, y viceversa.
  • Un cierto campo o establecedor de A depende del objeto de instancia de B, mientras que un cierto campo o establecedor de B depende del objeto de instancia de A, y viceversa.

Por supuesto, la resolución de Spring de las dependencias circulares no es incondicional. El primer requisito previo es que el alcance sea singleton y no especifique explícitamente un objeto que no necesita resolver dependencias circulares, y requiere que el objeto no haya sido proxy. Al mismo tiempo, Spring no es omnipotente para resolver dependencias circulares. Los tres casos anteriores solo pueden resolver los dos últimos . El primer caso de dependencia mutua en el método de construcción tampoco se recupera. La conclusión está aquí primero, echemos un vistazo a la solución de Spring, conociendo la solución, puede entender por qué la primera situación no se puede resolver.

La solución de Spring para las dependencias circulares

El problema se alarga: el ciclo de vida de Spring Bean

La base teórica de la dependencia circular de Spring es en realidad que Java se basa en el paso de referencias.Cuando obtenemos la referencia del objeto, el campo o propiedad del objeto se puede configurar más tarde.
La inicialización de los objetos singleton de Spring se puede dividir en tres pasos:

  • createBeanInstance , instanciación, es en realidad llamar al constructor correspondiente para construir el objeto. En este momento, solo se llama al constructor y la propiedad especificada en spring xml no se completa.
  • populateBean , rellene las propiedades, este paso rellena las propiedades especificadas en el xml de primavera
  • initializeBean , llame al método init especificado en spring xml, o al método AfterPropertiesSet.Los
    pasos donde ocurre la dependencia circular se concentran en el primer y segundo pasos.

Caché de nivel 3

Para los objetos singleton, en todo el ciclo de vida del contenedor de Spring, hay uno y solo un objeto. Es fácil pensar que este objeto debería existir en Cache. Spring hace un uso extensivo de Cache en el proceso de resolver el problema de dependencia circular. Incluso se utiliza la "caché de tres niveles".

"Caché de tres niveles" se refiere principalmente a

/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
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);

Literalmente:

  • Nivel 1: singletonObjects se refiere al caché de objetos singleton
  • Nivel 2: singletonFactories se refiere al caché de la fábrica de objetos singleton
  • Nivel 3: earlySingletonObjects se refiere al caché de objetos singleton expuestos de antemano
  • Los tres cachés anteriores constituyen un caché de tres niveles, y Spring usa estos cachés de tres niveles para resolver inteligentemente el problema de dependencia circular.

Solución

Recordando el proceso de creación de Bean en el artículo anterior, Spring primero intentará obtenerlo del caché. Este caché se refiere a singletonObjects. El método principal para llamar es:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this.singletonObjects) {
         singletonObject = this.earlySingletonObjects.get(beanName);
         if (singletonObject == null && allowEarlyReference) {
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
               singletonObject = singletonFactory.getObject();
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return (singletonObject != NULL_OBJECT ? singletonObject : null);}

Primero explique los dos parámetros:

  • isSingletonCurrentlyInCreation Determina si se está creando el objeto singleton correspondiente. Cuando el objeto singleton no está completamente inicializado (por ejemplo, el constructor definido por A depende del objeto B, primero debe crear el objeto B o confiar en el objeto B en el proceso populatebean, así que primero vaya a crear el objeto B, en este momento se está creando A)
  • allowEarlyReference ¿Está permitido obtener objetos de singletonFactories a través de getObject?

Al analizar todo el proceso de getSingleton, Spring primero intenta obtener de singletonObjects (caché de primer nivel), si no puede obtenerlo y el objeto se está creando, intenta obtenerlo de earlySingletonObjects (caché de segundo nivel), si todavía no se puede obtener y permite singletonFactories Obtenidos por getObject, luego obtenidos por singletonFactory.getObject () (caché de tercer nivel). Si lo consigues entonces

this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);

Luego elimine el singletonFactory correspondiente y coloque el singletonObject en earlySingletonObjects, que en realidad es para actualizar la caché de tercer nivel a la caché de segundo nivel.

El truco de Spring para resolver las dependencias circulares radica en la caché singletonFactories. La caché es de tipo ObjectFactory, que se define de la siguiente manera:

public interface ObjectFactory<T> {
    T getObject() throws BeansException;}

En el proceso de creación de beans, hay dos clases internas anónimas importantes que implementan esta interfaz. Un lugar es

new ObjectFactory<Object>() {
   @Override   public Object getObject() throws BeansException {
      try {
         return createBean(beanName, mbd, args);
      }      catch (BeansException ex) {
         destroySingleton(beanName);
         throw ex;
      }   }

Como se mencionó anteriormente, Spring lo usa para crear beans (esto no está muy claro ...)

El otro lugar es:

addSingletonFactory(beanName, new ObjectFactory<Object>() {
   @Override   public Object getObject() throws BeansException {
      return getEarlyBeanReference(beanName, mbd, bean);
   }});

Aquí está la clave para resolver dependencias circulares:

Este código se produce después del primer paso [ createBeanInstance ], lo que significa que el objeto singleton se ha creado en este momento.

Este objeto se ha producido, aunque no es perfecto, el segundo paso [ populateBean ] y el tercer paso [ initializeBean ] de inicialización no se han realizado , pero se ha reconocido (según la referencia del objeto se puede ubicar en el heap Object), por lo que Spring expondrá este objeto con anticipación para que todos lo conozcan y usen.

 

¿Cuáles son los beneficios de hacer esto? Analicemos la dependencia circular de "un cierto campo o establecedor de A depende del objeto de instancia de B, mientras que un cierto campo o establecedor de B depende del objeto de instancia de A". Un primero completó el primer paso de inicialización y se expuso a singletonFactories de antemano. En este momento, se llevó a cabo el segundo paso de inicialización y descubrió que confiaba en el objeto B. En este momento, intentó obtener (B) y encontró que B aún no se ha creado., Así que tome el proceso de creación, B descubrió que se basó en el objeto A al inicializar el primer paso, por lo que intentó obtener (A), probó los objetos singleton de caché de primer nivel (definitivamente no, porque A tiene no se ha inicializado completamente), probé el caché de segundo nivel tempranoSingletonObjects (Nor), pruebe el caché singletonFactories de tres niveles, porque A se expuso a sí mismo a través de ObjectFactory de antemano, por lo que B puede obtener un objeto a través de ObjectFactory.getObject (aunque A no se ha inicializado completamente, es mejor que nada), B toma Después de alcanzar el objeto A, las fases de inicialización 1, 2 y 3 se completan con éxito, y después de la inicialización completa, se coloca en los objetos singletonObjects de la caché de primer nivel. En este momento, regrese a A, A puede obtener el objeto de B en este momento para completar con éxito sus propias fases de inicialización 2, 3, y finalmente A también completa la inicialización, crece e ingresa los objetos singleton de la caché de primer nivel, y afortunadamente, dado que B ha obtenido la referencia de objeto de A, ¡el objeto A que B ahora sostiene también se transforma a la perfección! ¡Todo es tan mágico! !

Cuando conozca este principio, debe saber por qué Spring no puede resolver el problema de "El método de construcción de A depende del objeto de instancia de B, y el método de construcción de B depende del objeto de instancia de A".

Análisis del informe de errores de dependencia circular basado en constructor

El contenedor Spring colocará cada identificador de Bean que se cree en un "grupo de Bean creado actualmente". El identificador de Bean permanecerá en este grupo durante el proceso de creación, por lo que si se encuentra en la "BeanCurrentlyInCreationException se lanzará en el" Bean actualmente creado Pool ”para indicar una dependencia circular y el Bean creado se eliminará del“ Bean Pool creado actualmente ”.

El contenedor Spring primero crea un singleton A, A depende de B, y luego coloca A en el "grupo de Bean creado actualmente", luego crea B, y B depende de C, y luego coloca B en el "grupo de Bean creado actualmente", y luego crea C, C depende de A, pero en este momento A 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.

Supongo que te gusta

Origin blog.csdn.net/xiangwang2016/article/details/106177504
Recomendado
Clasificación