@Import原理与实战
文章目录
- 前言
- 一、导入普通类
- 二、导入ImportSelector实现类
- 三、导入ImportBeanDefinitionRegistrar实现类
- 四、@Import注解的解析
- 4.1、解析实现ImportSelector的候选bean
- 4.2、解析实现ImportBeanDefinitionRegistrar的候选bean
- 4.3、DeferredImportSelector的特殊处理
- 总结
前言
@Import
是Spring框架提供的一个核心注解,主要用于在配置类中引入其他配置类或组件。通过在类上标注@Import
注解,可以将其value属性中指定的类注册到Spring容器中,从而实现配置的模块化和灵活组合。
一、导入普通类
@Import
注解会将其中导入的普通类,注册成bean放入到Spring容器中。
public class Demo1 {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);System.out.println(context.getBean(MyService.class));}
}class MyService {
}@Configuration
@Import(MyService.class)
class Config {}
运行结果:
com.itbaima.importdemo.demo1.MyService@55183b20
二、导入ImportSelector实现类
解析@Import
注解时会执行实现了ImportSelector
的类的selectImports
方法,将返回的数组中的类注册成bean。
public class Demo2 {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config1.class);System.out.println(context.getBean(MyService.class));}
}@Configuration
@Import(MyImport.class)
class Config1{}class MyImport implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {return new String[]{"com.itbaima.importdemo.demo1.MyService"};}
}
运行结果
com.itbaima.importdemo.demo1.MyService@33d512c1
三、导入ImportBeanDefinitionRegistrar实现类
实现了ImportBeanDefinitionRegistrar
的类,可以自己注册bean定义,以及从已有的bean定义中获取指定的bean,进行修改,自由度是最高的。
public class Demo3 {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);System.out.println(context.getBean("orderService"));}
}@Configuration
@Import({MyBeanDefinitionRegistrar.class})
class Config {
}class MyBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry) {GenericBeanDefinition bd = new GenericBeanDefinition();bd.setBeanClass(MyService.class);registry.registerBeanDefinition("orderService", bd);}
}class MyService {
}
运行结果:
com.itbaima.importdemo.demo2.MyService@2f9f7dcf
四、@Import注解的解析
@Import
注解的解析,体现在refresh方法中的invokeBeanFactoryPostProcessors
(调用所有bean工厂后置处理器)这一步:
ConfigurationClassPostProcessor
,用于配置类的解析:
在org.springframework.context.annotation.ConfigurationClassParser
的processImports
中完成对于@Import
注解的解析:
4.1、解析实现ImportSelector的候选bean
对于候选bean实现了ImportSelector
接口的处理:
- 利用JVM的类加载机制,对于候选bean进行加载。
- 使用Spring提供的工具方法
instantiateClass
反射创建这个ImportSelector
实例,并注入必要的环境参数。 - 如果候选bean实现了
DeferredImportSelector
,则将其加入deferredImportSelectors
的集合中,延迟处理 - 候选bean实现的是
ImportSelector
接口,就调用目标类重写的selectImports
方法,决定要导入哪些类名。然后把类名数组转换成SourceClass
的集合,并应用排除过滤器。
调用目标类重写的selectImports
方法,传入的参数就是加入了@Configuration
注解的配置类的元信息。最后一步递归调用processImports
方法很关键:假设当前第一次调用processImports
方法,解析出的类路径是com.itbaima.importdemo.demo1.MyService,那么在递归调用processImports
方法时,再次进入
循环中进行解析时,由于MyService没有实现ImportSelector
或ImportBeanDefinitionRegistrar
注解,就会进入最后的else分支:(@Import导入的普通bean,进入的也是该分支)
在最后的else分支中,还会去递归调用processConfigurationClass
,将MyService当做配置类去解析,最终将解析完成的放入到ConfigurationClass
的集合中。(注意,通过Import解析出的类路径下的bean,bean名称为空,后续需要再次进行处理。)
this.reader.loadBeanDefinitions(configClasses);
这一步,会对于@Import导入的普通bean进行处理:
生成一个beanName,并且将bean定义注册到bean工厂中:
4.2、解析实现ImportBeanDefinitionRegistrar的候选bean
@Import
导入实现ImportBeanDefinitionRegistrar
的候选bean的解析逻辑,在else…if的分支中,同样是通过JVM的类加载机制,加载候选类的class类,然后使用Spring提供的工具方法instantiateClass
反射创建这个ImportBeanDefinitionRegistrar
实例,并注入必要的环境参数。
最后将其加入到ConfigurationClass
的importBeanDefinitionRegistrars
属性中:
它的解析同样是在processConfigBeanDefinitions
的this.reader.loadBeanDefinitions(configClasses);
中:
回调用户重写的registerBeanDefinitions
的逻辑。
4.3、DeferredImportSelector的特殊处理
用户实现ImportSelector
时,有一种特殊的情况,即用户实现了DeferredImportSelector
,DeferredImportSelector
是ImportSelector
的子类。在该分支中,仅仅是先将其加入DeferredImportSelectorHandler
的deferredImportSelectors
属性中:
DeferredImportSelectorHandler
的deferredImportSelectors
属性是一个集合:
最终会在所有bean解析完成后再去进行解析:
该机制主要应用于Spring Boot的自动配置场景,其核心作用是确保用户自定义的Bean能够优先于自动配置的Bean执行。由于spring.factories中定义的自动配置Bean通常采用条件装配机制,当容器中已存在用户自定义的同类型Bean时,系统将不再重复装配。这正是通过@Bean注解添加的Bean能够覆盖默认Bean的原因
在doProcessConfigurationClass
中,默认的@Bean
的注解,是后于@Import
注解解析的。如果你不显式允许覆盖,Spring 在注册 BeanDefinition 时会抛出异常或者忽略重复的注册。
但是对于DeferredImportSelector
的解析,是在doProcessConfigurationClass
的外层parse
方法中执行的,后于@Bean
注解的解析。
总结
@Import注解是为了替换掉配置文件中的import标签,主要是为了导入第三方的配置类,除此之外:
- 可以在配置类中指定@Import注解,将其中的类注入到容器中。
- 可以在配置类中指定@Import注解,同时其中的类实现了Import Selector接口,会执行重写的selectImports方法,并且将其中指定的路径的对象注入到容器中。
- 如果导入的类型实现了Import BeanDefinitionRegistrar,则可以自己注册 BeanDefinition,自由度更高。