欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java Springboot自动装配原理详解

程序员文章站 2022-03-09 10:55:12
目录debug路线图debug路线图说多都是泪,大家看图。让我们从run说起用了这么多年的的springboot,这个run()方法到底做了些什么事呢?@springbootapplicationpu...

debug路线图

说多都是泪,大家看图。

Java Springboot自动装配原理详解

让我们从run说起

用了这么多年的的springboot,这个 run() 方法到底做了些什么事呢?

@springbootapplication
public class springbootdemoapplication {
    public static void main(string[] args) {
        springapplication.run(springbootdemoapplication.class, args);
    }
}

归属

run() 方法归属于 springapplication.class 对象,所以在调用run() 方法前,需要先实例化 springapplication.class 对象:

	public static configurableapplicationcontext run(class<?>[] primarysources, string[] args) {
		return new springapplication(primarysources).run(args);
	}

springapplication.class 对象实例化时,都做了些什么事呢?

这里主要看需要注意两个方法:①getspringfactoriesinstances() 和 ②deducemainapplicationclass()

/**
 * 实例化时,实际调用的构造方法
 */
public springapplication(resourceloader resourceloader, class<?>... primarysources) {
		this.resourceloader = resourceloader;
		assert.notnull(primarysources, "primarysources must not be null");
		this.primarysources = new linkedhashset<>(arrays.aslist(primarysources));
		// 这里将spring.
		this.webapplicationtype = webapplicationtype.deducefromclasspath();
		this.bootstrapregistryinitializers = getbootstrapregistryinitializersfromspringfactories();
		// 设置初始化器
		setinitializers((collection) getspringfactoriesinstances(applicationcontextinitializer.class));
		// 设置监听器
		setlisteners((collection) getspringfactoriesinstances(applicationlistener.class));
		this.mainapplicationclass = deducemainapplicationclass();
	}

getspringfactoriesinstances()方法主要加载整个应用程序中的 spring.factories 文件,将文件的内容放到缓存对象中,方便后续获取使用。

private static map<string, list<string>> loadspringfactories(classloader classloader) {
		// 初次加载,cache中获取不到数据,所以为null
		map<string, list<string>> result = cache.get(classloader);
		if (result != null) {
			return result;
		}
		result = new hashmap<>();
		try {
			// factories_resource_location = "meta-inf/spring.factories",这个配置类眼熟吧
			// 加载配置类
			enumeration<url> urls = classloader.getresources(factories_resource_location);
			while (urls.hasmoreelements()) {
				url url = urls.nextelement();
				urlresource resource = new urlresource(url);
				// 这里加载资源
				properties properties = propertiesloaderutils.loadproperties(resource);
				for (map.entry<?, ?> entry : properties.entryset()) {
					string factorytypename = ((string) entry.getkey()).trim();
					string[] factoryimplementationnames =
							stringutils.commadelimitedlisttostringarray((string) entry.getvalue());
					for (string factoryimplementationname : factoryimplementationnames) {
						result.computeifabsent(factorytypename, key -> new arraylist<>())
								.add(factoryimplementationname.trim());
					}
				}
			}
			// replace all lists with unmodifiable lists containing unique elements
			result.replaceall((factorytype, implementations) -> implementations.stream().distinct()
					.collect(collectors.collectingandthen(collectors.tolist(), collections::unmodifiablelist)));
			// 这里将获取到资源放入cache中
			cache.put(classloader, result);
		}
		catch (ioexception ex) {
			throw new illegalargumentexception("unable to load factories from location [" +
					factories_resource_location + "]", ex);
		}
		return result;
	}

deducemainapplicationclass() 方法返回了启动类的类信息:

Java Springboot自动装配原理详解

小结

实例化springapplication.class 对象完成了两件事:

1.加载整个应用程序中的 spring.factories 文件,将文件的内容放到缓存对象中,方便后续获取使用。

2.返回了启动类的类信息。

run

有了springapplication.class 对象实例对象,接下来就可以调用run() 方法。

在run() 方法中,我们主要关注两个方法preparecontext() 和 refreshcontext()

public configurableapplicationcontext run(string... args) {
// 省略部分代码
	try {
		preparecontext(bootstrapcontext, context, environment, listeners, applicationarguments, printedbanner);
		refreshcontext(context);
	}
// 省略部分代码

preparecontext()方法准备上下文环境,通过调用load()方法加载启动类,为获取启动类上的注解做准备;

private void load(class<?> source) {
		if (isgroovypresent() && groovybeandefinitionsource.class.isassignablefrom(source)) {
			// any groovyloaders added in beans{} dsl can contribute beans here
			groovybeandefinitionsource loader = beanutils.instantiateclass(source, groovybeandefinitionsource.class);
			((groovybeandefinitionreader) this.groovyreader).beans(loader.getbeans());
		}
		// 这里会判断启动类不是一个groovy闭包也不是一个匿名类
		if (iseligible(source)) {
			// 注册读取启动类的注解信息
			// 注意,这里将启动类型注册为annotatedbeandefinition类型,后面parse()解析时会用到。
			this.annotatedreader.register(source);
		}
	}

refreshcontext() 方法最终调用了abstractapplicationcontext.class 类的 refresh(),这里相信看过spring源码的小伙伴都很熟悉 refresh() 这个方法。

自动装配操作的主战场主要是在 ①invokebeanfactorypostprocessors() 方法,①调用了②invokebeandefinitionregistrypostprocessors() 方法,②调用了configurationclasspostprocessor.class 的③postprocessbeandefinitionregistry()方法,③调用了 ④processconfigbeandefinitions() 方法;

④ processconfigbeandefinitions() 方法中:

public void processconfigbeandefinitions(beandefinitionregistry registry) {
		list<beandefinitionholder> configcandidates = new arraylist<>();
		string[] candidatenames = registry.getbeandefinitionnames();
		// 这里会循环匹配到启动类,并且添加到上面的configcandidates集合中。
		for (string beanname : candidatenames) {
			beandefinition beandef = registry.getbeandefinition(beanname);
			if (beandef.getattribute(configurationclassutils.configuration_class_attribute) != null) {
				if (logger.isdebugenabled()) {
					logger.debug("bean definition has already been processed as a configuration class: " + beandef);
				}
			}
			else if (configurationclassutils.checkconfigurationclasscandidate(beandef, this.metadatareaderfactory)) {
				configcandidates.add(new beandefinitionholder(beandef, beanname));
			}
		}
		
		// ...
		
		// 解析每一个标注了@configuration注解的类,启动类上的@springbootapplication就包含了@configuration注解
		configurationclassparser parser = new configurationclassparser(
				this.metadatareaderfactory, this.problemreporter, this.environment,
				this.resourceloader, this.componentscanbeannamegenerator, registry);
				set<beandefinitionholder> candidates = new linkedhashset<>(configcandidates);
		set<configurationclass> alreadyparsed = new hashset<>(configcandidates.size());
		do {
			startupstep processconfig = this.applicationstartup.start("spring.context.config-classes.parse");
			// 开始解析
			parser.parse(candidates);
			parser.validate();
	}
		// ============ 分割线 =================
/**
 * 为了方便阅读,这里将parse()方法接入
 */
public void parse(set<beandefinitionholder> configcandidates) {
		for (beandefinitionholder holder : configcandidates) {
			beandefinition bd = holder.getbeandefinition();
			try {
				// 在前面preparecontext()方法中的load()方法中已经说过,所以这会进入这个判断解析
				if (bd instanceof annotatedbeandefinition) {
					parse(((annotatedbeandefinition) bd).getmetadata(), holder.getbeanname());
				}	
		// ...
		}
		// 注意!!这里,parse()解析完成后,会回到这里执行process()方法;
		this.deferredimportselectorhandler.process();
	}

进入判断后parse()方法会接着调用 ①processconfigurationclass()方法,①调用②doprocessconfigurationclass()方法;

doprocessconfigurationclass()中又开始对注解进行进一步的解析,包括@propertysource、@componentscan、@import(咱们看这个)、@importresource、@bean,解析之前,会通过getimports()方法调用collectimports()方法,统计出被@import标注的类型信息;

	protected final sourceclass doprocessconfigurationclass(
			configurationclass configclass, sourceclass sourceclass, predicate<string> filter)
			throws ioexception {
		// ...
		// process any @import annotations
		processimports(configclass, sourceclass, getimports(sourceclass), filter, true);
		// ...
// ============ 分割线 =================
private set<sourceclass> getimports(sourceclass sourceclass) throws ioexception {
		set<sourceclass> imports = new linkedhashset<>();
		set<sourceclass> visited = new linkedhashset<>();
		collectimports(sourceclass, imports, visited);
		return imports;
	}
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"));
		}
	}
}

getimports() 方法查询结果展示:

Java Springboot自动装配原理详解

parse()方法解析完成 @import 注解后(这里忘记的小伙伴,可看看上面的parse()方法,我有代码注释),接着开始调用①process()方法,①中调用②processgroupimports()方法,②中接着调用 ③grouping.getimports()方法,③调用deferredimportselector.group 接口的 ④process()方法,这里我们看它的实现类 autoconfigurationimportselector.class 实现的 ④process()方法(这里需要留意一下,一会还会回来用到),④调用了 ⑤getautoconfigurationentry(),⑤中调用了⑥getcandidateconfigurations() 方法;

重点来了:经过上面的一系列方法调用,终于来到这个方法,相信大家在许多博客里都又看到过 ⑥getcandidateconfigurations() 这个方法,但又没有说清楚具体是怎么调用到这个方法的,只是说了这个类会得到待配置的class的类名集合等等;

在⑥这个方法中,显示通过 ⑦getspringfactoriesloaderfactoryclass() 这个方法返回了一个enableautoconfiguration.class 注解对象,然后又通过调用 ⑧loadfactorynames(),⑧又调用了 ⑨loadspringfactories();

⑧⑨方法看着是不是比较眼熟? 是的,我们在初始化springapplication对象时,曾调用过这两个方法,在调用⑨时,将 spring.factories 文件的内容放到cache缓存对象中。

@override
protected list<string> getcandidateconfigurations(annotationmetadata metadata, annotationattributes attributes) {
		// getspringfactoriesloaderfactoryclass()这个方法返回了一个enableautoconfiguration.class注解对象
		list<string> configurations = springfactoriesloader.loadfactorynames(getspringfactoriesloaderfactoryclass(),
				getbeanclassloader());
		assert.notempty(configurations, "no auto configuration classes found in meta-inf/spring.factories. if you "
				+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
}

protected class<?> getspringfactoriesloaderfactoryclass() {
		return enableautoconfiguration.class;
}
// ===========================分割线===========================
private static map<string, list<string>> loadspringfactories(classloader classloader) {
		map<string, list<string>> result = cache.get(classloader);
		if (result != null) {
			return result;
		}
	// ...

此时的cache对象中存在enableautoconfiguration对象,size=131个:

Java Springboot自动装配原理详解

这131个就是 spring.factories 文件中的自动装配配置项:

Java Springboot自动装配原理详解

当然,这里面有许多我们没有用到类信息也被装配了进来,这里不要着急接着往下看,装配完成后回到了⑤getautoconfigurationentry()方法中,且返回了一个list< string>的一个配置类信息集合,接着又做了些什么事?

Java Springboot自动装配原理详解

从上图可以看出,131个配置信息,经过过滤移除后,最终变成13个需要使用的,拿到最终配置信息,(愣着干嘛,赶紧撒花呀!),到这里自动装配过程基本上就结束了。

这里的结束是指自动装配过程结束,也就是我们 refresh()中的invokebeanfactorypostprocessors() 方法执行结束,当然这个方法还做很多别的事,但是本文只关注自动装配相关,完成此方法后并不表示类就已经实例化完成,这里只是将类信息装配到了spring容器中,后续会有别的方法完成类的实例化。(实例化看它:finishbeanfactoryinitialization())

Java Springboot自动装配原理详解

再说说注解

@springbootapplication 是的没错,这个注解大家都熟悉,springboot 项目启动类上都有:

Java Springboot自动装配原理详解

@springbootapplication 中包含了两个注解:

  • @enableautoconfiguration(重点):启用 springboot 的自动配置机制;
  • @componentscan: 扫描被@component (@service,@controller)注解的 bean,注解默认会扫描该类所在的包下所有的类;
  • @springbootconfiguration:允许在上下文中注册额外的 bean 或导入其他配置类;

三个注解中,自动装配的核心 @enableautoconfiguration 就是这个注解:

Java Springboot自动装配原理详解

@enableautoconfiguration 注解通过 spring 提供的 @import 注解导入了 autoconfigurationimportselector.class类(@import 注解可以导入配置类或者 bean 到当前类中),这个类的作用在上也说过(获取spring.factories文件中待配置的class的类名集合)。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注的更多内容!