¿Qué oportunidades ofrece Spring para personalizar su comportamiento?

Hola a todos. En contacto Vladislav Rodin. Actualmente, soy el jefe del curso High Load Architect en OTUS, y también doy cursos sobre arquitectura de software.

Además de la enseñanza, también estoy escribiendo material de copyright para el blog OTUS en el Haber y quiero coincidir con el artículo de hoy para lanzar el curso "Desarrollador en el Marco de Primavera" , que ahora está abierto para reclutamiento.




Introducción


Desde el punto de vista del lector, el código de la aplicación que usa Spring parece bastante simple: se declaran algunos beans, las clases se marcan con anotaciones, y luego se inyectan los beans cuando es necesario, todo funciona bien. Pero un lector curioso tiene una pregunta: “¿Cómo funciona? ¿Qué esta pasando?". En este artículo trataremos de responder esta pregunta, pero no por satisfacer una curiosidad ociosa.

Spring Framework es conocido por ser lo suficientemente flexible y ofrece opciones para personalizar el comportamiento del marco. Spring también abunda en una serie de reglas bastante interesantes para aplicar ciertas anotaciones (por ejemplo, Transaccional). Para comprender el significado de estas reglas, para poder derivarlas, y también para comprender qué y cómo se puede configurar en Spring, debe comprender varios principios de funcionamiento de lo que hay en Spring bajo el capó. Como saben, el conocimiento de varios principios exime del conocimiento de muchos hechos. Le sugiero que se familiarice con estos principios a continuación si, por supuesto, aún no los conoce.

Configuraciones de lectura


Al principio, debe analizar las configuraciones que están en su aplicación. Dado que hay varios tipos de configuraciones (configuración xml, groovy, java, configuración basada en anotaciones), se utilizan diferentes métodos para leerlas. De una forma u otra, se recopila un mapa de la forma Map <String, BeanDefinition>, en el que los nombres de beans se asignan a sus definiciones de bean. Los objetos de la clase BeanDefinition son metainformaciones sobre beans y contienen la identificación del bean, su nombre, su clase, los métodos de destrucción e inicio.

Ejemplos de clases involucradas en este proceso: GroovyBeanDefinitionReader, XmlBeanDefinitionReader, AnnotatedBeanDefinitionReader, implementando la interfaz BeanDefinitionReader .

Configurar Beandefinitions


Entonces, tenemos descripciones de beans, pero no hay beans en sí mismos, todavía no se han creado. Antes de crear beans, Spring ofrece la posibilidad de personalizar las BeanDefinitions resultantes. Para estos fines, se utiliza la interfaz BeanFactoryPostProcessor . Se parece a esto:

public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

El parámetro de método de esta interfaz permite usar su propio método getBeanDefinitionNames para obtener los nombres con los que puede obtener BeanDefinitions del mapa y editarlos.

¿Por qué podría ser necesario? Supongamos que algunos beans requieren detalles para conectarse a un sistema externo, como una base de datos. Queremos que los beans se creen ya con los detalles, pero los detalles se almacenan en el archivo de propiedades. Podemos aplicar uno de los BeanFactoryPostProcessors estándar: PropertySourcesPlaceholderConfigurer, que reemplazará la propiedad de nombre en BeanDefinition con el valor real almacenado en el archivo de propiedades. Es decir, en realidad reemplazará Value ("usuario") con Value ("root") en BeanDefinion. Para que esto funcione, el PropertySourcesPlaceholderConfigurer, por supuesto, necesita estar conectado. Pero esto no se limita a esto; puede registrar su BeanFactoryPostProcessor, para implementar cualquier lógica que necesite para procesar BeanDefinitions.

Creando Frijoles


En esta etapa, tenemos un mapa que tiene los nombres de los beans ubicados por las claves, y las BeanDefinitions configuradas por los valores. Ahora necesitas crear estos frijoles. Esto es lo que hace BeanFactory . Pero aquí puede agregar personalización escribiendo y registrando su FactoryBean . FactoryBean es una interfaz de la forma:

public interface FactoryBean {
    T getObject() throws Exception;
    Class<?> getObjectType();
    boolean isSingleton();
}

Por lo tanto, un BeanFactory crea un bean en sí mismo si no hay FactoryBean correspondiente a la clase de bean, o le pide a FactoryBean que cree este bean. Hay un pequeño matiz: si el alcance del bean es singleteone, entonces el bean se crea en esta etapa, si es un prototipo, cada vez que se necesite este bean, se solicitará a BeanFactory.

Como resultado, nuevamente obtenemos un mapa, pero ya es un poco diferente: las claves contienen los nombres de los beans y los valores de los beans. Pero esto es cierto solo para singletones.

Configurando Frijoles


Ahora viene la etapa más interesante. Tenemos un mapa que contiene los beans creados, pero estos beans aún no se han configurado. Es decir, no procesamos anotaciones que establezcan el estado del bean: Autowired, Value. Tampoco procesamos anotaciones que cambien el comportamiento del bean: Transactional, Async. BeanPostProcessors , que nuevamente son implementaciones de la interfaz correspondiente, nos permiten resolver este problema :

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

Vemos 2 métodos con nombres aterradores pero completos. Ambos métodos toman un bean como entrada, desde el cual puedes preguntar de qué clase es, y luego usar la API de Reflection para procesar anotaciones. Métodos de devolución de beans, posiblemente reemplazados por proxy.

Para cada bean antes de colocarlo en el contexto, sucede lo siguiente: los métodos postProcessBeforeInitialization se activan para todos los BeanPostProcessors, luego se activa el método init y luego se activan los métodos postProcessAfterInitialization para todos los BeanPostProcessors.

Estos dos métodos tienen una semántica diferente: postProcessBeforeInitialization procesa las anotaciones de estado, postProcessAfterInitialization procesa el comportamiento, porque el proxy se usa para procesar el comportamiento, lo que puede conducir a la pérdida de anotaciones. Es por eso que el comportamiento cambia en último lugar.

¿Dónde está la personalización? Podemos escribir nuestra anotación, BeanPostProcessor para ello, y Spring la procesará. Sin embargo, para que BeanPostProcessor funcione, también debe estar registrado como un bean.

Por ejemplo, para incrustar un número aleatorio en el campo, creamos una anotación InjectRandomInt (colgada en los campos), creamos y registramos un InjectRandomIntBeanPostProcessor, en el primer método del cual procesamos la anotación creada, y en el segundo método, simplemente devolvemos el bean entrante.

Para perfilar los beans, cree una anotación de perfil que se transmita a los métodos, cree y registre un ProfileBeanPostProcessor, en el primer método del cual devolveremos el bean entrante, y en el segundo método, devolveremos un proxy que envuelve la llamada al método original con registro de tiempo y ejecución.



Aprende más sobre el curso



All Articles