1. Información general
En este tutorial, aprenderemos cómo usar la anotación Spring @Import mientras aclaramos en qué se diferencia de @ ComponentScan .
2. Configuración y Beans
Antes de comprender la anotación @Import , necesitamos saber qué es un Spring Bean y tener un conocimiento práctico básico de la anotación @ Configuration .
Ambos temas están fuera del alcance de este tutorial. Aún así, podemos aprender sobre ellos en nuestro artículo de Spring Bean y en la documentación de Spring.
Supongamos que ya hemos preparado tres beans: Bird , Cat y Dog , cada uno con su propia clase de configuración.
Luego, podemos proporcionar nuestro contexto con estas clases de configuración :
@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = { BirdConfig.class, CatConfig.class, DogConfig.class }) class ConfigUnitTest { @Autowired ApplicationContext context; @Test void givenImportedBeans_whenGettingEach_shallFindIt() { assertThatBeanExists("dog", Dog.class); assertThatBeanExists("cat", Cat.class); assertThatBeanExists("bird", Bird.class); } private void assertThatBeanExists(String beanName, Class beanClass) { Assertions.assertTrue(context.containsBean(beanName)); Assertions.assertNotNull(context.getBean(beanClass)); } }
3. Agrupar configuraciones con @Import
No hay problema en declarar todas las configuraciones. Pero imagine el problema de controlar docenas de clases de configuración dentro de diferentes fuentes . Debería haber una forma mejor.
La anotación @ Import tiene una solución, por su capacidad para agrupar clases de configuración :
@Configuration @Import({ DogConfig.class, CatConfig.class }) class MammalConfiguration { }
Ahora, solo necesitamos recordar a los mamíferos :
@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = { MammalConfiguration.class }) class ConfigUnitTest { @Autowired ApplicationContext context; @Test void givenImportedBeans_whenGettingEach_shallFindOnlyTheImportedBeans() { assertThatBeanExists("dog", Dog.class); assertThatBeanExists("cat", Cat.class); Assertions.assertFalse(context.containsBean("bird")); } private void assertThatBeanExists(String beanName, Class beanClass) { Assertions.assertTrue(context.containsBean(beanName)); Assertions.assertNotNull(context.getBean(beanClass)); } }
Bueno, probablemente olvidaremos nuestro Bird pronto, así que hagamos un grupo más para incluir todas las clases de configuración de animales :
@Configuration @Import({ MammalConfiguration.class, BirdConfig.class }) class AnimalConfiguration { }
Finalmente, nadie se quedó atrás, y solo necesitamos recordar una clase:
@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = { AnimalConfiguration.class }) class AnimalConfigUnitTest { // same test validating that all beans are available in the context }
4. @Import vs @ComponentScan
Antes de continuar con los ejemplos de @ Import , hagamos una parada rápida y compárelo con @ ComponentScan .
4.1. Similitudes
Ambas anotaciones pueden aceptar cualquier clase @Component o @Configuration .
Agreguemos un nuevo @ Componente usando @ Import :
@Configuration @Import(Bug.class) class BugConfig { } @Component(value = "bug") class Bug { }
Ahora, el bean Bug está disponible como cualquier otro bean.
4.2. Diferencia conceptual
En pocas palabras, podemos alcanzar el mismo resultado con ambas anotaciones . Entonces, ¿hay alguna diferencia entre ellos?
Para responder a esta pregunta, recordemos que Spring generalmente promueve el enfoque de convención sobre configuración.
Haciendo una analogía con nuestras anotaciones, @ ComponentScan es más como una convención, mientras que @ Import parece una configuración .
4.3. Qué sucede en las aplicaciones reales
Normalmente, iniciamos nuestras aplicaciones usando @ ComponentScan en un paquete raíz para que pueda encontrar todos los componentes por nosotros. Si usamos Spring Boot, @SpringBootApplication ya incluye @ComponentScan , y estamos listos para comenzar . Esto muestra el poder de la convención.
Ahora, imaginemos que nuestra aplicación está creciendo mucho. Ahora tenemos que lidiar con beans de diferentes lugares, como componentes, diferentes estructuras de paquetes y módulos creados por nosotros y terceros.
En este caso, agregar todo al contexto corre el riesgo de iniciar conflictos sobre qué bean usar. Además de eso, es posible que tengamos un tiempo de inicio lento.
Por otro lado, no queremos escribir una @ Import para cada nuevo componente porque hacerlo es contraproducente.
Tomemos a nuestros animales, por ejemplo. De hecho, podríamos ocultar las importaciones de la declaración de contexto, pero aún necesitamos recordar el @ Import para cada clase de configuración .
4.4. Trabajando juntos
Podemos apuntar a lo mejor de ambos mundos. Imaginemos que tenemos un paquete solo para nuestros animales . También podría ser un componente o módulo y mantener la misma idea.
Entonces podemos tener un @ ComponentScan solo para nuestro paquete animal :
package com.baeldung.importannotation.animal; // imports... @Configuration @ComponentScan public class AnimalScanConfiguration { }
Y un @Import para mantener el control sobre lo que agregaremos al contexto:
package com.baeldung.importannotation.zoo; // imports... @Configuration @Import(AnimalScanConfiguration.class) class ZooApplication { }
Finalmente, nuestro contexto encontrará automáticamente cualquier bean nuevo agregado al paquete animal. Y todavía tenemos un control explícito sobre las configuraciones que estamos usando.
5. Conclusión
En este tutorial rápido, aprendimos cómo usar @Import para organizar nuestras configuraciones.
También aprendimos que @Import es muy similar a @ ComponentScan , excepto por el hecho de que @ Import tiene un enfoque explícito, mientras que @ ComponentScan usa uno implícito .
Además, analizamos las posibles dificultades para controlar nuestras configuraciones en aplicaciones reales y cómo tratarlas combinando ambas anotaciones.
Como de costumbre, el código completo está disponible en GitHub.