写点什么

Spring @Import 注解的使用和源码分析

作者:Java你猿哥
  • 2023-04-19
    湖南
  • 本文字数:3633 字

    阅读完需:约 12 分钟

Spring @Import注解的使用和源码分析

介绍

今天主要介绍 Spring @Import 注解,在 Spring 中 @Import 使用得比较频繁,它得作用是导入 bean,具体的导入方式有多种,特别在 SpringBoot 项目中,很多地方都使用到了 @Import 注解,特别对于一些和 SpringBoot 整合的组件,其实现都大量使用了 @Import,例如使用 Feign 集成 SpringBoot 时会加上注解 @EnableFeignClients,使用 Dubbo 时会使用 @EnableDubbo 等,这些注解里面都使用了 @Import 注解来注册一些 bean。

@Import 导入 bean 的三种方式

@Import 导入 bean 有三种方式,分别是导入普通类,实现 ImportSelector 接口的类,实现 ImportBeanDefinitionRegistrar 接口的类。

普通类

在开放过程中,尽量保持类不要太过于庞大,类过于庞大的话会变得臃肿复杂,不好维护,一个配置类中需要配置很多 bean,且逻辑实现也比较复杂,代码量大,如果全部都放在同一个配置类中,这显然不太理智,这时候我们可以将每个 bean 单独拿出来放到一个类里面,然后使用 @Import 注解导入,如下代码所示。

  • 定义一个 bean

@Datapublic class UserBean {    private String username;    private String sex;}
复制代码
  • 导入 bean

@Configuration@Import(value = {UserBean.class})  //注入普通Beanpublic class ImportConfiguration {
}
复制代码

从上面可以看出只需要在配置类上面使用 @Import 注解导入对应 Java Bean,然后这个 bean 就能注册进 IOC 容器中。

ImportSelector 接口

ImportSelector 是一个接口,可以通过实现它来完成 bean 的注册,它只有一个 selectImports()方法,它会返回一个 bean 的名称数组,这个数组中的 bean 名称就会被注册进 IOC 容器中。

public class MyImportSelector implements ImportSelector {    @Override    public String[] selectImports(AnnotationMetadata importingClassMetadata) {        return new String[]{UserBean.class.getName()};    }}
复制代码

ImportBeanDefinitionRegistrar 接口

使用 ImportBeanDefinitionRegistrar 也可以注册 bean,它会传入 BeanDefinitionRegistry 接口,然后进可以注册 bean,这里注册的是 bean 的元信息 BeanDefinition。

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { String name = UserBean.class.getName(); BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(UserBean.class); builder.addPropertyValue("sex","男"); AbstractBeanDefinition beanDefinition = builder.getBeanDefinition(); registry.registerBeanDefinition(name, beanDefinition); }}
复制代码

源码解析

spring 容器启动后,会在 ConfigurationClassParser 解析类中解析 @Import 注解,解析出需要注册的 bean,下面就是最关键的代码,通过调用 processImports 方法,然后解析出对应的 bean,可以看出有几个判断,分别判断是否是 ImportSelector 类型,ImportBeanDefinitionRegistrar 类型,如果都不是,则证明是直接导入普通 java 类,如果是普通 java 类和 ImportSelector 类型,那么就会将要注册的 bean 加入一个 Map 集合 configurationClasses 中,后续会将它进行注册,如果是 ImportBeanDefinitionRegistrar 类型,那么会将其加入一个 Map 集合 importBeanDefinitionRegistrars 中,后续在扩展点会对它进行再次处理。

private void processImports(ConfigurationClass configClass, ConfigurationClassParser.SourceClass currentSourceClass,                                Collection<ConfigurationClassParser.SourceClass> importCandidates, Predicate<String> exclusionFilter,                                boolean checkForCircularImports) {        if (candidate.isAssignable(ImportSelector.class)) {            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 deferredImportSelector) {                this.deferredImportSelectorHandler.handle(configClass, deferredImportSelector);            } else {                String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());                Collection<ConfigurationClassParser.SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);                processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);            }        } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {            Class<?> candidateClass = candidate.loadClass();            ImportBeanDefinitionRegistrar registrar =                    ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,                            this.environment, this.resourceLoader, this.registry);            configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());        } else {            this.importStack.registerImport(                    currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());            processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);        }    }
复制代码

经过上面解析后,Spring 会注册 Bean 的元信息,会通过 configClass.isImported()判断 bean 是否是通过 @Import 方式导入的普通 bean 或者 ImportSelector 类型的导入的 bean,如果是,则执行 registerBeanDefinitionForImportedConfigurationClass,里面主要就是组装成 BeanDefinition,然后注册进 BeanFactory。

private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, ConfigurationClassBeanDefinitionReader.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;        }        if (configClass.isImported()) {            registerBeanDefinitionForImportedConfigurationClass(configClass);        }        for (BeanMethod beanMethod : configClass.getBeanMethods()) {            loadBeanDefinitionsForBeanMethod(beanMethod);        }        loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());        loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());    }
复制代码

如果是通过 ImportBeanDefinitionRegistrar 方式,则会调用 loadBeanDefinitionsFromRegistrars,里面会循环去执行我们自定义的 ImportBeanDefinitionRegistrar,然后进行 bean 的元信息注册。

private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {    registrars.forEach((registrar, metadata) ->        registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator)); }
复制代码

从上面的源码解析中,我们看出通过 @Import 直接导入普通的 java 类和导入实现了 ImportSelector 接口的类是直接注册进 BeanFactory,这两者本质是一样的,而通过实现 ImportBeanDefinitionRegistrar 接口方式的类则需要去实现我们自定义的注册 bean 元信息的逻辑。

总结

上面我们介绍了 @Import 的一些场景,@Import 用得最多还是一些和 Spring 结合的中间件里面,也介绍了它的几种使用方式,还对它的源码进行解析,当然,只是从它最主要的逻辑去分析,深入的逻辑就没去一一详解,掌握 @Import 有助于我们在使用一些其他框架的时候能够了解框架的实现原理,然后更好的去使用框架!

今天的分享就到这里,感谢你的观看,下期见。

用户头像

Java你猿哥

关注

一只在编程路上渐行渐远的程序猿 2023-03-09 加入

关注我,了解更多Java、架构、Spring等知识

评论

发布
暂无评论
Spring @Import注解的使用和源码分析_Java_Java你猿哥_InfoQ写作社区