在日常开发中我们可能需要动态的来加载某些功能,而且需要方便的开启关闭和整合,那么这个时候我们就可以利用 @Import这个注解来实现这一特性。
以Mybatis为例子,在我们想使用Mybatis的时候可以可选的方法有在启动来上加@MapperSacn这个注解来进行Mybatis的整合,如果不想用我们也只需要把这个注解去掉就可以。那我如果我也想有这样的特性我们应该怎么做呢,首先@Import是一个需要添加在类上面的注解而且在运行阶段是可以解析到的(如果这个你不太懂为什么请看@Import注解类的源码和这篇文章),这个注解可以放到另一个注解类上例如HxlEnable,这里注解里面有一个参数(Class>[] value()) 这个地方我们有三种方式使用它。
1、可以传一个类或者一堆类进去例如@Import({A.class,B.class}) 他会把这两个当做bean添加到beanFactory中让我们在其他地方注入
@Import(TestImport.class) //这个注解被加到了另一个注解的类上面 当然它不只是可以添加到注解类上
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HxlEnable { //定义了一个HxlEnable注解
}
@Slf4j
public class TestImport { //等待被注入的bean
public void hello() {
log.info("hello");
}
}
@HxlEnable //自定义的标签
public class DemoApplication { //启动类
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@Test
public void should_import() { //测试代码
TestImport testImport = applicationContext.getBean(TestImport.class); //获取一个类型为TestImport的bean
log.info("factory bean {}", testImport.getClass());
testImport.hello();
}
上测试日志截图
那这个时候有人就会问了这和我在类上面加个@Component、@Service从使用上有什么区别吗?你别着急他的作用来了,当我们把@HxlEnable从启动类上去掉后他找不到这个bean了,这就是他的作用,加不加载这个bean是看你加没加@HxlEnable注解,当你有一堆类要动态加载的时候就可以用这种方式解决。
2、ImportSelector方式这种方式会比第一种更加的实用一些,这种方法我们可以传入一个实现了ImportSelector接口的类,要动态加载的类在要实现的方法selectImports的返回值上,不多比比写个实例给你看。
public class HxlImportSelector implements ImportSelector { //实现了ImportSelector的类
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {TestImport.class.getName()}; //返回值是要动态加载的类的类名
}
}
@Import(HxlImportSelector.class)//只有这里和第一种方法变了
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HxlEnable {
}
测试代码不变,测试依然通过了图就不放了和第一种方法的测试图相同。
3.ImportBeanDefinitionRegistrar方式这种方式需要给@Import传一个实现了ImportBeanDefinitionRegistrar接口的类,然后你可以在方法registerBeanDefinitions中得到BeanDefinitionRegistry,然后你都拿到BeanDefinitionRegistry你不就是想干嘛就干嘛,为所欲为之为所欲为。上代码
public class HxlImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(TestImport.class);
registry.registerBeanDefinition("testImport", rootBeanDefinition); //给registry 里添加 一个bean
}
}
@Import(HxlImportBeanDefinitionRegistrar.class) //只有这里变了
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HxlEnable {
}
测试代码依然能过图就不贴了,以上就是三种使用方式。
2、从源码角度分析@Import
如果你不知道spring是怎运行到处理Import注解这一步的建议看这个,我们现在就来看看spring是怎么处理@Import的,
在ConfigurationClassParser.java中调用方法processImports之前先用getImports(sourceClass)获得了当前类上加了@Import注解的元信息,在getImports方法内部有一个递归方法,这个递归方法获取了当前类上注解里面加了@Import的value里面的值,然后递归不是@Import的注解,把不是@Import的注解都解析一遍,这样一直递归下去 就找到了所有的@Import里面的value的值,这个时候是递归注解,对value里面没有处理,递归完之后传给processImports方法。
在processImports方法里,做了对value里面的值(也就是class)做了判断,
a.如果这个class实现了ImportSelector接口,他就先用ParserStrategyUtils.instantiateClass方法创建这个实例再看是否有过滤器和是否实现的DeferredImportSelector接口,如果没问题则调用这个类实现的selectImports方法拿到要注入的class文件,然后再扫描这个类上面的@Import注解调用, 其中DeferredImportSelector是ImportSelector的子类,起到延时加载的作用
b.如果没有实现ImportSelector接口,则判断是否实现了ImportBeanDefinitionRegistrar接口,如果实现了ImportBeanDefinitionRegistrar接口则创建实例并调用addImportBeanDefinitionRegistrar方法加入importBeanDefinitionRegistrar后面处理
c.如果都没实现则他会把你作为配置了,这样就回到了@Configuration的处理逻辑上他调用了processConfigurationClass方法来处理
这三个是三选一的
//ConfigurationClassParser.java
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
//处理非延迟的
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
}
//处理延迟的 (就在这处理的延迟的那些, 下面说的是这里)
this.deferredImportSelectorHandler.process();
}
//扫描当前类加了@Import的注解
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
Set<SourceClass> imports = new LinkedHashSet<>();
Set<SourceClass> visited = new LinkedHashSet<>();
collectImports(sourceClass, imports, visited);
return imports;
}
//这是个递归方法,扫描到注解后他又去扫描了注解的注解
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {
if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
//processImports 方法中的片段
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) { //是否实现了ImportSelector
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry); // 创建实例
Predicate<String> selectorFilter = selector.getExclusionFilter();
if (selectorFilter != null) { //检测过滤器
exclusionFilter = exclusionFilter.or(selectorFilter);
}
if (selector instanceof DeferredImportSelector) { //如果是延迟的就放到deferredImportSelectorHandler中 延迟处理 上面有些
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
//如果实现了ImportBeanDefinitionRegistrar就把它当成一个BeanDefinitionRegistrar处理
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
//如果都没实现则认为是个配置类按照处理@Configuration的逻辑
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
至此你已经了解了@Import的处理,如果有疑问欢迎评论区或者私信