Spring - @Import注解

news/2024/9/30 12:30:33 标签: spring, java, 后端

文章目录

  • 基本用法
  • 源码分析
    • ConfigurationClassPostProcessor
      • ConfigurationClass
    • SourceClass
    • getImports
    • processImports
      • 处理 ImportSelector
        • ImportSelector 接口
        • DeferredImportSelector
      • 处理 ImportBeanDefinitionRegistrar
        • ImportBeanDefinitionRegistrar 接口
      • 处理Configuration

本文源码基于spring-context-5.3.36 版本

基本用法

  1. 直接填class数组方式
    @Import注解填入要导入的类的Class即可
java">@Import({ A.class , B.class... })
@Configuration
public class TestDemo {

}
  1. ImportSelector方式
    实现ImportSelector接口,selectImport返回需要导入的类的全限定名称
java">public class MyImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[0];
    }
}

然后@Import中指定ImportSelector的实现类的Class

  1. ImportBeanDefinitionRegistrar方式
    实现ImportBeanDefinitionRegistrar接口
java">public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
      // 通过 BeanDefinitionRegistry 构造 BeanDefinition 并进行注册
    }
}

然后@Import中指定ImportBeanDefinitionRegistrar的实现类的Class
总结

  1. 以上三种用法方式皆可混合在一个@Import中使用,注意第一种和第二种都是以全类名的方式注册,而第三中可自定义名称
  2. 第一种最简单,第二种较简单,第三种需要操作BeanDefinition,较为复杂
  3. @Import注解不一定非要和@Configuration搭配使用,也可以和@Component等注解使用,效果一样
java">@Import({ A.class , B.class... })
@Component
public class TestDemo {

}

源码分析

入口

BeanDefinitionRegistryPostProcessor执行阶段生效

例如 ConfigurationClassPostProcessor

ConfigurationClassPostProcessor

ConfigurationClass

代表定义的@Configuration修饰的类,包含一些bean方法。配置加载都有一个解析过程,对ConfigurationClass的解析就是由ConfigurationClassParser#parse方法完成的,它会解析每个配置类上的配置,包括@Import注解这个配置

java">public void parse(Set<BeanDefinitionHolder> configCandidates) {
	// 拿到所有的BeanDefinition
	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());
			}
		}
		catch (BeanDefinitionStoreException ex) {
			...
		}
	}

	this.deferredImportSelectorHandler.process();
}

在这里插入图片描述

SourceClass

org.springframework.context.annotation.ConfigurationClassParser.SourceClass主要用于处理和解析配置类的源信息

java">private class SourceClass implements Ordered {
	// Class或者MetadataReader
	private final Object source;
	// 注解元数据
	private final AnnotationMetadata metadata;
}

主要作用如下:

  1. 表示源类:SourceClass 代表 Spring 配置类中的一个类,它封装了获取该类相关的源信息的方法,比如类的名称、注解、方法等。
  2. 提供元信息:SourceClass 提供了一些元数据的方法,帮助开发者获取类的详细信息,比如父类、实现的接口、注解信息等。

getImports

SourceClass可以简单的理解为java里的Class,同样,SourceClass可以代表普通类,也可以代表注解。同时SourceClass携带有一些注解元数据信息。其实getImports方法的过程就和根据反射获取一个类上的所有注解(包括修饰注解的注解)这个过程差不多

使用深度优先遍历

  1. 首先定义一个visited记录已经访问过的 SourceClass
  2. 对于每个访问的 SourceClass ,如果它被@Import注解修饰,则获取@Import注解的属性值
java">private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
	Set<SourceClass> imports = new LinkedHashSet<>();
	Set<SourceClass> visited = new LinkedHashSet<>();
	collectImports(sourceClass, imports, visited);
	return imports;
}

调用的collectImports()方法是一个递归操作,从第一个SourceClass开始,获取其所有注解,然后调用collectImports递归进行收集。因为@Import可能放在注解上形成复合注解

java">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"));
	}
}

最后返回的imports就是从根SourceClass开始遍历的所有@Import注解的值,这个值是一个java中Class的集合

processImports

getImports的返回值作为processImports方法的第三个参数

java">/**
 * 
 * @param configClass	配置类,一般是@Configuration修饰的类
 * @param currentSourceClass  扫描@Import注解的根节点
 * @param importCandidates	  currentSourceClass上携带的所有@Import注解的属性值
 * @param exclusionFilter    用于排除
 * @param checkForCircularImports  是否检查@Import形成环的情况
 */
private void processImports(ConfigurationClass configClass,
                            SourceClass currentSourceClass,
                            Collection<SourceClass> importCandidates,
                            Predicate<String> exclusionFilter,
                            boolean checkForCircularImports) {
	if (importCandidates.isEmpty()) {
		return;
	}
	// 检测可能存在的循环@Import情况
	if (checkForCircularImports && isChainedImportOnStack(configClass)) {
		this.problemReporter.error(
				new CircularImportProblem(configClass, this.importStack));
	} else {
		// 下面代码保留了整体结构,省略了不重要的细节
		this.importStack.push(configClass);
		try {
			for (SourceClass candidate : importCandidates) {
				// 一些if/else判断
				if (candidate.isAssignable(ImportSelector.class)) {
					...
				} else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
					...
				} else {
					// 既不是ImportSelector也不是ImportBeanDefinitionRegistrar
					// 当作@Configuration类处理
					this.importStack.registerImport(
					currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
					// 继续走处理配置类的流程,会继续进行processImports方法
					processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
				}
			}
		} catch (BeanDefinitionStoreException e) {
			// 省略
		}
		finally {
			this.importStack.pop();
		}
	}
}

处理 ImportSelector

如果是ImportSelector

处理细节如下所示

java">// 实例化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);
}

// 区分是否是DeferredImportSelector
if (selector instanceof DeferredImportSelector) {
	this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
} else {
	// 所有导入的类名称
	String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
	// 将ImportSelector返回的类名转换为SourceClass
	// 使用Class.forName转换成Class实例,构造SourceClass对象
	Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
	// 递归调用processImports
	processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
ImportSelector 接口
java">public interface ImportSelector {
	
	/**
	 * 根据配置类的元数据信息,返回类的全限定名称
	 * importingClassMetadata:@Import注解修饰的那个类
	 **/
	String[] selectImports(AnnotationMetadata importingClassMetadata);

	@Nullable
	default Predicate<String> getExclusionFilter() {
		return null;
	}
}
DeferredImportSelector

Deferred意思是延迟,相比于ImportSelector会立即被处理,DeferredImportSelector的selectImport方法会在当前配置类所有的Bean信息解析完毕后才进行处理
在这里插入图片描述
DeferredImportSelector的作用是用于调整Import的Bean和当前配置类配置的Bean的先后关系

因为有条件注解的存在,所以需要Bean之间的注册有先后关系,条件注解才能发挥作用

处理 ImportBeanDefinitionRegistrar

如果SourceClass类型是ImportBeanDefinitionRegistrar的实现类简单,则调用org.springframework.context.annotation.ConfigurationClass#addImportBeanDefinitionRegistrar方法

java">// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
		ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
				this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
ImportBeanDefinitionRegistrar 接口

ImportBeanDefinitionRegistrar接口用于手动注册BeanDefinition

java">public interface ImportBeanDefinitionRegistrar {

	/**
	 * 注册BeanDefinition
	 * BeanDefinitionRegistryPostProcessor此时还未注册
	 * @param importingClassMetadata 导入的类的注解元数据
	 * @param registry 当前BeanDefinitionRegistry,即BeanFactory实现
	 * @param importBeanNameGenerator 导入的Bean的命名策略
	 */
	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
			BeanNameGenerator importBeanNameGenerator) {

		registerBeanDefinitions(importingClassMetadata, registry);
	}

	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	}
}
  1. BeanNameGenerator:默认实现是ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR或者通过ConfigurationClassPostProcessor#setBeanNameGenerator进行设置
  2. BeanDefinitionRegistry registry:其实就是BeanFactory

@Configuration配置类解析完毕后,下一步就是将让配置生效。如下图所示,通过ConfigurationClassBeanDefinitionReader来加载BeanDefinition
在这里插入图片描述
ConfigurationClassBeanDefinitionReader加载BeanDefinition的过程中条件注解会生效

java">public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
	TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
	for (ConfigurationClass configClass : configurationModel) {
		loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
	}
}

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;
	}
	// 处理已经导入的配置类
	if (configClass.isImported()) {
		registerBeanDefinitionForImportedConfigurationClass(configClass);
	}
	// 配置类的所有@Bean修饰的方法
	for (BeanMethod beanMethod : configClass.getBeanMethods()) {
		loadBeanDefinitionsForBeanMethod(beanMethod);
	}
	// @ImportedResources用于导入Spring的配置文件
	// 而Spring的配置文件中也可以定义Bean
	loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
	// 加载ImportBeanDefinitionRegistrar中需要注册的Bean
	loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

处理Configuration

如果@Import的内容既不是ImportSelector也不是ImportBeanDefinitionRegistrar,那么就把它当作@Configuration修饰的类进行处理,无论它是不是真的被@Configuration修饰

java">// 既不是ImportSelector也不是ImportBeanDefinitionRegistrar
// 当作@Configuration类处理
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
// 继续走处理配置类的流程,会继续进行processImports方法
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);

假如@Import的是一个普通类,比如下面这样,什么都没有。由于@Import的存在,SimpleClass还是会被当作Bean注册进容器

java">public class SimpleClass {
}

如果给它加上@Configuration配置类可以有的配置,它也是会生效的

java">public class SimpleClass {

    @Bean
    public A a() {
        return new A();
    }
}

http://www.niftyadmin.cn/n/5685103.html

相关文章

《动手学深度学习》笔记2.5——神经网络从基础→使用GPU (CUDA-单卡-多卡-张量操作)

目录 0. 前言 原书正文 1. 计算设备 (CPU和GPU) 补充&#xff1a;torch版本cuda报错的解决方案 2. 张量与GPU 3. 存储在GPU上 4. 复制&#xff08;多卡操作&#xff09; 5. 旁注 (CPU和GPU之间挪数据) 6. 神经网络与GPU 小结 0. 前言 课程全部代码&#xff08;pytorc…

《ToDesk 云电脑、易腾云、青椒云移动端体验实测:让手机秒变超级电脑》

前言 科技发展到如今2024年&#xff0c;可以说每一年都在发生翻天覆地的变化。云电脑这个市场近年来迅速发展&#xff0c;无需购买和维护额外的硬件就可以体验到电脑端顶配的性能和体验&#xff0c;并且移动端也可以带来非凡体验。我们在外出办公随身没有携带电脑情况下&#x…

【C++——文件操作】

写入 #include<iostream> #include<fstream> //ofstream所需头文件 using namespace std;int main() {//一打开文件:string str R"(C:\Users\admin\Desktop\新建文件夹\test.txt)";//也可以用C风格字符串//打开文件&#xff0c;如果不存在就创建一…

Llama微调以及Ollama部署

1 Llama微调 在基础模型的基础上&#xff0c;通过一些特定的数据集&#xff0c;将具有特定功能加在原有的模型上。 1.1 效果对比 特定数据集 未使用微调的基础模型的回答 使用微调后的回答 1.2 基础模型 基础大模型我选择Mistral-7B-v0.3-Chinese-Chat-uncensored&#x…

uniapp生物识别示例(人脸识别、指纹识别)

准备工作&#xff1a; mainfest.json设置勾选&#xff1a; 勾选完成后打 App自定义调试基座测试包 示例代码&#xff1a; <template><view class"content"><button v-if"supportSoterAuthenticationArray.includes(facial)" click"…

​IAR全面支持国科环宇AS32X系列RISC-V车规MCU

全球领先的嵌入式系统开发软件解决方案供应商IAR与北京国科环宇科技股份有限公司&#xff08;以下简称”国科环宇”&#xff09;联合宣布&#xff0c;最新版本IAR Embedded Workbench for RISC-V将全面支持国科环宇AS32X系列RISC-V MCU&#xff0c;双方将共同助力中国汽车行业开…

【视频目标分割-2024CVPR】Putting the Object Back into Video Object Segmentation

Cutie 系列文章目录1 摘要2 引言2.1背景和难点2.2 解决方案2.3 成果 3 相关方法3.1 基于记忆的VOS3.2对象级推理3.3 自动视频分割 4 工作方法4.1 overview4.2 对象变换器4.2.1 overview4.2.2 Foreground-Background Masked Attention4.2.3 Positional Embeddings 4.3 Object Me…

损失函数篇 | YOLOv10 更换损失函数之 SIoU / EIoU / WIoU / Focal_xIoU 最全汇总版

文章目录 更换方式CIoUDIoUEIoUGIoUSIoUWIoUFocal_CIoUFocal_DIoUFocal_EIoUFocal_GIoUFocal_SIoU提示更换方式 第一步:将ultralytics/ultralytics/utils/metrics.py文件中的bbox_iou替换为如下的代码:class WIoU_Scale: if monotonous = None , v1if monotonous = True , v…