日积月累,水滴石穿 😄
@Import注解使用详解
@Import注解可以用来导入配置类,功能与 xml 中的 import 标签等效。
Import注解定义源码
/**
* 可以导入的一个或多个 @Configuration 类
Indicates one or more {@link Configuration @Configuration} classes to import.
* @Import的功能与 xml 中的 <import/>标签等效
* <p>Provides functionality equivalent to the {@code <import/>} element in Spring XML.
允许导入 @Configuration 类 、ImportSelector 和 ImportBeanDefinitionRegistrar 的实现 以及导入普通类(4.2版本开始支持;类似 AnnotationConfigApplicationContext#register(java.lang.Class<?>)
* Allows for importing {@code @Configuration} classes, {@link ImportSelector} and
* {@link ImportBeanDefinitionRegistrar} implementations, as well as regular component
* classes (as of 4.2; analogous to {@link AnnotationConfigApplicationContext#register}).
*
* 可以在类级别声明或作为元注释。
* <p>May be declared at the class level or as a meta-annotation.
* 如果需要导入XML 或其他非bean 定义资源,请改用@ImportResource注解
* <p>If XML or other non-{@code @Configuration} bean definition resources need to be
* imported, use the {@link ImportResource @ImportResource} annotation instead.
* @since 3.0
* @see Configuration
* @see ImportSelector
* @see ImportResource
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
* or regular component classes to import.
*/
Class<?>[] value();
}
Import
注解源码的定义非常简单,就一个属性 value
,而且是一个 Class
类型的数组。
结合上面的注释,对我们了解Import
有很大的帮助。
- 可以同时导入多个
@Configuration
类 、ImportSelector 和 ImportBeanDefinitionRegistrar
的实现,以及导入普通类(4.2版本开始支持) @Import
的功能与 xml 中的<import/>
标签等效- 在类级别声明或作为元注释
- 如果需要导入XML 或其他非bean 定义资源,请使用
@ImportResource
注解
支持的三种类型
- 实现
ImportSelector
接口的类 - 实现
ImportBeanDefinitionRegistrar
接口的类 - 不是上述两种情况,将类作为配置类处理。
接下来小杰带大家来看看使用过程。
代码测试
@Import导入普通类
- 提供
ImportTest
类,该类没有任何注解标注
public class ImportTest {
public void importTest(){
System.out.println("ImportTest === >");
}
}
- 扫描并使用
@Import
导入ImportTest
类,进行启动
@ComponentScan(basePackages = "com.cxyxj.importdemo")
@Import({ImportTest.class})
public class AppMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppMain.class);
// 从容器中获得 ImportTest
ImportTest bean = context.getBean(ImportTest.class);
bean.test();
}
}
结果:
ImportTest === >
@Import导入配置类
这里说的配置类是被 @Configuration
标注的类。
@Configuration
public class ImportTest {
public void test(){
System.out.println("ImportTest === >");
}
}
- 启动结果和导入普通类一致。区别在于 被
@Configuration
标注的类会被 CGLIB 进行代理。
实现 ImportSelector
基本使用
- 创建
ImportSelectorComponent
类,该类会被导入。
public class ImportSelectorComponent {
public void test(){
System.out.println("ImportSelectorComponent === >");
}
}
- 创建
ImportSelectorTest
类,实现ImportSelector
接口,并重写selectImports
方法,方法返回的是需要导入类的全限定名的数组。
public class ImportSelectorTest implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//return new String[]{"com.cxyxj.importdemo.ImportSelectorComponent"};
return new String[]{ImportSelectorComponent.class.getName()};
}
}
@Import
导入的值修改为ImportSelectorTest.class
,并进行启动
@ComponentScan(basePackages = "com.cxyxj.importdemo")
@Import({ImportSelectorTest.class})
public class AppMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppMain.class);
//这里也要修改:获得 ImportSelectorComponent
ImportSelectorComponent bean = context.getBean(ImportSelectorComponent.class);
bean.test();
// 打印 bean 名称
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String name : beanDefinitionNames){
System.out.println(name);
}
}
}
结果:
ImportSelectorComponent === >
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
appMain
com.cxyxj.importdemo.ImportSelectorComponent
注意被ImportSelector
导入的类的 beanName 是类的全限定名称,实现 ImportSelector 的类不会被注入容器中。
效果达到之后,你会发现这种使用方式有点繁琐,还要写一个实现类。觉得并没有什么用,直接使用 import
直接导入不更优雅吗。的确是的,如果是导入固定的 Bean 定义,这种方式肯定不是最优的。所以它的用途在其他方向,比如需要进行一些逻辑处理之后,进行动态注册时,该方式是非常有用的。在 Spring
、Spring Boot
底层大量使用该方式进行导入Bean定义。
源码定义
/**
* Interface to be implemented by types that determine which @{@link Configuration}
* class(es) should be imported based on a given selection criteria, usually one or
* more annotation attributes.
*
* <p>An {@link ImportSelector} may implement any of the following
* {@link org.springframework.beans.factory.Aware Aware} interfaces,
* and their respective methods will be called prior to {@link #selectImports}:
* <ul>
* <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
* <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}</li>
* <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}</li>
* <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}</li>
* </ul>
*
* <p>{@code ImportSelector} implementations are usually processed in the same way
* as regular {@code @Import} annotations, however, it is also possible to defer
* selection of imports until all {@code @Configuration} classes have been processed
* (see {@link DeferredImportSelector} for details).
*
* @author Chris Beams
* @since 3.1
* @see DeferredImportSelector
* @see Import
* @see ImportBeanDefinitionRegistrar
* @see Configuration
*/
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
ImportSelector
接口的源码也是很简单的。接口中就一个selectImports
方法。方法的参数是AnnotationMetadata
,通过该参数我们可以获得被 @Import
标注的类的各种信息,比如:Class名称,实现的接口名称、父类名称、类上的其他注解信息。
代码示例小杰就不写了,我们看看 Spring
的处理方式。找到ImportSelector
接口的抽象子类 AdviceModeImportSelector
,当我们在启动类上加上 @EnableAsync
或者@EnableCaching
就会进行到这里。
代码意思也不阐述了,打个断点跑一遍就清楚了。
我们来看看接口上的注释,大意如下:
由类实现的接口,可以导入一个或者多个类。
该接口的实现类同时实现EnvironmentAware, BeanFactoryAware ,BeanClassLoaderAware、ResourceLoaderAware,那么在调用其 selectImports 方法之前先调用上述接口中对应的方法。
ImportSelector实现的处理方式通常与常规的@Import注解相同,如果想延迟导入,可以使用 ImportSelector 的子接口 DeferredImportSelector,它会在所有 @Configuration类都被处理完之后,才开始进行导入。
DeferredImportSelector
与 SpringBoot自动装配
有很大的关系,这个到Spring Boot
系列再说。
实现 ImportBeanDefinitionRegistrar
基本使用
- 创建
ImportBeanDefinitionRegistrarComponent
普通类
public class ImportBeanDefinitionRegistrarComponent {
public void test(){
System.out.println("ImportBeanDefinitionRegistrarComponent === >");
}
}
- 创建
ImportBeanDefinitionRegistrarTest
,实现ImportBeanDefinitionRegistrar
接口,并重写registerBeanDefinitions
方法
public class ImportBeanDefinitionRegistrarTest implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// containsBeanDefinition:判断容器中是否存在指定的 bean 定义,true 存在
boolean b = registry.containsBeanDefinition(ImportBeanDefinitionRegistrarComponent.class.getName());
if (!b){
RootBeanDefinition beanDefinition = new RootBeanDefinition(ImportBeanDefinitionRegistrarComponent.class);
//注册BeanDefinition,并指定 beanName
registry.registerBeanDefinition("registrarComponent", beanDefinition);
}
}
}
@Import
导入的值修改为ImportBeanDefinitionRegistrarTest.class
,并进行启动
@ComponentScan(basePackages = "com.cxyxj.importdemo")
@Import({ImportBeanDefinitionRegistrarTest.class})
public class AppMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppMain.class);
//这里也要修改:获得 ImportBeanDefinitionRegistrarComponent
ImportBeanDefinitionRegistrarComponent bean = context.getBean(ImportBeanDefinitionRegistrarComponent.class);
bean.test();
// 打印 bean 名称
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String name : beanDefinitionNames){
System.out.println(name);
}
}
}
结果:
ImportBeanDefinitionRegistrarComponent === >
注意被ImportBeanDefinitionRegistrar
注册的类的 beanName 是可以自定义的,实现 ImportBeanDefinitionRegistrar
的类不会被注入容器中。
源码释义跟 ImportSelector
差不多,就不在阐述。
自定义注解
接下来小杰将使用 ImportBeanDefinitionRegistrar
实现,添加了我们自定义的@Cxyxj
注解的类会被自动加入到Spring容器中。
- 自定义注解 Cxyxj
/**
* 自定义注解
* @author cxyxj
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Cxyxj {
}
- 修改
ImportBeanDefinitionRegistrarTest
类的代码,修改如下:
public class ImportBeanDefinitionRegistrarTest implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获得 ComponentScan 注解
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
// 获得 basePackages 的信息
String[] basePackages = (String[]) annotationAttributes.get("basePackages");
// 扫描器,这也是Spring底层扫描使用的
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
// 满足任意 includeFilters的会被扫描
scanner.addIncludeFilter(new AnnotationTypeFilter(Cxyxj.class));
// 继续扫描
scanner.scan(basePackages);
}
}
- 启动类
@ComponentScan(basePackages = "com.cxyxj.importdemo")
@Import({ImportBeanDefinitionRegistrarTest.class})
public class AppMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppMain.class);
CxyxjTest bean = context.getBean(CxyxjTest.class);
bean.cxyxjTest();
// 打印 bean 名称
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String name : beanDefinitionNames){
System.out.println(name);
}
}
}
该效果使用 ImportSelector
也是可以实现的。
- 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。