通过@Import注解把类注入容器的四种方式

1. @Import导入的四种方式

          @Import是spring中的一个注解,在其他框架与spring整合时应用广泛,比如redis的自动配置RedisAutoConfiguration,就是通过@Import注解在容器启动时为容器中添加redis的环境配置。一个外来组件想要与spring整合,自然是要把一些配置提前布局在容器中的。

先看一下@Import注解的源码注释

@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可以导入四种对象,分别是:

  • 普通类
  • @Configuration配置类
  • 实现了ImportSelector接口的类
  • 实现了ImportBeanDefinitionRegistrar接口的类

下面就来看一下@Import注解把类注入容器的几种方式
 
 

2. 准备工作

  • 创建四个配置类:ConfigA、ConfigB、ConfigC、ConfigD。其中ConfigB中增加@Configuration注解,表示为配置类,其余三个均为普通类。

    public class ConfigA {
    
        public void print() {
            System.out.println("输出:ConfigA.class");
        }
    }
    
    @Configuration
    public class ConfigB {
    
        public void print() {
            System.out.println("输出:ConfigB.class");
        }
    
    }
    
    public class ConfigC {
    
        public void print() {
            System.out.println("输出:ConfigC.class");
        }
    
    }
    
    public class ConfigD {
        
        public void print() {
            System.out.println("输出:ConfigD.class");
        }
    
    }
    
  • 再创建一个主配置类Config,并试图通过@Resource注解将上面四个配置类进行注入。当然,这样是不成功的,还需要将它们进行导入。

    @Configuration
    public class Config {
    
        @Resource
        ConfigA configA;
    
        @Resource
        ConfigB configB;
    
        @Resource
        ConfigC configC;
    
        @Resource
        ConfigD configD;
    
    
        public void print() {
            configA.print();
            configB.print();
            configC.print();
            configD.print();
        }
    }
    
  • 准备一个main方法,运行时执行一下Config类的print()方法!

    public class ImportDemo {
    
        public static void main(String[] args) {
            AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
            Config config = ctx.getBean(Config.class);
            config.print();
        }
    }
    

运行时报错:说明ConfigA、ConfigB、ConfigC、ConfigD都还未曾注入容器,获取不到!

在这里插入图片描述

接下来使用@Improt注解,通过四种方式把ConfigA、ConfigB、ConfigC、ConfigD注入容器再来看结果

 

①:导入普通类ConfigA

@Configuration
@Import(ConfigA.class)
public class Config {
   ...
}

 

②:导入@Configuration配置类ConfigB

导入配置类与导入普通类一样,在@Import注解中传入目标类的Class对象。

@Configuration
@Import({ConfigA.class,
        ConfigB.class})
public class Config {
    ...
}

 

③:导入ImportSelector的实现类

     ImportSelector接口的全类名为org.springframework.context.annotationImportSelector。其主要作用的是收集需要导入的配置类,并根据条件来确定哪些配置类需要被导入。

该接口的实现类同时还可以实现以下任意一个Aware接口,它们各自的方法将在selectImport之前被调用:

  • EnvironmentAware
  • BeanFactoryAware
  • BeanClassLoaderAware
  • ResourceLoaderAware

另外,该接口实现类可以提供一个或多个具有以下形参类型的构造函数:

  • Environment
  • BeanFactory
  • ClassLoader
  • ResourceLoader

DeferredImportSelector的作用

public interface DeferredImportSelector extends ImportSelector 

          DeferredImportSelector是一个接口,继承了ImportSelector接口,如果你想要推迟导入配置类,直到处理完所有的@Configuration。那么你可以使用DeferredImportSelector

         springboot的启动类注解@SpringBootApplication的元注解EnableAutoConfiguration,就通过@Import导入了一个实现DeferredImportSelectorAutoConfigurationImportSelector,目的就是等到所有spring原生的配置类导入完毕后,在导入一些第三方整合过来的配置类!

在这里插入图片描述
AutoConfigurationImportSelector继承关系如下
在这里插入图片描述

下面回归正题,创建一个实现该接口的类 MyImportSelector

         需要重写selectImports方法,入参AnnotationMetadata为主配置类 Config 的注解元数据。返回值为目标配置类 ConfigC 的全类名,这里是一个数组,表示可以导入多个配置类。

public class MyImportSelector implements ImportSelector {
    
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.chinalife.demo.config.ConfigC"};
    }
}

在配置类 Config 中导入 MyImportSelector 类。

@Configuration
@Import({ConfigA.class,
        ConfigB.class,
        MyImportSelector.class})
public class Config {
    ...
}

 

④:导入ImportBeanDefinitionRegistrar的实现类

          通过注册bean定义的方式注册bean,可以修改bean的属性,比如懒加载、构造方法、甚至是.Class,功能强大,可以改变类的本质!只需自定义一个类,实现ImportBeanDefinitionRegistrar 接口,并重写registerBeanDefinitions过程即可,在自定义类被加载过程中,容器会顺带执行registerBeanDefinitions帮我们注册指定的类。

          创建一个实现 ImportBeanDefinitionRegistrar 接口的类 MyImportBeanDefinitionRegistrar,并在 registerBeanDefinitions方法中注册 configD 类。

  • 入参 AnnotationMetadata为主配置类 Config 的注解元数据;
  • 入参BeanDefinitionRegistry可以注册Bean的定义信息。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        registry.registerBeanDefinition("configD", new RootBeanDefinition(ConfigD.class));
    }
}

在配置类 Config 中导入 MyImportBeanDefinitionRegistrar 类。

@Configuration
@Import({ConfigA.class,
        ConfigB.class,
        MyImportSelector.class,
        MyImportBeanDefinitionRegistrar.class})
public class Config {
    ...
}

 

3. 测试结果

在这里插入图片描述

 

4. 源码解读

spring源码中对@Import的处理是在refresh()中的invokeBeanFactoryPostProcessors方法中,更多细节请查看 Spring源码解读!,下面只贴出处理@Import的源码!

@Nullable
protected final SourceClass doProcessConfigurationClass(
    ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
    throws IOException {

    if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
        // 1.首先会递归的处理所有成员类,即@Component注解
        processMemberClasses(configClass, sourceClass, filter);
    }

    // 2.处理所有@PropertySource注解
    for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
        sourceClass.getMetadata(), PropertySources.class,
        org.springframework.context.annotation.PropertySource.class)) {
        if (this.environment instanceof ConfigurableEnvironment) {
            processPropertySource(propertySource);
        }
        else {
            logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                        "]. Reason: Environment must implement ConfigurableEnvironment");
        }
    }

    // 3.处理所有@ComponentScan注解
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
        sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    if (!componentScans.isEmpty() &&
        !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
        for (AnnotationAttributes componentScan : componentScans) {
            // 配置类的注解为@ComponentScan-> 立即执行扫描
            Set<BeanDefinitionHolder> scannedBeanDefinitions =
                this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
            // 检查扫描过的BeanDefinition集合,看看是否有其他配置类,如果需要,递归解析
            for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                if (bdCand == null) {
                    bdCand = holder.getBeanDefinition();
                }
                if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                    parse(bdCand.getBeanClassName(), holder.getBeanName());
                }
            }
        }
    }

    // 4.处理所有@Import注解
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

    // 5.处理所有@ImportResource注解
    AnnotationAttributes importResource =
        AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
    if (importResource != null) {
        String[] resources = importResource.getStringArray("locations");
        Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
        for (String resource : resources) {
            String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
            configClass.addImportedResource(resolvedResource, readerClass);
        }
    }

    // 6.处理标注为@Bean注解的方法
    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    for (MethodMetadata methodMetadata : beanMethods) {
        configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }

    // 7.处理配置类的接口上的默认方法
    processInterfaces(configClass, sourceClass);

    // 8.处理配置类的超类(如果有的话)
    if (sourceClass.getMetadata().hasSuperClass()) {
        String superclass = sourceClass.getMetadata().getSuperClassName();
        if (superclass != null && !superclass.startsWith("java") &&
            !this.knownSuperclasses.containsKey(superclass)) {
            this.knownSuperclasses.put(superclass, configClass);
            // Superclass found, return its annotation metadata and recurse
            return sourceClass.getSuperClass();
        }
    }

    // 处理完成
    return null;
}

其中processImports会处理所有的@Improt注解

该方法会循环处理每一个由@Import导入的类:

  • ImportSelector类的处理
  • ImportBeanDefinitionRegistrar类的处理
  • 其它类统一按照@Configuration类来处理,所以加不加@Configuration注解都能被导入
/**
 * 处理配置类上的@Import注解引入的类
 *
 * @param configClass 配置类,这里是Config类
 * @param currentSourceClass 当前资源类
 * @param importCandidates 该配置类中的@Import注解导入的候选类列表
 * @param exclusionFilter 排除过滤器
 * @param checkForCircularImports 是否循环检查导入
 */
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
                            Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
                            boolean checkForCircularImports) {
    // 如果该@Import注解导入的列表为空,直接返回
    if (importCandidates.isEmpty()) {
        return;
    }
    // 循环检查导入
    if (checkForCircularImports && isChainedImportOnStack(configClass)) {
        this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    }
    else {
        this.importStack.push(configClass);
        try {
            // 循环处理每一个由@Import导入的类
            for (SourceClass candidate : importCandidates) {
                if (candidate.isAssignable(ImportSelector.class)) {
                    // 1. ImportSelector类的处理
                    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) {
                        // 1.1 若是DeferredImportSelector接口的实现,则延时处理
                        this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                    }
                    else {
                        // 1.2 在这里调用我们的ImportSelector实现类的selectImports方法
                        String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                        Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                        // 1.3 递归处理每一个selectImports方法返回的配置类
                        processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
                    }
                }
                else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                     // 2. ImportBeanDefinitionRegistrar类的处理
                    Class<?> candidateClass = candidate.loadClass();
                    ImportBeanDefinitionRegistrar registrar =
                        ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                                                             this.environment, this.resourceLoader, this.registry);
                    configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                }
                else {
                    // 3. 其它类统一按照@Configuration类来处理,所以加不加@Configuration注解都能被导入
                    this.importStack.registerImport(
                        currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                    processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
                }
            }
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(
                "Failed to process import candidates for configuration class [" +
                configClass.getMetadata().getClassName() + "]", ex);
        }
        finally {
            this.importStack.pop();
        }
    }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值