【Spring源码核心篇-08】spring中配置类底层原理和源码实现

Spring源码核心篇整体栏目


内容 链接地址
【一】Spring的bean的生命周期 https://zhenghuisheng.blog.csdn.net/article/details/143441012
【二】深入理解spring的依赖注入和属性填充 https://zhenghuisheng.blog.csdn.net/article/details/143854482
【三】精通spring的aop的底层原理和源码实现 https://zhenghuisheng.blog.csdn.net/article/details/144012934
【四】spring中refresh刷新机制的流程和实现 https://zhenghuisheng.blog.csdn.net/article/details/144118337
【五】spring中循环依赖的解决和底层实现 https://zhenghuisheng.blog.csdn.net/article/details/144132213
【六】spring中事务的底层实现与执行流程 https://zhenghuisheng.blog.csdn.net/article/details/144178500
【七】spring中事物传播机制的流程和原理 https://zhenghuisheng.blog.csdn.net/article/details/144178500
【八】spring中配置类底层原理和源码实现 https://zhenghuisheng.blog.csdn.net/article/details/148657333

spring配置类实现原理

  • [一. 深入理解spring配置类实现原理](#一. 深入理解spring配置类实现原理)
    • 1,配置类方法入口checkConfigurationClassCandidate
    • 2,解析配置类doProcessConfigurationClass
      • [2.1. @PropertySource 解析](#2.1. @PropertySource 解析)
      • [2.2. @ComponentScan 解析](#2.2. @ComponentScan 解析)
      • [2.3. @Import 解析](#2.3. @Import 解析)
      • [2.4. @ ImportResource 解析](#2.4. @ ImportResource 解析)
      • [2.5. @Bean 解析](#2.5. @Bean 解析)
      • [2.6. @父类解析](#2.6. @父类解析)
    • 3,注册配置类的beanDefinition
      • [3.1. @Import注解生成BeanDefinition](#3.1. @Import注解生成BeanDefinition)
      • [3.2. @Bean注解生成BeanDefinition](#3.2. @Bean注解生成BeanDefinition)
      • [3.3. @ImportResources注解生成BeanDefinition](#3.3. @ImportResources注解生成BeanDefinition)

如需转载,请附上链接:https://blog.csdn.net/zhenghuishengq/article/details/148657333

一. 深入理解spring配置类实现原理

在spring中,内部存在一些配置类,配置注解,在我们使用这些注解的时候,可以手动的将一些配置类加载到spring容器中,类似与比较常规的 @Configuration 注解等,除了这个注解之外,spring内部也提供了多个这种注解,接下来从spring源码中,来分析这些配置类以及配置注解

在spring源码中,会解析配置类,校验是否配置类的入口如下,通过下面这个方法来判断该

java 复制代码
ConfigurationClassUtils.checkConfigurationClassCandidate(...)

1,配置类方法入口checkConfigurationClassCandidate

其方法详情如下,主要是通过获取类上面的注解,然后通过注解来判断该类是不是配置类

首先会判断这个类上面是不是加了这个 @Configuration 注解

java 复制代码
AnnotationMetadata metadata;
// 校验是不是加了 @Configuration 注解
if (isFullConfigurationCandidate(metadata)) {
    //如果加了,那么给这个注解上面加一个标记
	beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) {
	return metadata.isAnnotated(Configuration.class.getName());
}

接下来根据源码继续往下走,上面是一个full重的配置类,接下来会判断一些lite配置类,即一些比较轻的配置类

java 复制代码
else if (isLiteConfigurationCandidate(metadata)) {
	beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}

这个lite轻的配置类的具体实现如下,首先会判断要加载的类上面的注解包不包括在 candidateIndicators 集合列表中, 这个set集合中包括的主要注解如下:Component、ComponentScan、Import、ImportResource 这四个,除了这几个之外,下面还会判断内部是否有 @Bean 注解

java 复制代码
private static final Set<String> candidateIndicators = new HashSet<>(8);
static {
	candidateIndicators.add(Component.class.getName());
	candidateIndicators.add(ComponentScan.class.getName());
	candidateIndicators.add(Import.class.getName());
	candidateIndicators.add(ImportResource.class.getName());
}

也就是说,只要类上面有 以下注解,那么spring就会将这些类扫描并解析成一个配置类

  • @Configuration
  • @Bean
  • @Component
  • @ComponentScan
  • @Import
  • @ImportResource

2,解析配置类doProcessConfigurationClass

在refresh方法中,会有一个 invokeBeanFactoryPostProcessors 方法,在内部就会调用到这个 doProcessConfigurationClass 真正的去解析这个配置类的方法。接下来直接看源码,看看内部到底是如何解析的。

2.1. @PropertySource 解析

首先第一步是解析带有这个 PropertySource 注解的实体类,这个注解就是用于处理 Properties 配置文件的,将配置文件中的内容加载到spring容器内部

java 复制代码
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
		sourceClass.getMetadata(), PropertySources.class,
		org.springframework.context.annotation.PropertySource.class)) {
	if (this.environment instanceof ConfigurableEnvironment) {
		processPropertySource(propertySource);
	}
}

2.2. @ComponentScan 解析

接下来解析的是这个 ComponentScan 注解,ComponentScan说白了就是对 @Component注解的扫描,判断类上面是否有这个 @Component注解,在前面讲解bean的生命周期的时候重点的讲解过。

java 复制代码
for (AnnotationAttributes componentScan : componentScans) {
	//把我们扫描出来的类变为bean定义的集合 真正的解析
	Set<BeanDefinitionHolder> scannedBeanDefinitions =
			this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
	//循环处理我们包扫描出来的bean定义
	for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
		BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
		if (bdCand == null) {
			bdCand = holder.getBeanDefinition();
		}
		//判断当前扫描出来的bean定义是不是一个配置类,若是的话 直接进行递归解析
		if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
			//递归解析 因为@Component算是lite配置类
			parse(bdCand.getBeanClassName(), holder.getBeanName());
		}
	}
}

首先会将这些扫描到的类解析成一个 BeanDefinition,判断是否存在includeFilters中,excludeFilters中,然后判断是否@Lazy @DependsOn等注解,最后将这些属性全部注册到beanDefinition中返回

java 复制代码
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName())

最后又会判断这个类属不属于配置类,属于的话再次递归解析,判断这个类可不可能存在父类也需要解析成配置类等,直到返回null跳出递归

java 复制代码
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
	//递归解析 因为@Component算是lite配置类
	parse(bdCand.getBeanClassName(), holder.getBeanName());
}

2.3. @Import 解析

接下来就是处理Import的注解,这个部分的解析相对绕一些。首先会调用这个 getImports 方法获取全部带有Import的类

java 复制代码
processImports(configClass, sourceClass, getImports(sourceClass), true);

然后遍历这些获取的Import实例,里面会有三个分支,校验这些加了Import的类上面是否是单纯的加了这个注解,还是同时实现了别的接口,要对相关实现的接口做不同的判断

java 复制代码
for (SourceClass candidate : importCandidates) {
    ...
}
  • 1,首先是先看只有单纯加了Import的配置类,spring会直接将这种配置类进行解析注册
java 复制代码
else {
	// 当做配置类再解析,注意这里会标记:importedBy,  表示这是Import的配置的类
	// 再执行之前的processConfigurationClass()方法 ,
	this.importStack.registerImport(
			currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
	processConfigurationClass(candidate.asConfigClass(configClass));
}		
  • 2,随后是判断这个加了Import注解的类,同时实现了 ImportSelector 接口,如果是的话,那么会先实例化一个 ImportSelector 组件,然后通过递归的方式,将一个ImportSelector组件解析成上面多个 Import 组件,最后又调用这个 processImports 方法,将解析成多个的import组件走上面的这个else逻辑
java 复制代码
if (candidate.isAssignable(ImportSelector.class)) {
	// Candidate class is an ImportSelector -> delegate to it to determine imports
	Class<?> candidateClass = candidate.loadClass();
	//实例化我们的SelectImport组件
	ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
	//调用相关的aware方法
	ParserStrategyUtils.invokeAwareMethods(
			selector, this.environment, this.resourceLoader, this.registry);
	//判断是不是延时的DeferredImportSelectors,是这个类型 不进行处理
	if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
		this.deferredImportSelectors.add(
				new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
	}
	else {//不是延时的
		//调用selector的selectImports
		String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
		// 所以递归解析-- 直到成普通组件
		Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
		processImports(configClass, currentSourceClass, importSourceClasses, false);
	}
}
  • 3,最后再看这个加了Import注解,同时也实现了 ImportBeanDefinitionRegistrar 接口的方法,他和上面这个实现 ImportSelector 的方式不同,不会解析转成多个Import,而是作为一个属性加入到 ConfigurationClass配置类中
java 复制代码
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
	Class<?> candidateClass = candidate.loadClass();
	//实例话我们的ImportBeanDefinitionRegistrar对象
	ImportBeanDefinitionRegistrar registrar =
			BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
	ParserStrategyUtils.invokeAwareMethods(
			registrar, this.environment, this.resourceLoader, this.registry);
	//保存我们的ImportBeanDefinitionRegistrar对象 currentSourceClass=所在配置类
	configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}

2.4. @ ImportResource 解析

接下来就是处理这个 ImportResource 注解,其主要实现如下

java 复制代码
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);
	}
}

其核心就在于下面这句,用于获取本地的一些环境变量或者一些属性,比如获取一些 xml 文件中的一些配置属性等,然后将内容读取到配置文件

java 复制代码
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);

最后也是作为一个属性加入到 ConfigurationClass 配置文件中,和上面实现这个 ImportBeanDefinitionRegistrar 接口的一样,目前只是将这些属性加入到配置文件中,还有生成具体的beanDefinition

java 复制代码
configClass.addImportedResource(resolvedResource, readerClass);

2.5. @Bean 解析

接下来就是接下这个 @Bean注解,这里也比较简单,就是在类中获取全部带有 @Bean的注解,然后去遍历这些元数据,然后将这些打包成一个 BeanMethod实体,加入到ConfigurationClass配置文件中

java 复制代码
// 获取全部带有 @Bean的方法
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
	configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

除了在类中获取 @Bean 注解之外,他还有一个在接口中获取该注解的解析。比如有的接口的default方法中加了这个注解进行注入,但其实现和在类中找一致,也是将这些打包成BeanMethod实体类,加入到配置文件中

java 复制代码
processInterfaces(configClass, sourceClass);

private void processInterfaces(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
	for (SourceClass ifc : sourceClass.getInterfaces()) {
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(ifc);
		for (MethodMetadata methodMetadata : beanMethods) {
			if (!methodMetadata.isAbstract()) {
				// A default method or other concrete method on a Java 8+ interface...
				configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
			}
		}
		processInterfaces(configClass, ifc);
	}
}

2.6. @父类解析

最后就剩一个父类解析,因为很多地方涉及到递归解析,判断父类是否也是一个配置类,因此需要在此处进行处理

递归直到没有父类需要解析,那么就会返回一个null,前面的while需要通过这个返回的null跳出递归,可以参考源码实现。

java 复制代码
// 处理配置类的父类的 ,循环再解析
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;

3,注册配置类的beanDefinition

上面PropertySource 、ComponentScan 、Import 都注册成了对应的beanDefinition,而@Bean、@ImportResource 注解目前只是封装成了实体加载到了配置类中,还没有进行注册到对应beanDefinition中,那么继续看源码,底层是如何将后面加到配置类的几个注册成bean定义的

还是得回到 processConfigBeanDefinitions 中,查看这个@Bean和实现了这个 ImportBeanDefinitionRegistrar 的 @Import方法时如何解析成BeanDefinition的,这块内容在前面讲解bean的生命周期的时候详细的讲过,这个也是前面的一个流程

java 复制代码
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    // 此处才把@Bean的方法和@Import 注册到BeanDefinitionMap中
	this.reader.loadBeanDefinitions(configClasses);
}

接下来直接查看这个 loadBeanDefinitions 方法,里面也没做特别的事情,就是一个循环

java 复制代码
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
	TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
	//注册我们的配置类到容器中
	for (ConfigurationClass configClass : configurationModel) {
		loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
	}
}

接下来重点查看这个 loadBeanDefinitionsForConfigurationClass 方法,在这个方法中,对@Import、@Bean、@ImportResources、@ImportBeanDefinition 注解进行解析,然后生成对应的beanDefinition

3.1. @Import注解生成BeanDefinition

首先会判断这个配置类是不是导入进来的

java 复制代码
if (configClass.isImported()) {
	registerBeanDefinitionForImportedConfigurationClass(configClass);
}

如果这个类是通过Import导入进来的,那么会调用这个 registerBeanDefinitionForImportedConfigurationClass 方法来将这个类注册成 BeanDefinition,会设置一些元数据信息,bean定义,作用域等,最后通过注册的方式将这个实体类注册成一个BeanDefinition

3.2. @Bean注解生成BeanDefinition

随后会判断这个配置类上面是不是通过@Bean的方式注入进来的,如果是通过@Bean方式注入进来的,前面已经将@Bean的配置类包装成了 BeanMethod 实体类,因此只需要遍历这个配置类中的 BeanMethods实体类集合即可

java 复制代码
//是不是通过我们的@bean导入进来的组件
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
	loadBeanDefinitionsForBeanMethod(beanMethod);
}

这个解析会相对复杂点,里面设计多重校验,最后注册成BeanDefinition

java 复制代码
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
    //设计大量的校验`
	...
    // 最后注册成BeanDefinition中
    this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}

3.3. @ImportResources注解生成BeanDefinition

随后判断是不是importResources注解注入进来的

java 复制代码
private void loadBeanDefinitionsFromImportedResources(){
    ...
}

主要是加载一些grovvy配置文件或者xml文件,然后将这些配置文件设置到环境变量中

最后将这个配置文件通过reader读取的方式加载到配置文件中

java 复制代码
reader.loadBeanDefinitions(resource);
相关推荐
xiaoye370820 小时前
Spring 中高级面试题
spring·面试
枫叶落雨22221 小时前
ShardingSphere 介绍
java
花花鱼21 小时前
Spring Security 与 Spring MVC
java·spring·mvc
言慢行善21 小时前
sqlserver模糊查询问题
java·数据库·sqlserver
专吃海绵宝宝菠萝屋的派大星21 小时前
使用Dify对接自己开发的mcp
java·服务器·前端
大数据新鸟1 天前
操作系统之虚拟内存
java·服务器·网络
Tong Z1 天前
常见的限流算法和实现原理
java·开发语言
凭君语未可1 天前
Java 中的实现类是什么
java·开发语言
He少年1 天前
【基础知识、Skill、Rules和MCP案例介绍】
java·前端·python
克里斯蒂亚诺更新1 天前
myeclipse的pojie
java·ide·myeclipse