Implementación de Spring Framework API desde cero. Tutorial para principiantes. Parte 1



Spring Framework es uno de los marcos más complicados para la comprensión y el aprendizaje. La mayoría de los desarrolladores lo aprenden lentamente, a través de tareas prácticas y google. Este enfoque no es efectivo, ya que no ofrece una imagen completa y al mismo tiempo es costoso.

Me gustaría ofrecerle un enfoque fundamentalmente nuevo para el estudio de la primavera. Consiste en el hecho de que una persona pasa por una serie de tutoriales especialmente preparados e implementa independientemente el funcionamiento de la primavera. La peculiaridad de este enfoque es que, además de una comprensión del 100% de los aspectos estudiados de Spring, también proporciona un gran aumento en Java Core (Anotaciones, Reflexión, Archivos, Genéricos).

El artículo te brindará una experiencia inolvidable y te hará sentir como un desarrollador Pivotal. Paso a paso, hará que sus clases sean más agradables y organizará su ciclo de vida (lo mismo que en una primavera real). Las clases que implementará son BeanFactory , Component , Service , BeanPostProcessor , BeanNameAware , BeanFactoryAware , InitializingBean , PostConstruct , PreDestroy , DisposableBean , ApplicationContext , ApplicationListener , ContextClosedEvent .

Un poco sobre ti


Mi nombre es Yaroslav y soy un desarrollador de Java con 4 años de experiencia. En este momento trabajo para EPAM Systems (SPB) y profundizo en las tecnologías que utilizamos. Muy a menudo tengo que lidiar con la primavera, y veo en ella un término medio en el que puedes crecer (Java todo el mundo lo sabe muy bien, y las herramientas y tecnologías demasiado específicas pueden ir y venir).

Hace un par de meses, pasé la certificación Spring Professional v5.0 (sin tomar cursos). Después de eso, pensé en cómo enseñar a otras personas a saltar. Desafortunadamente, por el momento no existe una metodología de enseñanza efectiva. La mayoría de los desarrolladores tienen una idea muy superficial del marco y sus características. La depuración de las fuentes de primavera es demasiado difícil y absolutamente no efectiva desde el punto de vista del entrenamiento (de alguna manera me gustaba). ¿10 proyectos? Sí, puede profundizar su conocimiento en algún lugar y obtener mucha experiencia práctica, pero gran parte de lo que está "bajo el capó" nunca se abrirá ante usted. Leer la primavera en acción? Genial, pero costoso en esfuerzo. Lo trabajé un 40% (durante la preparación para la certificación), pero no fue fácil.

La única forma de entender algo hasta el final es desarrollarlo usted mismo. Recientemente, tuve la idea de que puedes guiar a una persona a través de un interesante tutorial que supervisará el desarrollo de su marco DI. Su característica principal será que la API coincidirá con la API que se está estudiando. La genialidad de este enfoque es que, además de una comprensión profunda (sin espacios) de la primavera, una persona obtendrá una GRAN cantidad de experiencia en Java Core. Francamente, yo mismo aprendí muchas cosas nuevas durante la preparación del artículo, tanto en Spring como en Java Core. ¡Comencemos a desarrollar!

Proyecto desde cero


Entonces, lo primero que debe hacer es abrir su IDE favorito y crear un proyecto desde cero. No conectaremos ninguna biblioteca de Maven ni de terceros. Ni siquiera conectaremos las dependencias de Spring. Nuestro objetivo es desarrollar una API que sea más similar a Spring API e implementarla nosotros mismos.

En un proyecto limpio, cree 2 paquetes principales. El primer paquete es su aplicación ( com.kciray ) y la clase Main.java dentro de él. El segundo paquete es org.springframework. Sí, duplicaremos la estructura del paquete del resorte original, el nombre de sus clases y sus métodos. Hay un efecto tan interesante: cuando creas algo propio, el tuyo comienza a parecer simple y comprensible. Luego, cuando trabaje en proyectos grandes, le parecerá que todo se crea allí en función de su pieza de trabajo. Este enfoque puede tener un efecto muy positivo en la comprensión del sistema en su conjunto, mejorarlo, corregir errores, resolver problemas, etc.

Si tiene algún problema, puede realizar un proyecto de trabajo aquí .

Crea un contenedor


Para comenzar, configure la tarea. Supongamos que tenemos 2 clases: ProductFacade y PromotionService . Ahora imagine que desea conectar estas clases entre sí, pero para que las clases mismas no se conozcan entre sí (Patrón DI). Necesitamos una clase separada que gestione todas estas clases y determine las dependencias entre ellas. Llamémoslo un contenedor. Creemos la clase Container ... Aunque no, ¡espera! Spring no tiene una sola clase de contenedor. Tenemos muchas implementaciones de contenedores, y todas estas implementaciones se pueden dividir en 2 tipos: fábricas de contenedores y contextos. La fábrica de contenedores crea beans y los vincula (inyección de dependencia, DI), y el contexto hace casi lo mismo, además agrega algunas características adicionales (por ejemplo, mensajes de internacionalización). Pero ahora no necesitamos estas funciones adicionales, por lo que trabajaremos con la fábrica de contenedores.

Cree una nueva clase BeanFactory y póngala en el paquete org.springframework.beans.factory . Deje que los Map<String, Object> singletons almacenen dentro de esta clase, en la que el id bin se asigna al bin en sí. Agregue el Object getBean(String beanName) , que extrae los beans por identificador.

 public class BeanFactory { private Map<String, Object> singletons = new HashMap(); public Object getBean(String beanName){ return singletons.get(beanName); } } 

Tenga en cuenta que BeanFactory y FactoryBean son dos cosas diferentes. El primero es la fábrica de contenedores (contenedor), y el segundo es la fábrica de contenedores, que se encuentra dentro del contenedor y también produce contenedores. Fábrica dentro de la fábrica. Si está confundido entre estas definiciones, puede recordar que en inglés el segundo sustantivo es el principal y el primero es algo así como un adjetivo. En Bean Factory, la palabra principal es fábrica, y en Factory Bean , el frijol.

Ahora, cree las clases ProductService y PromotionsService . ProductService devolverá el producto de la base de datos, pero antes de eso debe verificar si se aplican descuentos (Promociones) a este producto. En el comercio electrónico, el trabajo con descuento a menudo se asigna a una clase de servicio separada (y a veces a un servicio web de terceros).

 public class PromotionsService { } public class ProductService { private PromotionsService promotionsService; public PromotionsService getPromotionsService() { return promotionsService; } public void setPromotionsService(PromotionsService promotionsService) { this.promotionsService = promotionsService; } } 

Ahora tenemos que hacer que nuestro contenedor ( BeanFactory ) detecte nuestras clases, crearlas para nosotros e inyectar una en la otra. Las operaciones como el new ProductService() deben ubicarse dentro del contenedor y realizarse para el desarrollador. Usemos el enfoque más moderno (escaneo de clase y anotaciones). Para hacer esto, necesitamos crear una anotación @Component con las @Component ( org.springframework.beans.factory.stereotype ).

 @Retention(RetentionPolicy.RUNTIME) public @interface Component { } 

De forma predeterminada, las anotaciones no se cargan en la memoria mientras se ejecuta el programa ( RetentionPolicy.CLASS ). Cambiamos este comportamiento a través de una nueva política de retención ( RetentionPolicy.RUNTIME ).

Ahora agregue @Component antes de las clases ProductService y antes de PromotionService .

 @Component public class ProductService { //... } @Component public class PromotionService { //... } 


Necesitamos BeanFactory escanear nuestro paquete ( com.kciray ) y encontrar clases en él anotadas por @Component . Esta tarea está lejos de ser trivial. No hay una solución preparada en Java Core, y tendremos que hacer una muleta nosotros mismos. Miles de aplicaciones de resorte utilizan escaneo de componentes a través de esta muleta. Has aprendido la terrible verdad. Tendrá que extraer los nombres de ClassLoader de ClassLoader y verificar ClassLoader terminan con ".class" o no, y luego construir su nombre completo y extraer objetos de clase de él.

Quiero advertirle de inmediato que habrá muchas excepciones marcadas, así que prepárese para envolverlas. Pero primero, decidamos qué queremos. Queremos agregar un método especial a BeanFactory y llamarlo en Main :

 //BeanFactory.java public class BeanFactory{ public void instantiate(String basePackage) { } } //Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray"); 

A continuación, necesitamos obtener ClassLoader . Es responsable de cargar las clases, y se extrae de manera bastante simple:

 ClassLoader classLoader = ClassLoader.getSystemClassLoader(); 

Probablemente ya haya notado que los paquetes están separados por un punto y los archivos por una barra diagonal. Necesitamos convertir la ruta del lote a la ruta de la carpeta y obtener algo como List<URL> (las rutas en su sistema de archivos donde puede buscar archivos de clase).

 String path = basePackage.replace('.', '/'); //"com.kciray" -> "com/kciray" Enumeration<URL> resources = classLoader.getResources(path); 

¡Espera un momento! Enumeration<URL> no es una List<URL> . ¿De qué se trata todo esto? Oh, horror, este es el antiguo progenitor de Iterator , disponible desde Java 1.0. Este es el legado con el que tenemos que lidiar. Si es posible recorrer Iterable usando for (todas las colecciones lo implementan), entonces, en el caso de Enumeration , tendrá que hacer un bypass de manejador, while(resources.hasMoreElements()) y nextElement() . Y, sin embargo, no hay forma de eliminar elementos de la colección. Solo 1996, solo hardcore. Ah, sí, en Java 9 agregaron el método Enumeration.asIterator() , para que pueda resolverlo.

Vamos más allá. Necesitamos extraer las carpetas y trabajar a través del contenido de cada una de ellas. Convierta la URL en un archivo y luego obtenga su nombre. Cabe señalar aquí que no escanearemos paquetes anidados para no complicar el código. Puede complicar su tarea y hacer una recursión si lo desea.

 while (resources.hasMoreElements()) { URL resource = resources.nextElement(); File file = new File(resource.toURI()); for(File classFile : file.listFiles()){ String fileName = classFile.getName();//ProductService.class } } 

A continuación, necesitamos obtener el nombre del archivo sin la extensión. En el patio en 2018, Java ha desarrollado File I / O (NIO 2) durante muchos años, pero aún no puede separar la extensión del nombre del archivo. Tengo que crear mi propia bicicleta, porque decidimos no utilizar bibliotecas de terceros como Apache Commons. lastIndexOf(".") el antiguo método de abuelo lastIndexOf(".") :

 if(fileName.endsWith(".class")){ String className = fileName.substring(0, fileName.lastIndexOf(".")); } 

A continuación, podemos obtener el objeto de clase usando el nombre completo de la clase (para esto llamamos la clase de la clase Class ):

 Class classObject = Class.forName(basePackage + "." + className); 

Bien, ahora nuestras clases están en nuestras manos. Además, solo queda destacar entre ellos aquellos que tienen la anotación @Component :

 if(classObject.isAnnotationPresent(Component.class)){ System.out.println("Component: " + classObject); } 

Corre y comprueba. La consola debería ser algo como esto:

 Component: class com.kciray.ProductService Component: class com.kciray.PromotionsService 

Ahora necesitamos crear nuestro bean. Debe hacer algo como el new ProductService() , pero para cada bean tenemos nuestra propia clase. Reflection in Java nos proporciona una solución universal (se llama al constructor predeterminado):

 Object instance = classObject.newInstance();//=new CustomClass() 

A continuación, tenemos que poner este bean en Map<String, Object> singletons . Para hacer esto, seleccione el nombre del bean (su id). En Java, llamamos variables como clases (solo la primera letra es minúscula). Este enfoque también se puede aplicar a los beans, porque Spring es un framework Java. Convierta el nombre del contenedor para que la primera letra sea pequeña y agréguela al mapa:

 String beanName = className.substring(0, 1).toLowerCase() + className.substring(1); singletons.put(beanName, instance); 

Ahora asegúrese de que todo funcione. El contenedor debe crear beans y se deben recuperar por nombre. Tenga en cuenta que el nombre de su método instantiate() y el nombre del método classObject.newInstance(); tener una raíz común Además, instantiate() es parte del ciclo de vida del frijol. ¡En Java, todo está interconectado!

 //Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray"); ProductService productService = (ProductService) beanFactory.getBean("productService"); System.out.println(productService);//ProductService@612 


Intente también implementar la anotación org.springframework.beans.factory.stereotype.Service . Realiza exactamente la misma función que @Component , pero se llama de manera diferente. Todo el asunto está en el nombre: demuestras que la clase es un servicio, no solo un componente. Esto es algo así como la tipificación conceptual. En la certificación de primavera hubo una pregunta: "¿Qué anotaciones son estereotipadas?" (de los mencionados) ". Entonces, las anotaciones estereotipadas son aquellas que están en el paquete de stereotype .

Rellena las propiedades


Mire el diagrama a continuación, muestra el comienzo del ciclo de vida del frijol. Lo que hicimos antes de esto es Instantiate (creando beans mediante newInstance() ). El siguiente paso es la inyección cruzada de beans (inyección de dependencia, también es la inversión de control (IoC)). Debe revisar las propiedades de los beans y comprender qué propiedades debe inyectar. Si llama a productService.getPromotionsService() , obtendrá null , porque dependencia aún no agregada.



Primero, cree el paquete org.springframework.beans.factory.annotation y agregue la anotación @Autowired . La idea es marcar los campos que son dependencias con esta anotación.

 @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { } 

A continuación, agréguelo a la propiedad:

 @Component public class ProductService { @Autowired PromotionsService promotionsService; //... } 

Ahora necesitamos enseñar a nuestra BeanFactory encontrar estas anotaciones e inyectar dependencias en ellas. Agregue un método separado para esto y llámelo desde Main :

 public class BeanFactory { //... public void populateProperties(){ System.out.println("==populateProperties=="); } } 

A continuación, solo tenemos que revisar todos nuestros bins en el mapa de singletons , y para cada bin ir a través de todos sus campos ( object.getClass().getDeclaredFields() devuelve todos los campos, incluidos los privados). Y verifique si el campo tiene una anotación @Autowired :

 for (Object object : singletons.values()) { for (Field field : object.getClass().getDeclaredFields()) { if (field.isAnnotationPresent(Autowired.class)) { } } } 

Luego, tenemos que revisar todos los contenedores una vez más y ver su tipo; de repente, este es el tipo que nuestro contenedor quiere tomar por sí mismo. ¡Sí, tenemos un ciclo tridimensional!

 for (Object dependency : singletons.values()) { if (dependency.getClass().equals(field.getType())) { } } 

Además, cuando encontramos la adicción, necesitamos inyectarla. Lo primero que puede pensar es escribir el campo de servicio de promotionsService usando la reflexión directamente. Pero la primavera no funciona así. Después de todo, si el campo tiene un modificador private , primero tendremos que configurarlo como public , luego escribir nuestro valor, luego configurarlo nuevamente como private (para mantener la integridad). Suena como una gran muleta. En lugar de una muleta grande, hagamos una muleta pequeña (formaremos el nombre del setter y lo llamaremos):

 String setterName = "set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);//setPromotionsService System.out.println("Setter name = " + setterName); Method setter = object.getClass().getMethod(setterName, dependency.getClass()); setter.invoke(object, dependency); 

Ahora ejecute su proyecto y asegúrese de que al llamar a productService.getPromotionsService() lugar de null , se devuelva nuestro bean.

Lo que hemos implementado es inyección por tipo. También hay una inyección por nombre (anotación javax.annotation.Resource ). Se diferencia en que, en lugar del tipo de campo, se extraerá su nombre y, según este, la dependencia del mapa. Aquí todo es similar, incluso en algo más simple. Le recomiendo que experimente y cree su propio bean, y luego lo inyecte con @Resource y extienda el método populateProperties() .

Apoyamos los frijoles que saben sobre su nombre.




Hay momentos en los que necesitas poner su nombre dentro del contenedor. Tal necesidad no surge a menudo, porque Los contenedores, en esencia, no deben conocerse entre sí y que son contenedores. En las primeras versiones de la primavera, se suponía que el bean es un POJO (Objeto de Java antiguo simple, el buen objeto de Java antiguo), y toda la configuración se representa en archivos XML y se separa de la implementación. Pero implementamos esta funcionalidad, ya que la inyección de nombre es parte del ciclo de vida del contenedor.

¿Cómo sabemos qué bean quiere saber cuál es su nombre y qué no quiere? Lo primero que viene a la mente es hacer una nueva anotación de tipo @InjectName y esculpirla en campos de tipo String. Pero esta solución será demasiado general y le permitirá dispararse en el pie muchas veces (coloque esta anotación en campos de tipos inapropiados (no String) o intente inyectar un nombre en varios campos de la misma clase). Hay otra solución, más precisa: crear una interfaz especial con un método de configuración. Todos los contenedores que lo implementan reciben su nombre. Cree la clase BeanNameAware en el paquete org.springframework.beans.factory :

 public interface BeanNameAware { void setBeanName(String name); } 

A continuación, deje que nuestro Servicio de PromotionsService implemente:

 @Component public class PromotionsService implements BeanNameAware { private String beanName; @Override public void setBeanName(String name) { beanName = name; } public String getBeanName() { return beanName; } } 

Y finalmente, agregue un nuevo método a la fábrica de frijoles. Aquí todo es simple: revisamos nuestro bin-singleton, verificamos si el bin implementa nuestra interfaz y llamamos al configurador:

 public void injectBeanNames(){ for (String name : singletons.keySet()) { Object bean = singletons.get(name); if(bean instanceof BeanNameAware){ ((BeanNameAware) bean).setBeanName(name); } } } 

Corre y asegúrate de que todo funcione:

 BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray"); beanFactory.populateProperties(); beanFactory.injectBeanNames(); //... System.out.println("Bean name = " + promotionsService.getBeanName()); 

Cabe señalar que en la primavera hay otras interfaces similares. Le recomiendo que implemente la interfaz BeanFactoryAware usted mismo , lo que permite que los beans reciban un enlace a la fábrica de beans. Se implementa de manera similar.

Inicializar frijoles




Imagine que tiene una situación en la que necesita ejecutar algo de código después de que se hayan inyectado las dependencias (se establecen las propiedades del contenedor). En términos simples, debemos darle al contenedor la capacidad de inicializarse. Alternativamente, podemos crear una interfaz InitializingBean y ponerle la firma del método void afterPropertiesSet() . La implementación de este mecanismo es exactamente la misma que la presentada para la interfaz BeanNameAware , por lo que la solución está bajo el spoiler. Practica y hazlo tú mismo en un minuto:

Solución de inicialización de frijoles
 //InitializingBean.java package org.springframework.beans.factory; public interface InitializingBean { void afterPropertiesSet(); } //BeanFactory.java public void initializeBeans(){ for (Object bean : singletons.values()) { if(bean instanceof InitializingBean){ ((InitializingBean) bean).afterPropertiesSet(); } } } //Main.java beanFactory.initializeBeans(); 



Agregar procesadores posteriores


Imagínese en el lugar de los primeros desarrolladores de primavera. Su marco está creciendo y es muy popular entre los desarrolladores, se envían cartas por correo todos los días con solicitudes para agregar una u otra característica útil. Si para cada característica agrega su propia interfaz y la verifica en el ciclo de vida del bean, entonces (el ciclo de vida) se obstruirá con información innecesaria. En su lugar, podemos crear una interfaz universal que le permita agregar algo de lógica (absolutamente ninguna, ya sea verificar la anotación, reemplazar el bin con otro bin, establecer algunas propiedades especiales, etc.).

Pensemos para qué sirve esta interfaz. Necesita realizar un procesamiento posterior de los beans, por lo tanto, puede llamarse BeanPostProcessor. Pero nos enfrentamos a una pregunta difícil: ¿cuándo se debe seguir la lógica? Después de todo, podemos ejecutarlo antes de la inicialización, pero podemos ejecutarlo después. Para algunas tareas, la primera opción es mejor, para otras, la segunda ... ¿Cómo ser?

Podemos habilitar ambas opciones a la vez. Deje que un postprocesador lleve dos lógicas, dos métodos. Uno se ejecuta antes de la inicialización (antes del método afterPropertiesSet() ) y el otro después. Ahora pensemos en los métodos mismos: ¿qué parámetros deberían tener? Obviamente, el Object bean sí ( Object bean ) debe estar allí. Por conveniencia, además del bean, puede pasar el nombre de este bean. Recuerda que el contenedor en sí mismo no conoce su nombre. Y no queremos obligar a todos los beans a implementar la interfaz BeanNameAware. Pero, en el nivel posterior al procesador, el nombre del bean puede ser muy útil. Por lo tanto, lo agregamos como el segundo parámetro.

¿Y qué debería devolver el método cuando se procesa el bean? Hagamos que devuelva el contenedor en sí. Esto nos da una gran flexibilidad, porque en lugar de un contenedor, puede deslizar un objeto proxy que envuelve sus llamadas (y agrega seguridad). O puede devolver completamente otro objeto volviendo a crear el contenedor nuevamente. Los desarrolladores tienen una gran libertad de acción. A continuación se muestra la versión final de la interfaz diseñada:

 package org.springframework.beans.factory.config; public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object bean, String beanName); Object postProcessAfterInitialization(Object bean, String beanName); } 

A continuación, necesitamos agregar una lista de procesadores simples a nuestra fábrica de frijoles y la capacidad de agregar otros nuevos. Sí, esta es una ArrayList regular.

 //BeanFactory.java private List<BeanPostProcessor> postProcessors = new ArrayList<>(); public void addPostProcessor(BeanPostProcessor postProcessor){ postProcessors.add(postProcessor); } 

Ahora cambie el método initializeBeans para que tenga en cuenta los postprocesadores:

 public void initializeBeans() { for (String name : singletons.keySet()) { Object bean = singletons.get(name); for (BeanPostProcessor postProcessor : postProcessors) { postProcessor.postProcessBeforeInitialization(bean, name); } if (bean instanceof InitializingBean) { ((InitializingBean) bean).afterPropertiesSet(); } for (BeanPostProcessor postProcessor : postProcessors) { postProcessor.postProcessAfterInitialization(bean, name); } } } 

Creemos un pequeño postprocesador que simplemente rastree las llamadas a la consola y agréguelo a nuestra fábrica de frijoles:

 public class CustomPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) { System.out.println("---CustomPostProcessor Before " + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) { System.out.println("---CustomPostProcessor After " + beanName); return bean; } } 

 //Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.addPostProcessor(new CustomPostProcessor()); 


Ahora corre y asegúrate de que todo funcione. Como tarea de capacitación, cree un postprocesador que proporcione la anotación @PostConstruct (javax.annotation.PostConstruct) . Proporciona una forma alternativa de inicializar (arraigado en Java, no en primavera). Su esencia es que coloca la anotación en algún método, y este método se denominará ANTES de la inicialización de resorte estándar (InitializingBean).

Asegúrese de crear todas las anotaciones y paquetes (incluso javax.annotation) manualmente, ¡no conecte las dependencias! Esto lo ayudará a ver la diferencia entre el núcleo de resorte y sus extensiones (soporte javax), y recordarlo. Esto mantendrá un estilo en el futuro.

Te interesará el hecho de que en un verdadero resorte la anotación @PostConstructse implementa de esta manera, a través del postprocesador CommonAnnotationBeanPostProcessor. Pero no mires allí, escribe tu implementación.

Por último, le recomiendo que agregue un método void close()a la clase BeanFactoryy resuelva dos mecanismos más. La primera es una anotación @PreDestroy (javax.annotation.PreDestroy), destinada a los métodos que deberían llamarse cuando el contenedor está cerrado. El segundo es la interfaz org.springframework.beans.factory.DisposableBeanque contiene el método void destroy(). Todos los contenedores que ejecutan esta interfaz tendrán la capacidad de destruirse a sí mismos (liberar recursos, por ejemplo).

@PreDestroy + Bean desechable
 //DisposableBean.java package org.springframework.beans.factory; public interface DisposableBean { void destroy(); } //PreDestroy.java package javax.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface PreDestroy { } //DisposableBean.java public void close() { for (Object bean : singletons.values()) { for (Method method : bean.getClass().getMethods()) { if (method.isAnnotationPresent(PreDestroy.class)) { try { method.invoke(bean); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } if (bean instanceof DisposableBean) { ((DisposableBean) bean).destroy(); } } } 



Ciclo de vida completo del frijol


Así que hemos implementado el ciclo de vida completo del contenedor, en su forma moderna. Espero que este enfoque te ayude a recordarlo.

Nuestro contexto favorito


Los programadores muy a menudo usan el término contexto, pero no todos entienden lo que realmente significa. Ahora pondremos todo en orden. Como señalé al principio del artículo, el contexto es la implementación del contenedor, así como también BeanFactory. Pero, además de las funciones básicas (DI), todavía agrega algunas características interesantes. Una de estas características es enviar y procesar eventos entre contenedores.

El artículo resultó ser demasiado grande y el contenido comenzó a cortarse, así que puse la información de contexto debajo del spoiler.

Nos damos cuenta del contexto
. org.springframework.context , ApplicationContext . BeanFactory . , close() .

 public class ApplicationContext { private BeanFactory beanFactory = new BeanFactory(); public ApplicationContext(String basePackage) throws ReflectiveOperationException{ System.out.println("******Context is under construction******"); beanFactory.instantiate(basePackage); beanFactory.populateProperties(); beanFactory.injectBeanNames(); beanFactory.initializeBeans(); } public void close(){ beanFactory.close(); } } 


Main , , :

 ApplicationContext applicationContext = new ApplicationContext("com.kciray"); applicationContext.close(); 

, . close() , « » - . , :

 package org.springframework.context.event; public class ContextClosedEvent { } 

ApplicationListener , . , ( ApplicationListener<E> ). , Java-, . , , :

 package org.springframework.context; public interface ApplicationListener<E>{ void onApplicationEvent(E event); } 

ApplicationContext . close() , , . ApplicationListener<ContextClosedEvent> , onApplicationEvent(ContextClosedEvent) . , ?

 public void close(){ beanFactory.close(); for(Object bean : beanFactory.getSingletons().values()) { if (bean instanceof ApplicationListener) { } } } 

Pero no . bean instanceof ApplicationListener<ContextClosedEvent> . Java. (type erasure) , <T> <Object>. , ? , ApplicationListener<ContextClosedEvent> , ?

, , . , , , , :

 for (Type type: bean.getClass().getGenericInterfaces()){ if(type instanceof ParameterizedType){ ParameterizedType parameterizedType = (ParameterizedType) type; } } 

, , , — . , :

 Type firstParameter = parameterizedType.getActualTypeArguments()[0]; if(firstParameter.equals(ContextClosedEvent.class)){ Method method = bean.getClass().getMethod("onApplicationEvent", ContextClosedEvent.class); method.invoke(bean, new ContextClosedEvent()); } 

ApplicationListener:

 @Service public class PromotionsService implements BeanNameAware, ApplicationListener<ContextClosedEvent> { //... @Override public void onApplicationEvent(ContextClosedEvent event) { System.out.println(">> ContextClosed EVENT"); } } 

, Main , , :

 //Main.java void testContext() throws ReflectiveOperationException{ ApplicationContext applicationContext = new ApplicationContext("com.kciray"); applicationContext.close(); } 


Conclusión


Inicialmente, planeé este artículo para Baeldung en inglés, pero luego pensé que la audiencia del Habré podría evaluar positivamente este enfoque de capacitación. Si le gustaron mis ideas, asegúrese de apoyar el artículo. Si obtiene una calificación de más de 30, entonces prometo continuar. Al escribir el artículo, traté de mostrar exactamente el conocimiento de Spring Core, que se usa con mayor frecuencia, y también basado en la Guía de estudio de certificación Core Spring 5.0 . En el futuro, con la ayuda de dichos tutoriales, puede cubrir toda la certificación y hacer que la primavera sea más accesible para los desarrolladores de Java.

Actualización 10/05/2018


Constantemente me llegan cartas con preguntas "y cuando la continuación, lo estamos esperando". Pero no hay tiempo en absoluto, y otros proyectos personales son una prioridad. Sin embargo, si a uno de ustedes realmente le gustó la idea, puede estudiar la sección estrecha de la primavera y escribir un artículo de continuación. Si no tiene una cuenta habr, entonces puedo publicar un artículo de mi cuenta o ayudarlo a obtener una invitación.

Distribución de temas:
Spring Container - [nombre de usuario]
Spring AOP - [nombre de usuario]
Spring Web - [nombre de usuario]
Spring Cloud - [nombre de usuario]

Source: https://habr.com/ru/post/es419679/


All Articles