Spring扩展——@Import注解
引言
在Spring中有许多Enable开头的注解,比如以下常见注解
@EnableTransactionManagement
@EanbleAsync
@EnableCache
@EnableAspectJAutoProxy
@EnableSchedulin
这些注解是在什么时候,什么地方被处理的呢?
我们在另一篇博客里面可以找到相应的答案——Spring源码——ConfigurationClassPostProcessor类
ConfigurationClassPostProcessor类继承关系图

@Import原理
@Import源码
/**
* Indicates one or more <em>component classes</em> to import — typically
* {@link Configuration @Configuration} classes.
*
* <p>Provides functionality equivalent to the {@code <import/>} element in Spring XML.
* Allows for importing {@code @Configuration} classes, {@link ImportSelector} and
* {@link ImportBeanDefinitionRegistrar} implementations, as well as regular component
* classes (as of 4.2; analogous to {@link AnnotationConfigApplicationContext#register}).
*
* <p>{@code @Bean} definitions declared in imported {@code @Configuration} classes should be
* accessed by using {@link org.springframework.beans.factory.annotation.Autowired @Autowired}
* injection. Either the bean itself can be autowired, or the configuration class instance
* declaring the bean can be autowired. The latter approach allows for explicit, IDE-friendly
* navigation between {@code @Configuration} class methods.
* <p>May be declared at the class level or as a meta-annotation.
* <p>If XML or other non-{@code @Configuration} bean definition resources need to be
* imported, use the {@link ImportResource @ImportResource} annotation instead.
* 此注解,如果被扫描到,ConfigurationClassPostProcessor在处理的时候,会扫描其value字段,会根据Value字段的类型不同,分别调用其不同的处理方法。
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.0
* @see Configuration
* @see ImportSelector
* @see ImportBeanDefinitionRegistrar
* @see ImportResource
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* 可以引入普通Bean 配置Bean ImportSelector指定的名字的对象 ImportBeanDefinitionRegistrar手动向Register里面注册BennDefinition
* ImportSelector ImportBeanDefinitionRegistrar 本身的实现类,也是作为一个Bean的,只不过它还能引入其它类。
* {@link Configuration @Configuration}, {@link ImportSelector},
* {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
*/
Class<?>[] value();
}
ConfigurationClassPostProcessor属于BDRPP,所以在执行InvokeBeanFactoryPostProcessor的时候,会先执行其postProcessBeanDefinitionRegistry方法,通过这个方法可以向容器里面添加更多的BeanDefinition信息。
在执行其postProcessBeanDefinitionRegistry的时候,会遍历当前容器里BeanDefinitionsMap里面所有的Bean信息,如果是Configuration配置类,则会处理其@Import注解,在处理的时候,是递归进行处理的。
ConfigurationClassPostProcessor在处理Import注解的时候,会扫描其value字段,会根据Value字段的类型不同,分别调用其不同的处理方法。
// 遍历每一个@Import注解的类
for (SourceClass candidate : importCandidates) {
// 检验配置类Import引入的类是否是ImportSelector子类
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
// 候选类是一个导入选择器->委托来确定是否进行导入
Class<?> candidateClass = candidate.loadClass();
// 通过反射生成一个ImportSelect对象
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接口的实例
// 如果是则应用选择器将会在所有的配置类都加载完毕后加载
if (selector instanceof DeferredImportSelector) {
// 将选择器添加到deferredImportSelectorHandler实例中,预留到所有的配置类加载完成后统一处理自动化配置类
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
// 获取引入的类,然后使用递归方式将这些类中同样添加了@Import注解引用的类
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
// 递归处理,被Import进来的类也有可能@Import注解
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
}
// 如果是实现了ImportBeanDefinitionRegistrar接口的bd
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
// 候选类是ImportBeanDefinitionRegistrar -> 委托给当前注册器注册其他bean
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
/**
* 放到当前configClass的importBeanDefinitionRegistrars中
* 在ConfigurationClassPostProcessor处理configClass时会随之一起处理
*/
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
// 候选类既不是ImportSelector也不是ImportBeanDefinitionRegistrar-->将其作为@Configuration配置类处理
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
/**
* 如果Import的类型是普通类,则将其当作带有@Configuration的类一样处理
* 将candidate构造为ConfigurationClass,标注为importedBy,意味着它是通过被@Import进来的
* 后面处理会用到这个判断将这个普通类注册进DefaultListableBeanFactory
*/
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
@Import在处理的时候,根据它的value值的类型来选择不同的处理流程
@Import引入Bean
1. ImportSelector
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
* @return the class names, or an empty array if none
* 选择并返回应基于哪个类导入的名称
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
/**
* Return a predicate for excluding classes from the import candidates, to be
* transitively applied to all classes found through this selector's imports.
* <p>If this predicate returns {@code true} for a given fully-qualified
* class name, said class will not be considered as an imported configuration
* class, bypassing class file loading as well as metadata introspection.
* @return the filter predicate for fully-qualified candidate class names
* of transitively imported configuration classes, or {@code null} if none
* @since 5.2.4
* 需要排除那项类的名称
*/
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
如果@Import引入的是ImportSelector的子类,将它添加到deferredImportSelectorHandler实例中,预留到所有的配置类加载完成后统一处理自动化配置类
此类的主要功能是:根据给定的条件(通常是一个或多个注解属性)判断要导入哪个配置类
2. ImportBeanDefinitionRegistrar
ublic interface ImportBeanDefinitionRegistrar {
/**
* @since 5.2
* @see ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR
* @see ConfigurationClassPostProcessor#setBeanNameGenerator
*/
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
/**
* <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
* registered here, due to lifecycle constraints related to {@code @Configuration}
* class processing.
* <p>The default implementation is empty.
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
*/
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
如果@Import引入的是ImportBeanDefinitionRegistrar的子类,将它添加到ConfigurationClass对象的importBeanDefinitionRegistrars集合中,待后面统一进行处理
它的功能主要是:可以非常灵活地手动注册BeanDefinitions信息
3. 引入普通Bean对象
如果是引入的普通Bean对象,则将其看作是Configuration配置类进行处理,递归处理
@Import扩展
Import引入普通Bean对象
在需要的时候,通过注解的形式,在项目中引入SpringUtil工具类
1.自定义注解@EnableSpringUtil
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SpringUtil.class)
public @interface EnableSpringUtil {
}
2.SpringUtil工具类
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
/**
* 注入ApplicationContext
*
* @param applicationContext the ApplicationContext object to be used by this object
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringUtil.applicationContext = applicationContext;
}
在获取到ApplicationContext后,就可以在代码里面获取任意指定的Bean对象了。
@EnableSpringUtil注解作为一个第三方包的注解,我们在需要的项目中,只需要在主类上面写上此注解,就可以优雅地使用SpringUtil工具类了
Import之ImportSelector扩展
通过自定义注解上的参数,选择引入不同的Bean组件
1.自定义注解@EnableXXX
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(XXXSelector.class)
public @interface EnableXXX {
PersonRole value() default PersonRole.TEACHER;
}
2.PersonRole枚举类
public enum PersonRole {
TEACHER,
STUDENT;
}
3.实现XXXSelector
public class XXXSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
MergedAnnotations annotations = importingClassMetadata.getAnnotations();
PersonRole xxValue = annotations.get(EnableXXX.class).getEnum("value", PersonRole.class);
switch (xxValue) {
case STUDENT:
return new String[]{StudentService.class.getName()};
case TEACHER:
return new String[]{TeacherService.class.getName()};
default:
return new String[]{TeacherService.class.getName()};
}
}
}
根据@EnableXXX注解的参数值引入不同的Bean对象
4.使用@EnableXXX
@EnableAspectJAutoProxy
@Configuration
@EnableXXX(PersonRole.TEACHER)
public class XXXConfiguration {
}
在需要使用的地方,写上@EnableXXX,并写上指定的参数就可以使用了
Import之ImportBeanDefinitionRegistrar扩展
通过@EnableCustomComponent注解,将我们指定包里面的,同时被标注了指定注解的类,引入到容器中作为Bean对象
1.自定义注解@EnableCustomComponent
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CustomImportBeanDefinitionRegistrar.class)
public @interface EnableCustomComponent {
//需要扫描的包路径
String[] basePackage();
//需要扫描的注解
Class<? extends Annotation>[] annotations();
}
2.自定义一个需要被扫描到的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomComponent {
}
如果指定包里面的类,被标注了此注解,这个类就会被扫描到,加入到容器中作为Bean组件
3.实现CustomImportBeanDefinitionRegistrar
public class CustomImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
System.out.println("registerBeanDefinitions2");
ClassPathBeanDefinitionScanner scanner
= new ClassPathBeanDefinitionScanner(registry, false);
MergedAnnotation<EnableCustomComponent> enableCustomComponentMergedAnnotation
= importingClassMetadata.getAnnotations().get(EnableCustomComponent.class);
String[] basePackages = enableCustomComponentMergedAnnotation.getStringArray("basePackage");
Class<?>[] annotations = enableCustomComponentMergedAnnotation.getClassArray("annotations");
for (String basePackage : basePackages) {
System.out.println(basePackage);
}
for (Class<?> annotation : annotations) {
scanner.addIncludeFilter(new AnnotationTypeFilter((Class<? extends Annotation>) annotation));
}
int scan = scanner.scan(basePackages);
System.out.println("scan result " + scan);
}
}
4.使用@EnableCustomComponent注解
@Configuration
@EnableCustomComponent(
basePackage = "com.mashibing.ycb.importtest.test3.service",
annotations = {CustomComponent.class, CustomComponent2.class})
public class KKKConfiguration {
}
这样就实现了,通过一个注解,将某一个包里面的被指定注解标注了的类,扫描到Bean容器中来
Spring扩展——@Import注解的更多相关文章
- EnableAutoConfiguration注解 Spring中@Import注解的作用和使用
EnableAutoConfiguration注解 http://www.51gjie.com/javaweb/1046.html springboot@EnableAutoConfiguration ...
- Spring中@Import注解的使用
Spring中@Import注解的使用 @Import注解算是SpringBoot自动配置原理中一个很重要的注解 认识@Import注解 先看一下源码 @Target(ElementType.TYPE ...
- 【Spring注解驱动开发】在@Import注解中使用ImportSelector接口导入bean
写在前面 在上一篇关于Spring的@Import注解的文章<[Spring注解驱动开发]使用@Import注解给容器中快速导入一个组件>中,我们简单介绍了如何使用@Import注解给容器 ...
- 五、Spring中的@Import注解
一.使用@Import注解导入组件 @Import注解的作用是给容器中导入组件,回顾下我们给容器中导入组件的方式,可以通过Spring的xm配置方式,可以通过注解,如@Component等,也可以通过 ...
- spring4.1.8扩展实战之八:Import注解
spring4.1.8扩展实战之八:Import注解 2018年09月10日 12:53:57 博陵精骑 阅读数:441更多 所属专栏: spring4源码分析与实战 版权声明:欢迎转载,请注明 ...
- Spring Cache扩展:注解失效时间+主动刷新缓存(二)
*:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...
- Spring框架中的org.springframework.context.annotation.Import注解类
@Import注解的作用和在使用spring的xml配置时用到的<import/>类似.但应注意是@Import在使用时必须要保证能被IOC容器扫描到,所以通常它会和@Configurat ...
- Spring Import注解
今天了解了,Spring @Import的使用 先贴上Spring官方关于Spring @Import注解的文档链接 https://docs.spring.io/spring/docs/3.0. ...
- spring框架中的@Import注解
spring框架中的@Import注解 Spring框架中的@Import注解 在之前的文章中,作者介绍了Spring JavaConfig. 这是除了使用传统的XML文件之外,spring带来的新的 ...
- spring 中的@Import注解和@ImportResource注解
概述:@Import注解是引入带有@Configuration的java类. @ImportResource是引入spring配置文件.xml 案例的核心代码如下: package com.timo. ...
随机推荐
- Serverless 在阿里云函数计算中的实践
简介: 近日,阿里云 aPaaS&Serverless 前端技术专家袁坤在 CSDN 云原生 meetup 长沙站分享了 Serverless 在阿里云函数计算 FC 的实践. 作者:CSDN ...
- 阿里云RemoteShuffleService 新功能:AQE 和流控
简介:阿里云EMR 自2020年推出 Remote Shuffle Service(RSS)以来,帮助了诸多客户解决 Spark 作业的性能.稳定性问题,并使得存算分离架构得以实施.为了更方便大家使 ...
- 走完线上 BUG 定位最后一公里
简介: 因为线上线下环境隔离的问题,线上的输入很多时候难以在日常环境中构造,定位 bug 效率低下.是否有简单快捷的办法呢? 一个小故事 周末12点的闹钟在回龙观均价3000的出租屋急促的响起,程序员 ...
- 技术干货| 阿里云基于Hudi构建Lakehouse实践探索「内附干货PPT下载渠道」
简介: 阿里云高级技术专家王烨(萌豆)在Apache Hudi 与 Apache Pulsar 联合 Meetup 杭州站上的演讲整理稿件,本议题介绍了阿里云如何使用 Hudi 和 OSS 对象存储 ...
- OLAP系列之分析型数据库clickhouse备份方式(五)
一.常见备份方式 1.1 备份方式 备份方式 特点 物理文件备份 对物理文件进行拷贝,备份期间禁止数据写入 dump数据导入导出 备份方式灵活,但备份速度慢 快照表备份 制作_bak表进行备份 FRE ...
- django-rest-framework框架(一)
1.Web开发模式 # web开发模式 #前后端混合开发(前后端不分离):返回的是html的内容,需要写模板 #前后端分离:只专注于写后端接口,返回json,xml格式数据 # xml格式 <x ...
- CF746 期望+逆序对
Link 题意:给定一个 \(1\) 到 \(n\) 的排列,等概率选一段区间 \([l, r]\) 随机排序,求期望逆序对数. \[E = \dfrac{\sum(cnt_{[1, n]} - cn ...
- 【AI新趋势期刊#2】AI发明计算机算法,如何给大模型排行,照片秒变二维码,视频一键动漫风
前言 每天都要浏览大量AI相关新闻,是不是感到信息量爆炸,有效信息少? 这么多新产品和新工具,到底哪些是真正是有价值的,哪些只是浮躁的一时热点? 想参与AI产品和工具的开发,从哪里能够获得大量的灵感和 ...
- TVM 中的 Profiler 设计
一.基本用法 首先看 Profiler 的用法: with ms.Profiler() as profiler: # .... 用户代码 print("Tuning Time:") ...
- CSS样式(第二篇)
CSS样式(第二篇) 关于定位position: li.widget {position: relative;}相对定位,设定一个参照物. @media (max-width: 980px) .t ...