prototipos
Contenido
Como marco de IOC, Spring implementa la inyección de dependencia y una fábrica de Bean centralizada es responsable de la creación de instancias y la gestión de dependencias de cada Bean. Cada Bean no necesita preocuparse por su complejo proceso de creación, logrando un buen efecto de desacoplamiento.
Hagamos un resumen aproximado del flujo de trabajo de Spring, que consta principalmente de dos enlaces principales:
Suponemos que todas las clases de configuración y extensión se han cargado en ApplicationContext y luego analizamos específicamente. el proceso de carga de Bean.
Pensando en una pregunta, aparte de la implementación del marco Spring, suponiendo que ya tenemos un conjunto completo de mapas de definición de beans a mano y luego especificamos un nombre de bean para la creación de instancias, ¿qué debemos preocuparnos? ¿acerca de? Incluso si no tenemos el marco Spring, aún necesitamos comprender estos dos aspectos del conocimiento:
Spring abstrae y encapsula, haciendo que la configuración del alcance y las dependencias sea transparente para los desarrolladores. Se ha especificado claramente qué hay en la configuración, su ciclo de vida y de quién depende. En cuanto a cómo se implementa y cómo se inyectan las dependencias, se ha confiado a la fábrica de Spring para su gestión.
Spring solo expone una interfaz muy simple para la persona que llama, como getBean:
Luego usaremos el método getBean como punto de entrada para comprender el proceso de carga de Spring y los detalles internos. de manejar información de creación, alcances, dependencias, etc.
Lo anterior es un diagrama de flujo que rastrea la creación de la cadena de llamadas getBean. Para comprender mejor el proceso de carga del Bean, se omiten algunas excepciones, registros y procesamiento de ramas y el juicio de algunas condiciones especiales.
En el diagrama de flujo anterior, puedes ver que la carga de un Bean pasará por las siguientes etapas (marcadas en verde):
La parte más complicada de todo el proceso es la resolución. de dependencias circulares. El plan será analizado en detalle más adelante.
El mapa creado después de analizar la configuración utiliza beanName como clave. Consulte DefaultListableBeanFactory:
El nombre pasado en BeanFactory.getBean puede darse en las siguientes situaciones:
Para obtener la BeanDefinition correcta, primero debe convertir el nombre. Obtener beanName.
Consulte AbstractBeanFactory.doGetBean:
Si es un nombre de alias, durante la fase de análisis, la relación de mapeo entre el nombre de alias y el nombre del bean se registra en SimpleAliasRegistry. Obtenga el nombre del frijol de este registrador. Consulte SimpleAliasRegistry.canonicalName:
Si es el nombre de un bean de fábrica, significa que es un bean de fábrica. Si lleva el modificador de prefijo &, simplemente elimine el prefijo. Consulte BeanFactoryUtils.transformedBeanName:
La BeanDefinition que leemos del archivo de configuración es GenericBeanDefinition.
Registra algunas propiedades o parámetros de construcción declarados por la clase actual, pero solo usa un parentName para la clase principal.
A continuación, encontrará un problema. Cuando cree una instancia de un Bean más adelante, el BeanDefinition utilizado es el tipo RootBeanDefinition en lugar de GenericBeanDefinition. ¿porqué es eso?
La respuesta es obvia. GenericBeanDefinition no tiene suficiente información definida cuando existe una relación de herencia:
Para inicializar correctamente el objeto se necesita información completa. Se requiere la fusión recursiva de la definición de la clase principal:
Consulte AbstractBeanFactory.doGetBean:
Cuando se determina que parentName existe, indica que la definición de la clase principal existe y comienza a fusionarse. ¿Qué pasa si la clase principal todavía tiene una clase principal? Llamada recursiva para continuar fusionando.
Consulte el método AbstractBeanFactory.getMergedBeanDefinition:
Cada vez que se fusiona la definición de la clase principal, se llamará a RootBeanDefinition.overrideFrom para sobrescribir la definición de la clase principal y obtener que la clase actual pueda ser instanciado correctamente.
¿Qué es una dependencia circular?
Por ejemplo, hay tres clases A, B y C. Entonces A está asociada con B, B está asociada con C y C está asociada con A. Esto forma una dependencia circular. Si se trata de una llamada a un método, no se considera una dependencia circular. La dependencia circular debe contener una referencia.
Las dependencias circulares se dividen en dos tipos según el momento de la inyección:
Si se trata de una dependencia circular del constructor, es esencialmente imposible de resolver. Por ejemplo, llamamos con precisión al constructor de A y descubrimos que depende de B, por lo que llamamos al constructor de B para crear instancias. Descubrimos que depende de C, por lo que llamamos al constructor de C para inicializarlo y el resultado es. que depende de A. Todo esto forma un punto muerto, lo que hace que A no pueda crear.
Si se trata de una dependencia circular de valor establecido, el marco Spring solo admite dependencias circulares de valor establecido en singletons. Spring almacena en caché y expone el singleton por adelantado almacenándolo en caché mientras aún se está creando, para que otras instancias puedan hacer referencia a la dependencia.
Spring no admite ninguna dependencia circular en modo prototipo. Si se detecta una dependencia circular, se generará directamente una excepción BeanCurrentlyInCreationException.
Una variable ThreadLocal prototiposCurrentlyInCreation se utiliza para registrar el objeto Bean creado por el hilo actual, consulte AbstractBeanFactory#prototypesCurrentlyInCreation:
Registre antes de que se cree el Bean y elimine el registro después de Se crea frijol. Ver AbstractBeanFactory.doGetBean:
Ver la operación de registro de AbstractBeanFactory.beforePrototypeCreation:
Ver la operación de eliminación de AbstractBeanFactory.beforePrototypeCreation:
Para ahorrar espacio en la memoria, en un solo elemento Cuando prototiposCurrentlyInCreation solo registra objetos String, use Set en su lugar después de múltiples elementos dependientes.
Aquí hay un pequeño truco para ahorrar memoria utilizado por Spring.
Ahora que entendemos el proceso de escribir y eliminar registros, veamos los métodos de lectura y evaluación de bucles. Hay dos situaciones que discutir aquí.
La implementación de estos dos lugares es ligeramente diferente.
Si depende del constructor, por ejemplo, el constructor de A depende de B, se producirá tal situación. Durante la fase de creación de instancias de A, el constructor que se utilizará coincide y se descubre que el constructor tiene el parámetro B, y se usa BeanDefinitionValueResolver para recuperar la instancia de B. Consulte BeanDefinitionValueResolver.resolveReference:
Descubrimos que aquí se sigue llamando a beanFactory.getBean para cargar B.
Si se trata de una dependencia circular, por ejemplo, no proporcionamos un constructor aquí y usamos @Autowire para anotar la dependencia (hay otras formas de no dar ejemplos):
Durante el proceso de carga, se encuentra el constructor sin parámetros, no es necesario recuperar las referencias de los parámetros de construcción y la creación de instancias es exitosa. Luego continúe la ejecución e ingrese a la etapa de llenado de atributos AbtractBeanFactory.populateBean, donde se realizará la inyección de dependencia de B.
Para obtener la referencia instanciada de B, la dependencia eventualmente se leerá recuperando la clase DependencyDescriptor, consulte DependencyDescriptor.resolveCandidate:
Encontré el método beanFactory.getBean Llamado nuevamente .
Aquí, las dos dependencias circulares son idénticas. Ya sea una dependencia circular del constructor o una configuración de dependencia circular, cuando es necesario inyectar un objeto dependiente, se seguirá llamando a beanFactory.getBean para cargar el objeto, formando una operación recursiva.
Cada vez que se llama a beanFactory.getBean para la creación de instancias, se utiliza la variable prototiposCurrentlyInCreation para grabar. Siguiendo la idea aquí, el efecto general es equivalente a establecer una cadena de construcción de objetos dependientes.
El valor en prototiposCurrentlyInCreation cambia de la siguiente manera:
El lugar donde se llama a la determinación es en AbstractBeanFactory.doGetBean, y la creación de instancias de todos los objetos se iniciará desde aquí.
El método de implementación de determinación es AbstractBeanFactory.isPrototypeCurrentlyInCreation:
Por lo tanto, en el modo prototipo, las dependencias circulares del constructor y las dependencias circulares del valor establecido se detectan esencialmente de la misma manera. Spring no puede resolverlo y lanza directamente la excepción BeanCurrentlyInCreationException.
Spring tampoco admite la construcción de dependencias circulares en modo singleton. La detección de una dependencia circular de construcción también generará una excepción BeanCurrentlyInCreationException.
Al igual que el patrón prototipo, el patrón singleton también utiliza una estructura de datos para registrar el nombre del bean que se está creando. Consulte DefaultSingletonBeanRegistry:
registrará antes de la creación y eliminará el registro después de la creación.
Consulte DefaultSingletonBeanRegistry.getSingleton
Para conocer el método de registro y determinación, consulte DefaultSingletonBeanRegistry.beforeSingletonCreation:
Aquí intentaremos registrar el bean instanciado actualmente en singletonsCurrentlyInCreation. Sabemos que la estructura de datos de singletonsCurrentlyInCreation está configurada, lo que no permite elementos repetidos, por lo que una vez registrada previamente, la operación de agregar aquí devolverá un error.
Por ejemplo, cargar el singleton de A es similar al modo prototipo. El modo singleton también llamará al constructor coincidente que se utilizará, encontrará que el constructor tiene el parámetro B y luego usará BeanDefinitionValueResolver para recuperar el. instancia de B. De acuerdo con El análisis anterior continúa llamando al método beanFactory.getBean.
Entonces, tome los ejemplos de A, B y C para ilustrar los cambios de singletonsCurrentlyInCreation. Aquí puede ver que el algoritmo es el mismo que el método de juicio de dependencia circular del modo prototipo:
En modo singleton, la dependencia circular del constructor no se puede resolver, pero se puede resolver la dependencia circular del valor establecido.
Aquí hay un diseño importante: exponer el singleton que se está creando de antemano.
Entendamos por qué hacemos esto.
Analicemos las dependencias de configuración de A, B y C anteriores
=> 1. Se crea A -> Se construye A, comienza a inyectar atributos y encuentra la dependencia B. iniciar la creación de instancias de B
=> 2. Creación de B -> Se completa la construcción de B, comenzar a inyectar atributos, encontrar dependencia en C, iniciar la creación de instancias de C
=> 3. Creación de C -> Se completa la construcción de C, comienza a inyectar propiedades y encuentra la dependencia A
Aquí viene el punto clave, en nuestra fase 1, se construyó A y el objeto Bean ha asignado memoria en el montón , Incluso si posteriormente Completar atributos en A (como completar objetos B dependientes) no modificará la dirección de referencia de A.
Entonces, en este momento, ¿podemos obtener la referencia de la instancia A por adelantado e inyectarla en C primero para completar la creación de instancias de C, de modo que el proceso se vuelva así?
=> 3. Creación de C -> Se completa la construcción de C, comienza la inyección de dependencia, se encuentra la dependencia A, se descubre que A se ha construido, se hace una referencia directa y se completa la creación de instancias de C.
=> 4. Después de que C completa la creación de instancias, B inyecta C y completa la creación de instancias, y A inyecta B y también completa la creación de instancias.
Esta es la técnica de Spring para resolver aplicaciones de dependencia circular en configuraciones de modo singleton. El diagrama de flujo es:
Para lograr la exposición temprana de los singleton. Spring usa un caché de tres niveles, consulte DefaultSingletonBeanRegistry:
Las diferencias entre estos tres cachés son las siguientes:
A partir de getBean ("a"), la implementación específica del agregado SingletonFactory es el siguiente:
p>
Puede ver que si usa SingletonFactory para obtener una instancia, usa el método getEarlyBeanReference, que devuelve una referencia no inicializada.
Para conocer el lugar para leer el caché, consulte DefaultSingletonBeanRegistry:
Primero intente leer desde singletonObjects y singletonFactory, no hay datos, luego intente singletonFactories para leer singletonFactory, ejecute getEarlyBeanReference para obtener la referencia y guárdela en earlySingletonObjects.
La ventaja de este earlySingletonObjects es que si hay otros lugares que intentan obtener singletons no inicializados en este momento, se pueden sacar directamente de earlySingletonObjects sin llamar a getEarlyBeanReference.
A juzgar por el diagrama de flujo, la instancia A realmente inyectada en C todavía se encuentra en la etapa de llenado de atributos y no se ha inicializado por completo. Cuando la recursividad retrocede y A obtiene con éxito la dependencia B, la carga de A se completará realmente.
Después de obtener la RootBeanDefintion completa, puede utilizar esta información de definición para crear una instancia de un Bean específico.
Para la creación de instancias específicas, consulte AbstractAutowireCapableBeanFactory.createBeanInstance, que devuelve la clase contenedora BeanWrapper. Hay tres estrategias:
Utilice el método de fábrica para crear y primero utilizará getBean. para obtener la clase de fábrica, luego busque el método de fábrica coincidente a través de los parámetros y llame al método de creación de instancias para implementar la creación de instancias. Para obtener más información, consulte ConstructorResolver.instantiateUsingFactoryMethod:
Crear usando un constructor parametrizado. Es más complicado e implica hacer coincidir parámetros y constructores. Para encontrar un constructor coincidente, Spring dedicó mucho trabajo, consulte ConstructorResolver.autowireConstructor:
Crear utilizando un constructor sin argumentos es la forma más sencilla, consulte AbstractAutowireCapableBeanFactory.instantiateBean:
Descubrimos que estos tres métodos de creación de instancias eventualmente usarán getInstantiationStrategy().instantiate(...), consulte la clase de implementación SimpleInstantiationStrategy.instantiate:
Aunque se obtiene el constructor, no se crea una instancia de inmediato. Debido a que el usuario usa los métodos de configuración de reemplazo y búsqueda, se usa un proxy dinámico para agregar la lógica correspondiente. De lo contrario, utilice la reflexión directamente para crear la instancia.
Una vez creada la instancia, puedes comenzar a inyectar propiedades e inicializarla.
Pero el Bean aquí no es el Bean final. Al regresar a la persona que llama, si es un FactoryBean, debe usar el método getObject para crear una instancia. Consulte AbstractBeanFactory.getObjectFromBeanInstance, que ejecutará doGetObjectFromFactoryBean:
Después de crear la instancia, comienza la inyección de atributos. Si hay una instancia dependiente externamente involucrada, se recuperará automáticamente y se asociará con la instancia actual.
Ideas de COI reflejadas. Es con este paso que Spring reduce el acoplamiento entre varias clases.
El método de entrada para el llenado de atributos está en AbstractAutowireCapableBeanFactory.populateBean.
Puedes ver que los principales enlaces de procesamiento son:
¿Qué pasa si nuestro Bean necesita algunos recursos del contenedor? Por ejemplo, necesita obtener BeanFactory, ApplicationContext, etc.
Spring proporciona la serie Aware de interfaces para resolver este problema. Por ejemplo, existe este Consciente:
En la fase de inicialización, si Spring determina que Bean implementa una de estas interfaces, inyectará los recursos que le interesan en el Bean.
Ver AbstractAutowireCapableBeanFactory.invokeAwareMethos:
¿Qué sucede si necesitamos realizar algunas operaciones de mejora antes o después de la inicialización del Bean?
Estas operaciones mejoradas incluyen registro, verificación, modificación de atributos, detección que requiere mucho tiempo, etc. El marco Spring proporciona BeanPostProcessor para lograr este objetivo. Por ejemplo, usamos la anotación @Autowire para declarar dependencias y usamos AutowiredAnnotationBeanPostProcessor para implementar la consulta e inyección de dependencias. La interfaz se define de la siguiente manera:
Los beans que implementan esta interfaz se registrarán en beanPostProcessors en Spring, consulte AbstractBeanFactory:
Siempre que el Bean implemente la interfaz BeanPostProcessor, será Spring lo reconoce automáticamente al cargar Bean y se registra automáticamente, lo cual es muy conveniente.
Luego, antes y después de crear una instancia del bean, Spring llamará a los beanPostProcessors que hemos registrado para ejecutar los procesadores.
Aquí se utiliza el modelo de cadena de responsabilidad, y el Bean se pasará y procesará en la cadena del procesador. Cuando llamamos a BeanFactory.getBean, ejecutar el método de inicialización del Bean AbstractAutowireCapableBeanFactory.initializeBean iniciará estos procesadores.
Hay dos formas de elegir para la inicialización personalizada:
Consulte AbstractAutowireCapableBeanFactory.invokeInitMethods:
El Bean se ha cargado, las propiedades se han completado. y la inicialización se ha completado.
Todavía existe la oportunidad de realizar la conversión de tipos en la instancia de Bean antes de regresar a la persona que llama. Ver AbstractBeanFactory.doGetBean:
Dejando de lado algunos detalles y funciones de extensión, el proceso de creación de un Bean no es más que:
Obtener la definición completa -> Creación de instancias -> Inyección de dependencia - > Inicialización ->Conversión de tipo.
Como marco completo, Spring necesita considerar varias posibilidades y la escalabilidad del acceso.
Por lo tanto, existen complejas soluciones de dependencia circular, complejos procesos de coincidencia de constructores de parámetros y BeanPostProcessor para extender y modificar beans instanciados o inicializados.
Primero tenga un pensamiento de diseño general y luego desglose gradualmente el diseño para estos escenarios especiales, y todo el proceso de carga de beans se podrá resolver fácilmente.