¡Hola! Mientras la administración de JavaRush trabaja en nuevos niveles, quiero comenzar una serie de artículos de capacitación sobre Spring Framework. Sí, sé que ya hay mucho material sobre este tema en Internet, pero, como muestra la práctica, todos están al nivel de Hola Mundo. No quiero hablar de cómo colocar correctamente las anotaciones, sino de cómo funciona todo "bajo el capó". El artículo está dirigido a quienes ya han trabajado con este marco de una forma u otra y están familiarizados con los conceptos básicos.
Inicializando el contexto.
Así que comencemos con lo básico. En mi opinión, uno de los puntos más importantes es comprender cómo se configura el contexto y cómo se inicializan los beans. Como sabes, antes de que Spring comience a funcionar, es necesario configurarlo. En tiempos antediluvianos, esto se hacía usando archivos xml (en algunos proyectos, principalmente los antiguos, continúan haciéndolo hasta el día de hoy). A continuación se muestra un pequeño ejemplo de dicho archivo de configuración:<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="helloWorld" class="ru.javarush.HelloWorld">
<property name="message" value="Hello World!"/>
</bean>
</beans>
En general, esto es suficiente para crear un par de controladores y ejecutar una startup (que no despegará). Pero, ¿cómo hará que esta configuración funcione Spring? Y aquí es donde las cosas se ponen interesantes. Para que Spring comprenda nuestra configuración, existe un archivo XmlBeanDefinitionReader
. BeanDefinition
Este es un componente interno de Spring que escanea (analiza) xml y crea archivos basados en lo que escribimos allí . BeanDefinition
es un objeto que almacena información sobre el bean. Esto incluye: desde qué clase se debe crear (el bean); alcance; si está instalada la inicialización diferida; ¿Es necesario inicializar antes de este bean otro y otras propiedades que se describen en xml? Todos los recibidos BeanDefinition
se agregan a HashMap
, en el que el identificador es el nombre del bean (establecido por usted o asignado por Spring) y BeanDefinition
el objeto en sí. BeanDefinition
Una vez creado todo , aparece un nuevo héroe en el escenario: BeanFactory
. Este objeto itera sobre HashMap’e
s BeanDefinition
, crea beans basados en ellos y los coloca en un contenedor de IoC. Hay un matiz aquí, de hecho, cuando se inicia la aplicación, el contenedor de IoC contendrá beans que tienen un alcance Singleton (configurado de forma predeterminada), mientras que el resto se crean cuando son necesarios (prototipo, solicitud, sesión). Y ahora una pequeña digresión, conozcamos a otro personaje.
Conozca BeanPostProcessor. (PPB)
El hecho es que un bean no es necesariamente una clase de lógica empresarial para su aplicación. Un bean también se denomina bean de infraestructura. En resumen, un bean de infraestructura es una clase que configura sus beans lógicos de negocios (sí, demasiados beans). Te contaré más sobre esto a continuación, pero para que quede un poco más claro qué configura exactamente BPP, te daré un ejemplo. ¿ Están todos familiarizados con el resumen@Autowired
? Por lo tanto, usted AutowiredAnnotationBeanPostProcessor
es responsable de garantizar que todas sus clases estén integradas entre sí.
Volvamos a BeanFactory
Conociendo ahora acerca de BPP, es necesario aclarar que al iterar sobreHashMap
los, BeanDefinition
todos primero se crean y se colocan por separado (no en el contenedor de IoC) BeanPostProcessor
. Después de esto, se crean beans regulares de nuestra lógica de negocios, se colocan en un contenedor de IoC y su configuración comienza utilizando BPP diferidos por separado. Y así sucede, cada BPP tiene 2 métodos:
postProcessorBeforeInitialization(Object bean, String beanName);
postProcessorAfterInitialization(Object bean, String beanName);
Itera a través de nuestros contenedores dos veces. La primera vez que se llama al método postProcessorBeforeInitialization
y la segunda vez que se llama al método postProcessorAfterInitialization
. Seguramente te ha surgido la duda de por qué se necesitan dos métodos, déjame explicarte. El hecho es que para procesar algunas anotaciones (como @Transactional
, por ejemplo), nuestro bean se reemplaza por una clase proxy. Para entender por qué se hace esto, es necesario saber cómo funciona @Transactional
, y así es como funciona. Debe agregar un par de líneas más de código al método marcado con esta anotación sobre la marcha. ¿Cómo hacerlo? Así es, creando una clase de proxy, dentro de la cual se agregará el código necesario al método requerido. Ahora imagina esta situación, tenemos una clase:
class A {
@Autowired
private SomeClass someClass;
@Transactional
public void method() {
// модификатор доступа обязательно public
}
}
Esta clase tiene 2 anotaciones @Autowired
y @Transactional
. Ambas anotaciones son procesadas por BPP diferentes. Si funciona primero AutowiredBPP
, entonces todo estará bien, pero si no, nos encontraremos con este problema. El hecho es que cuando se crea la clase de proxy, toda la metainformación se pierde. En otras palabras, no habrá información sobre la anotación @Autowired
en la clase proxy y, por lo tanto AutowiredBPP
, no funcionará, lo que significa que nuestro campo someClass
tendrá el valor null
, lo que probablemente conducirá a una NPE. También vale la pena saber que entre llamadas a métodos, postProcessorBeforeInitialization
se postProcessorAfterInitialization
llama al método - init
, si lo hay. Este es básicamente el segundo constructor, pero la diferencia es que en este momento todas nuestras dependencias ya están integradas en la clase y podemos acceder a ellas desde init
el método -. Entonces, una vez más el algoritmo de inicialización del contexto:
XmlBeanDefinitionReader
escanea nuestro archivo de configuración xml.- Crea
BeanDefinition
y los poneHashMap
. - Viene
BeanFactory
y de estoHashMap
por separado suma todos losBeanPostProcessor
's. - Crea
BeanDefinition
beans a partir de 's y los coloca en un contenedor de IoC. - Aquí los BPP vienen y configuran estos beans usando 2 métodos.
- Listo.
GO TO FULL VERSION