Spring的@Import 注解的作用与用法

Easter79
• 阅读 672

@Import注解

@Import 是 Spring 基于 Java 注解配置的主要组成部分。 @Import 注解提供了 @Bean 注解的功能,同时还有原来 Spring 基于 xml 配置文件里的 标签组织多个分散的xml文件的功能,当然在这里是组织多个分散的 @Configuration 的类。

Spring的@Import 注解的作用与用法

下面将分别说明 @Import 注解的功能。

1. 引入其他的@Configuration

假设有如下接口和两个实现类:

package com.test
interface ServiceInterface {
    void test();
}

class ServiceA implements ServiceInterface {

    @Override
    public void test() {
        System.out.println("ServiceA");
    }
}

class ServiceB implements ServiceInterface {

    @Override
    public void test() {
        System.out.println("ServiceB");
    }
}
复制代码

两个 @Configuration ,其中 ConfigA``@Import``ConfigB :

package com.test
@Import(ConfigB.class)
@Configuration
class ConfigA {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceA() {
        return new ServiceA();
    }
}

@Configuration
class ConfigB {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceB() {
        return new ServiceB();
    }
}
复制代码

通过 ConfigA 创建 AnnotationConfigApplicationContext ,获取 ServiceInterface ,看是哪种实现:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigA.class);
    ServiceInterface bean = ctx.getBean(ServiceInterface.class);
    bean.test();
}
复制代码

输出为: ServiceB .证明 @Import 的优先于本身的的类定义加载。

2. 直接初始化其他类的Bean

Spring 4.2 之后, @Import 可以直接指定实体类,加载这个类定义到 context 中。 例如把上面代码中的 ConfigA 的 @Import 修改为 @Import(ServiceB.class) ,就会生成 ServiceB 的Bean 到容器上下文中,之后运行 main 方法,输出为: ServiceB .证明 @Import 的优先于本身的的类定义加载.

3. 指定实现ImportSelector(以及DefferredServiceImportSelector)的类,用于个性化加载

指定实现 ImportSelector 的类,通过 AnnotationMetadata 里面的属性,动态加载类。 AnnotationMetadata 是 Import 注解所在的类属性(如果所在类是注解类,则延伸至应用这个注解类的非注解类为止)。

需要实现 selectImports 方法,返回要加载的 @Configuation 或者具体 Bean 类的全限定名的String 数组。

package com.test;
class ServiceImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //可以是@Configuration注解修饰的类,也可以是具体的Bean类的全限定名称
        return new String[]{"com.test.ConfigB"};
    }
}

@Import(ServiceImportSelector.class)
@Configuration
class ConfigA {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceA() {
        return new ServiceA();
    }
}
复制代码

再次运行 main 方法,输出: ServiceB .证明 @Import 的优先于本身的的类定义加载。 一般的,框架中如果基于 AnnotationMetadata 的参数实现动态加载类,一般会写一个额外的 Enable注解,配合使用。例如:

package com.test;

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(ServiceImportSelector.class)
@interface EnableService {
    String name();
}

class ServiceImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //这里的importingClassMetadata针对的是使用@EnableService的非注解类
        //因为`AnnotationMetadata`是`Import`注解所在的类属性,如果所在类是注解类,则延伸至应用这个注解类的非注解类为止
        Map<String , Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
        String name = (String) map.get("name");
        if (Objects.equals(name, "B")) {
            return new String[]{"com.test.ConfigB"};
        }
        return new String[0];
    }
}
复制代码

之后,在 ConfigA 中增加注解 @EnableService(name = "B")

package com.test;
@EnableService(name = "B")
@Configuration
class ConfigA {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceA() {
        return new ServiceA();
    }
}
复制代码

再次运行 main 方法,输出: ServiceB .

还可以实现 DeferredImportSelector 接口,这样 selectImports 返回的类就都是最后加载的,而不是像 @Import 注解那样,先加载。 例如:

package com.test;
class DefferredServiceImportSelector implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
        String name = (String) map.get("name");
        if (Objects.equals(name, "B")) {
            return new String[]{"com.test.ConfigB"};
        }
        return new String[0];
    }
}
复制代码

修改 EnableService 注解:

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(DefferredServiceImportSelector.class)
@interface EnableService {
    String name();
}
复制代码

这样 ConfigA 就优先于 DefferredServiceImportSelector 返回的 ConfigB 加载,执行 main方法,输出: ServiceA

4. 指定实现ImportBeanDefinitionRegistrar的类,用于个性化加载

与 ImportSelector 用法与用途类似,但是如果我们想重定义 Bean ,例如动态注入属性,改变Bean 的类型和 Scope 等等,就需要通过指定实现 ImportBeanDefinitionRegistrar 的类实现。例如:

定义 ServiceC

package com.test;
class ServiceC implements ServiceInterface {

    private final String name;

    ServiceC(String name) {
        this.name = name;
    }

    @Override
    public void test() {
        System.out.println(name);
    }
}
复制代码

定义 ServiceImportBeanDefinitionRegistrar 动态注册 ServiceC ,修改 EnableService

package com.test;

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(ServiceImportBeanDefinitionRegistrar.class)
@interface EnableService {
    String name();
}

class ServiceImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
        String name = (String) map.get("name");
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(ServiceC.class)
                //增加构造参数
                .addConstructorArgValue(name);
        //注册Bean
        registry.registerBeanDefinition("serviceC", beanDefinitionBuilder.getBeanDefinition());
    }
}
复制代码

并且根据后面的源代码解析可以知道, ImportBeanDefinitionRegistrar 在 @Bean 注解之后加载,所以要修改 ConfigA 去掉其中被 @ConditionalOnMissingBean 注解的 Bean ,否则一定会生成 ConfigA 的 ServiceInterface

package com.test;
@EnableService(name = "TestServiceC")
@Configuration
class ConfigA {
//    @Bean
//    @ConditionalOnMissingBean
//    public ServiceInterface getServiceA() {
//        return new ServiceA();
//    }
}
复制代码

之后运行 main ,输出: TestServiceC

@Import相关源码解析

加载解析 @Import 注解位于 BeanFactoryPostProcessor 处理的时候:

AbstractApplicationContext 的 refresh 方法

-> invokeBeanFactoryPostProcessors(beanFactory);

-> PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

-> registryProcessor.postProcessBeanDefinitionRegistry(registry);

这里的 registryProcessor ,我们指 ConfigurationClassPostProcessor

ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(registry)

-> processConfigBeanDefinitions(registry) :

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    //省略一些配置检查与设置的逻辑

    //根据@Order注解,排序所有的@Configuration类
    configCandidates.sort((bd1, bd2) -> {
            int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
            int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
            return Integer.compare(i1, i2);
        });

    // 创建ConfigurationClassParser解析@Configuration类
    ConfigurationClassParser parser = new ConfigurationClassParser(
            this.metadataReaderFactory, this.problemReporter, this.environment,
            this.resourceLoader, this.componentScanBeanNameGenerator, registry);

    //剩余没有解析的@Configuration类
    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    //已经解析的@Configuration类
    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
    do {
        //解析
        parser.parse(candidates);
        parser.validate();

        Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
        configClasses.removeAll(alreadyParsed);

        // 生成类定义读取器读取类定义
        if (this.reader == null) {
            this.reader = new ConfigurationClassBeanDefinitionReader(
                    registry, this.sourceExtractor, this.resourceLoader, this.environment,
                    this.importBeanNameGenerator, parser.getImportRegistry());
        }
        this.reader.loadBeanDefinitions(configClasses);
        alreadyParsed.addAll(configClasses);

        candidates.clear();
        if (registry.getBeanDefinitionCount() > candidateNames.length) {
            //省略检查是否有其他需要加载的配置的逻辑
        }
    }
    while (!candidates.isEmpty());

    //省略后续清理逻辑
}
复制代码

其中 parser.parse(candidates) 的逻辑主要由 org.springframework.context.annotation.ConfigurationClassParser 实现,功能是加载 @Import 注解还有即系 @Import 注解。 reader.loadBeanDefinitions(configClasses); 的逻辑主要由 org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader 的 loadBeanDefinitionsForConfigurationClass 方法实现,功能是将上面解析的配置转换为 BeanDefinition 就是 Bean 定义。

1. 加载@Import注解

org.springframework.context.annotation.ConfigurationClassParser

首先是 parse 方法

public void parse(Set<BeanDefinitionHolder> configCandidates) {
    for (BeanDefinitionHolder holder : configCandidates) {
        BeanDefinition bd = holder.getBeanDefinition();
        try {
            if (bd instanceof AnnotatedBeanDefinition) {
                //这里的parse实际上就是调用下面即将分析的doProcessConfigurationClass
                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());
            }
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(
                    "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
        }
    }
    //最后处理所有的`DeferredImportSelector`,符合上面提到的`DeferredImportSelector`的功能
    this.deferredImportSelectorHandler.process();
}


@Nullable
protected final SourceClass doProcessConfigurationClass(
            ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
            throws IOException {
    //处理`@Component`注解的MemberClass相关代码...
    //处理`@PropertySource`注解相关代码...
    //处理`@ComponentScan`注解相关代码...
    //处理`@Import`注解:
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
    //处理`@ImportResource`注解相关代码...
    //处理`@Bean`注解相关代码...
    //处理接口方法相关代码...
    //处理父类相关代码...
}
复制代码

通过 getImports 方法,采集相关的 @Import 里面的类。

private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
    Set<SourceClass> imports = new LinkedHashSet<>();
    Set<SourceClass> visited = new LinkedHashSet<>();
    //递归查询所有注解以及注解的注解是否包含@Import
    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();
            //对于非@Import注解,递归查找其内部是否包含@Import注解
            if (!annName.equals(Import.class.getName())) {
                collectImports(annotation, imports, visited);
            }
        }
        //添加@Import注解里面的所有配置类
        imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
    }
}
复制代码

采集好之后,就可以解析了。

2. 解析@Import注解

解析的方法是: processImports

//在解析时,入栈,解析结束后,出栈,通过检查栈中是否有当前类,判断是否有循环依赖
private final ImportStack importStack = new ImportStack();

//记录所有的ImportBeanDefinitionRegistrar
private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars = new LinkedHashMap<>();

//解析也是递归方法
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
            Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
            boolean checkForCircularImports) {

    if (importCandidates.isEmpty()) {
        return;
    }
    //通过importStack检查循环依赖
    if (checkForCircularImports && isChainedImportOnStack(configClass)) {
        this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    }
    else {
        //入栈
        this.importStack.push(configClass);
        try {
            for (SourceClass candidate : importCandidates) {
                if (candidate.isAssignable(ImportSelector.class)) {
                    //处理ImportSelector接口的实现类
                    Class<?> candidateClass = candidate.loadClass();
                    //创建这些Selector实例
                    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);
                    }
                    //如果是DeferredImportSelector,则用deferredImportSelectorHandler处理
                    if (selector instanceof DeferredImportSelector) {
                        this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                    }
                    else {
                        //如果不是DeferredImportSelector,调用selectImports方法获取要加载的类全限定名称,递归调用本方法继续解析
                        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接口的实现类
                    Class<?> candidateClass = candidate.loadClass();
                    //同样的,创建这些ImportBeanDefinitionRegistrar实例
                    ImportBeanDefinitionRegistrar registrar =
                            ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                                    this.environment, this.resourceLoader, this.registry);
                    //放入importBeanDefinitionRegistrar,用于后面加载
                configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                }
                else {
                    //处理@Configuration注解类,或者是普通类(直接生成Bean)
                    //在栈加上这个类
                    this.importStack.registerImport(
                            currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                    //递归回到doProcessConfigurationClass处理@Configuration注解类    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();
        }
    }
}
复制代码

这样,所有的 @Conditional 类相关的 @Import 注解就加载解析完成了,这是一个大的递归过程。

3. 转换为BeanDefinition注册到容器

org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader 的 loadBeanDefinitionsForConfigurationClass 方法:

private void loadBeanDefinitionsForConfigurationClass(
            ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

    if (trackedConditionEvaluator.shouldSkip(configClass)) {
        String beanName = configClass.getBeanName();
        if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
            this.registry.removeBeanDefinition(beanName);
        }
        this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
        return;
    }

    //对Import完成的,加载其Import的BeanDefinition
    if (configClass.isImported()) {
        registerBeanDefinitionForImportedConfigurationClass(configClass);
    }
    //加载@Bean注解的方法生成的Bean的Definition
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
        loadBeanDefinitionsForBeanMethod(beanMethod);
    }
    //@ImportResource 注解加载的
    loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    //加载ImportBeanDefinitionRegistrar加载的Bean的Definition
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
复制代码

通过这里可以看出,为啥之前说 @Bean 注解的 Bean 会优先于 ImportBeanDefinitionRegistrar 返回的 Bean 加载。

原文链接: https://juejin.cn/post/6917771718433964040

如果觉得本文对你有帮助,可以关注一下我公众号,回复关键字【面试】即可得到一份Java核心知识点整理与一份面试大礼包!另有更多技术干货文章以及相关资料共享,大家一起学习进步!

Spring的@Import 注解的作用与用法

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Easter79 Easter79
3年前
spring注解
随着越来越多地使用Springboot敏捷开发,更多地使用注解配置Spring,而不是Spring的applicationContext.xml文件。Configuration注解:Spring解析为配置类,相当于spring配置文件Bean注解:容器注册Bean组件,默认id为方法名@Configurat
Easter79 Easter79
3年前
Spring全解系列
本文基于Spring5.2.x@Import注解@Import是Spring基于Java注解配置的主要组成部分。@Import注解提供了@Bean注解的功能,同时还有原来Spring基于xml配置文件里的<import标签组织多个分散的xml文件的功能,当然在这里是组织多个分散的
Stella981 Stella981
3年前
From表单提交的几种方式
<body<formaction"https://my.oschina.net/u/3285916"method"get"name"formOne"id"formId"name:<inputtype"text"name"name"pwd:<inputtyp
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
9个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k