当前位置: 代码网 > it编程>编程语言>Java > Spring解析配置类和扫描包路径的详细过程

Spring解析配置类和扫描包路径的详细过程

2024年12月19日 Java 我要评论
目标这是我们使用注解方式启动spring容器的核心代码annotationconfigapplicationcontext applicationcontext = new annotationcon

目标

这是我们使用注解方式启动spring容器的核心代码

annotationconfigapplicationcontext applicationcontext = new annotationconfigapplicationcontext(myconfig.class);
user user = (user) applicationcontext.getbean("user");
user.printname();

其中配置类myconfig的代码是

@componentscan(value = "com.mydemo")
public class myconfig {
}

现在我们的目标是搞清楚spring是怎么解析这个配置类并且扫描该配置类包路径下的bean?

重要的组件

  • annotatedbeandefinitionreader : spring容器启动的时候就会创建这个读取器,主要是将类以beandefinition的方式保存到bean工厂(defaultlistablebeanfactory)
    在创建这个读取器的时候,spring会默认添加一个configurationclasspostprocessor的beandefinition,这个就是在解析配置类时的主要对象,在annotationconfigutils类的registerannotationconfigprocessors中实现
if (!registry.containsbeandefinition(configuration_annotation_processor_bean_name)) {
    rootbeandefinition def = new rootbeandefinition(configurationclasspostprocessor.class);
    def.setsource(source);
    beandefs.add(registerpostprocessor(registry, def, configuration_annotation_processor_bean_name));
}
  • classpathbeandefinitionscanner : 路径扫描器,在spring启动的时候就会创建,主要功能就是对类路径进行扫描,内含一些扫描规则,例如在创建时候就会内置一个component注解的过滤器
protected void registerdefaultfilters() {
    this.includefilters.add(new annotationtypefilter(component.class));
    ...
}

加载配置类

我们的配置类是由annotatedbeandefinitionreader类的doregisterbean方法,转成beandefinition存到bean工厂的beandefinitionmap中,基于asm获取一个类信息转成beandefinition。
转成的核心代码

annotatedgenericbeandefinition abd = new annotatedgenericbeandefinition(beanclass);

得到配置类对象的annotatedgenericbeandefinition后,虽然还没有加载类,但是已经获取到了类的注解信息。
虽然都是带有beandefinition,但是保存到bean工厂的beandefinition和这个是不一样的,这个annotatedgenericbeandefinition主要是一些注解信息,并没有类似于beandefinition的属性,如是否懒加载,作用域,是否依赖等。

解析annotatedgenericbeandefinition注解信息的主要代码,主要就是读取lazy、primary 、dependson、description设置成属性值

annotationattributes lazy = attributesfor(metadata, lazy.class);
if (lazy != null) {
	abd.setlazyinit(lazy.getboolean("value"));
}
else if (abd.getmetadata() != metadata) {
	lazy = attributesfor(abd.getmetadata(), lazy.class);
	if (lazy != null) {
		abd.setlazyinit(lazy.getboolean("value"));
	}
}

if (metadata.isannotated(primary.class.getname())) {
	abd.setprimary(true);
}
annotationattributes dependson = attributesfor(metadata, dependson.class);
if (dependson != null) {
	abd.setdependson(dependson.getstringarray("value"));
}

annotationattributes role = attributesfor(metadata, role.class);
if (role != null) {
	abd.setrole(role.getnumber("value").intvalue());
}
annotationattributes description = attributesfor(metadata, description.class);
if (description != null) {
	abd.setdescription(description.getstring("value"));
}

解析annotatedgenericbeandefinition后转成beandefinitionholder才是我们要保存到bean工厂的beandefinition

beandefinitionholder definitionholder = new beandefinitionholder(abd, beanname);

如果配置类不是代理模式,就直接保存beandefinition到bean工厂中了,
如果是代理模式,就创建一个新的rootbeandefinition保存到bean工厂中,主要实现的代码在scopedproxyutils类createscopedproxy方法中

启动解析组件

spring在启动配置类扫描的任务时,是以启动一个beandefinitionregistrypostprocessor的方式调用扫描类执行的,属于一种组件化启动任务类的方式

for (beandefinitionregistrypostprocessor postprocessor : postprocessors) {
	...
	postprocessor.postprocessbeandefinitionregistry(registry);
	...
}

这个组件的实现类是configurationclasspostprocessor,所以所有的扫描代码都在该类的postprocessbeandefinitionregistry方法下

定位配置类

在bean工厂的beandefinitionmap中遍历每个元素来定位符合配置类的bd,规则校验在configurationclassutils类checkconfigurationclasscandidate方法中:

  1. 主要是确定该bd是annotatedbeandefinition类型,
  2. 如果beandef不是annotatedbeandefinition的实例,则进一步检查它是否是abstractbeandefinition的实例并且已经有了对应的class对象。如果是的话,接着会检查这个class是否实现了某些特定接口(如beanfactorypostprocessor, beanpostprocessor, aopinfrastructurebean, 或者eventlistenerfactory)。如果确实实现了这些接口中的一个或多个,函数将返回false,表示不需要继续解析。否则,它将通过annotationmetadata.introspect(beanclass)方法来获取该类的注解元数据。
  3. 如果以上两种情况都不满足,代码将尝试通过metadatareader从类路径中读取指定类名(classname)的元数据。这通常涉及到加载类文件并从中提取信息。如果在这个过程中发生io异常(例如找不到类文件),则记录错误信息并返回false。

解析配置类

解析的操作是configurationclassparser来完成的,所有解析的相关逻辑都在该类的processconfigurationclass方法中,主要负责解析和注册配置类中的各种注解:
处理@propertysource @componentscan @import @importresour @bean注解,这里值分析 @componentscan注解,因为已经获取到了类的元信息,所以就可以获取@componentscan配置的路径,进而进行路径扫描,扫描是交由componentscanannotationparser组件执行的,由componentscanannotationparser组件发起最终在classpathbeandefinitionscanner类型的doscan来实现

扫描过程

set<beandefinition> candidates = findcandidatecomponents(basepackage);

通过调用findcandidatecomponents方法,根据提供的基础包名(basepackage)来查找该包及其子包下的所有符合组件扫描条件的类,并将它们作为候选组件返回。每个候选组件都是一个beandefinition对象,表示潜在的spring bean:

  • 构建搜索路径:
    构建一个资源模式路径,用于指示resourcepatternresolver在哪里查找资源。这个路径包括了类路径前缀、基础包名以及资源模式(例如/**/*.class),以便于匹配所有的类文件。
string packagesearchpath = resourcepatternresolver.classpath_all_url_prefix +
					resolvebasepackage(basepackage) + '/' + this.resourcepattern;
  • 获取资源
resource[] resources = getresourcepatternresolver().getresources(packagesearchpath);

通过getresourcepatternresolver()获取资源解析器实例,并调用其getresources方法来获取与给定模式匹配的所有资源。这里的资源是指符合路径模式的类文件。

  • 初步筛选
    遍历每个资源,使用metadatareaderfactory为每个资源创建一个metadatareader实例,它能够读取类的元数据而无需加载该类到jvm中。
scannedgenericbeandefinition sbd = new scannedgenericbeandefinition(metadatareader);
sbd.setsource(resource);

首先使用iscandidatecomponent(metadatareader)方法初步判断资源是否可能是一个候选组件:

annotationmetadata metadata = beandefinition.getmetadata();
return (metadata.isindependent() && (metadata.isconcrete() ||
				(metadata.isabstract() && metadata.hasannotatedmethods(lookup.class.getname()))));
  1. 类必须是独立的(非内部类)。
  2. 同时,类必须是具体的(非接口或非抽象类),或者如果是抽象类的话,它必须包含至少一个用 @lookup 注解标记的方法。
  • 确定是否创建为beandefinition
scopemetadata scopemetadata = this.scopemetadataresolver.resolvescopemetadata(candidate);
candidate.setscope(scopemetadata.getscopename());
string beanname = this.beannamegenerator.generatebeanname(candidate, this.registry);

对于每个候选的beandefinition,使用scopemetadataresolver解析其作用域(scope)信息,同时为bean生成或获取一个唯一的beanname

if (candidate instanceof abstractbeandefinition) {
    postprocessbeandefinition((abstractbeandefinition) candidate, beanname);
}

如果候选bean是一个abstractbeandefinition类型的实例,则调用postprocessbeandefinition方法进行额外的后处理,比如应用默认值和自动装配规则

if (candidate instanceof annotatedbeandefinition) {
	annotationconfigutils.processcommondefinitionannotations((annotatedbeandefinition) candidate);
}

如果候选bean是annotatedbeandefinition类型,那么将处理常见的注解,如@lazy, @primary, @dependson, @role, 和 @description等

if (checkcandidate(beanname, candidate)) {
	beandefinitionholder definitionholder = new beandefinitionholder(candidate, beanname);
	definitionholder =
			annotationconfigutils.applyscopedproxymode(scopemetadata, definitionholder, this.registry);
	beandefinitions.add(definitionholder);
	registerbeandefinition(definitionholder, this.registry);
}

检查当前候选bean是否可以被注册到容器中,如果可以,继续执行以下操作:
创建一个beandefinitionholder对象,该对象持有bean定义、bean名称以及其他元数据,
如果需要使用applyscopedproxymode根据作用域代理模式来创建作用域代理,
将处理后的beandefinitionholder添加到beandefinitions列表,并注册到registry中。

在checkcandidate中还有一个方法

protected boolean iscompatible(beandefinition newdef, beandefinition existingdef) {
	return (!(existingdef instanceof scannedgenericbeandefinition) ||  // explicitly registered overriding bean
			(newdef.getsource() != null && newdef.getsource().equals(existingdef.getsource())) ||  // scanned same file twice
			newdef.equals(existingdef));  // scanned equivalent class twice
}

检查新的bean定义是否与已存在的bean定义兼容,避免重复扫描同一个文件或者类而引起的冲突。

总结

  1. 配置类加载:使用annotatedbeandefinitionreader将配置类转换为beandefinition,并通过asm库获取类信息。
  2. 启动解析组件:通过实现beandefinitionregistrypostprocessor接口的configurationclasspostprocessor组件来启动配置类的解析任务。
  3. 定位与解析配置类:遍历bean工厂中的所有beandefinition以定位配置类,并使用configurationclassparser处理配置类上的各种注解,如@componentscan。
  4. 组件扫描:classpathbeandefinitionscanner根据指定的基础包名查找符合组件扫描条件的类,进行初步筛选后创建beandefinition对象,最终注册到spring容器中。

以上就是spring解析配置类和扫描包路径的详细过程的详细内容,更多关于spring解析配置类和扫描包路径的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com