Springboot3.0源码揭秘 --SpringApplication初始化

274 阅读7分钟

在了解了SpringApplication这个类的大致作用之后,我们来看看这个类初始化的时候做了些啥?随着main方法的启动,最终会来到这里,去new SpringApplication,并将main方法所在的类的class作为primarySources传入

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		// 实例化SpringApplication,为一些核心属性进行赋值,然后再调用run方法
		return new SpringApplication(primarySources).run(args);
	}

最终调用SpringApplication的构造方法,进行SpringApplication的实例化

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		// resourceLoader赋值,默认是null
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		// 将primarySources转set,保存到primarySources
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		// 从classpath确定webApplicationType,这里通过默认的类加载器去尝试加载类
		// 先判断是不是REACTIVE,再判断是否非web,如果均不是,那么默认返回SERVLET
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		// 到META-INF/spring.factories中获取bootstrapRegistryInitializers
		this.bootstrapRegistryInitializers = new ArrayList<>(
				getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
		// 到META-INF/spring.factories中获取ApplicationContextInitializer
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		// 到META-INF/spring.factories中获取ApplicationListener
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		// 将运行main方法的类也赋值,后面会用到
		this.mainApplicationClass = deduceMainApplicationClass();
	}

1、设置resourceLoader

this.resourceLoader = resourceLoader;

当我们使用SpringApplication.run运行Springboot程序的时候,默认情况下,resourceLoader是null。resourceLoader从其命名可以猜出,是做资源加载的,也可看其类注释,其主要作用就是用于从class path或文件系统中加载资源的。如果想自定义这个资源加载器,也可以直接在main方法中new SpringApplication,调用上面的构造函数。这里虽然默认是null,但是后面做资源加载的时候,会去判断并正确使用resourceLoader

2、设置webApplicationType

this.webApplicationType = WebApplicationType.deduceFromClasspath();

我们看看究竟是怎么判断webApplicantType的

static WebApplicationType deduceFromClasspath() {
		// classpath中存在DispatcherHandler,并且不存在DispatcherServlet,
		// 并且不存在ServletContainer,则webApplicationType为REACTIVE
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		// classPath中不存在jakarta.servlet.Servlet、
		// org.springframework.web.context.ConfigurableWebApplicationContext,怎非web类型
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}

代码非常简单,直接用ClassUtils.isPresent进行判断,该方法会判断提供的名称标识的类是否存在并且可以加载。如果类或其依赖项之一不存在或无法加载,则返回 false。也就是做了两件事,即进行类加载,同时判断classPath下是否存在该类。注意ClassUtils.isPresent这里入参的classLoader都是null,后面加载类的时候用的是默认的classLoader。

deduceFromClasspath整个流程也非常简单,先判断是否REACTIVE,在判断是否非web项目,如果都不符合,则默认是SERVLET。这里得到类型之后,后面启动的时候会更具类型做不通的处理。

3、获取扩展点

接下来这几行的实现都是一样的,主要是为了获取Springboot启动时的一些拓展点:BootstrapRegistryInitializer、ApplicationContextInitializer、ApplicationListener

// 到META-INF/spring.factories中获取bootstrapRegistryInitializers
this.bootstrapRegistryInitializers = new ArrayList<>(
		getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 到META-INF/spring.factories中获取ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitiali
// 到META-INF/spring.factories中获取ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

这个三个操作都是通过getSpringFactoriesInstances来获取扩展点的,只是入参的class不同,获取不同的拓展点,我们深入看看:

private <T> List<T> getSpringFactoriesInstances(Class<T> type) {
	return getSpringFactoriesInstances(type, null);
}
private <T> List<T> getSpringFactoriesInstances(Class<T> type, ArgumentResolver argumentResolver) {
	return SpringFactoriesLoader.forDefaultResourceLocation(getClassLoader()).load(type, argumentResolver);
}

最终调用的是:

SpringFactoriesLoader.forDefaultResourceLocation(getClassLoader()).load(type, argumentResolver);

这一行代码其实做了挺多事情:

3.1 获取classLoader

先通过getClassLoader()获取类加载器,一般情况下,我们启动的时候没设置resourceLoader,这里用 的就是默认的classLoader

public ClassLoader getClassLoader() {
	// 判断是否有自定义resourceLoader,有则使用其classLoader
	if (this.resourceLoader != null) {
		return this.resourceLoader.getClassLoader();
	}
	// 使用默认classLoader
	return ClassUtils.getDefaultClassLoader();
}

3.2 获取SpringFactoriesLoader

通过SpringFactoriesLoader.forDefaultResourceLocation获取SpringFactoriesLoader。这个方法的注释挺重要的,所以一并附上:

通过forDefaultResourceLocation,创建一个 SpringFactoriesLoader 实例,该实例将使用给定的类加载器从 FACTORIES_RESOURCE_LOCATION加载和实例化工厂实现。

/**
 * Create a {@link SpringFactoriesLoader} instance that will load and
 * instantiate the factory implementations from
 * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader.
 * @param classLoader the ClassLoader to use for loading resources; can be
 * {@code null} to use the default
 * @return a {@link SpringFactoriesLoader} instance
 * @since 6.0
 * @see #forDefaultResourceLocation()
 */
public static SpringFactoriesLoader forDefaultResourceLocation(@Nullable ClassLoader classLoader) {
	return forResourceLocation(FACTORIES_RESOURCE_LOCATION, classLoader);
}

静态变量FACTORIES_RESOURCE_LOCATION的值是 "META-INF/spring.factories",看到这里大家应该不陌生吧?如果你之前实现过这些扩展或者自定义过starter,那这个值应该非常熟悉。

接着,我们进入forResourceLocation

public static SpringFactoriesLoader forResourceLocation(String resourceLocation, @Nullable ClassLoader classLoader) {
	Assert.hasText(resourceLocation, "'resourceLocation' must not be empty");
	//  获取classLoader
    ClassLoader resourceClassLoader = (classLoader != null ? classLoader :
			SpringFactoriesLoader.class.getClassLoader());
    // 通过resourceClassLoader获取cache中的Map<String, SpringFactoriesLoader>
	Map<String, SpringFactoriesLoader> loaders = cache.computeIfAbsent(
			resourceClassLoader, key -> new ConcurrentReferenceHashMap<>());
    // 从Map<String, SpringFactoriesLoader>获取SpringFactoriesLoader
    //  这里需要关注下loadFactoriesResource
	return loaders.computeIfAbsent(resourceLocation, key ->
			new SpringFactoriesLoader(classLoader, loadFactoriesResource(resourceClassLoader, resourceLocation)));
}

代码也非常简单,先获取ClassLoader,因为我们入参已经传入默认的classLoader,所以resourceClassLoader就是我们入参传的

然后,从cache中,获取Map<String, SpringFactoriesLoader>,这里cache中存储了classLoader以及其Map<ClassLoader, Map<String, SpringFactoriesLoader>>的对应关系,简单的缓存了下。

这里要注意computeIfAbsent,默认情况下,cache里面可能啥都没有,根据resourceClassLoader找不对的时候,会通过key -> new ConcurrentReferenceHashMap<>())创建一个map,下面的操作也是一样的。

拿到loaders之后,同样通过computeIfAbsent操作,通过resourceLocation,去获取SpringFactoriesLoader,没有的情况下,会自己实例化一个。new SpringFactoriesLoader比较简单,只是对属性进行赋值。但是,这里第二个入参执行了

loadFactoriesResource(resourceClassLoader, resourceLocation))

这里也是真正进行资源加载的地方,会去加载META-INF/spring.factories下的东西,我们仔细看看

	protected static Map<String, List<String>> loadFactoriesResource(ClassLoader classLoader, String resourceLocation) {
		Map<String, List<String>> result = new LinkedHashMap<>();
		try {
            // 从classPath中寻找资源
			Enumeration<URL> urls = classLoader.getResources(resourceLocation);
			while (urls.hasMoreElements()) {
				UrlResource resource = new UrlResource(urls.nextElement());
				// 加载配置
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				properties.forEach((name, value) -> {
					String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) value);
					List<String> implementations = result.computeIfAbsent(((String) name).trim(),
							key -> new ArrayList<>(factoryImplementationNames.length));
					Arrays.stream(factoryImplementationNames).map(String::trim).forEach(implementations::add);
				});
			}
			result.replaceAll(SpringFactoriesLoader::toDistinctUnmodifiableList);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" + resourceLocation + "]", ex);
		}
        // 转成不可变map
		return Collections.unmodifiableMap(result);
	}

这里会去查找依赖的jar包里的META-INF/spring.factories,将对应的value按逗号分割之后转成list,形成Map<String, List>的形式进行返回。具体看项目的classPath下有多少配置文件,均会进行加载。

在这个项目中会加载下列的jar包中的META-INF/spring.factories配置

spring-boot-autoconfigure-3.3.8-SNAPSHOT.jar

spring-boot-3.3.8-SNAPSHOT.jar

spring-aop-6.1.16.jar

我们也可以到对应的项目,查看具体的配置

配置文件加载完成之后,最终形成最终形成Map<String, List>,key是接口,value是对应的实现,这些值最终在new SpringFactoriesLoader的时候,设置到了SpringFactoriesLoader的factories

具体的key如下

"org.springframework.boot.autoconfigure.AutoConfigurationImportListener"

"org.springframework.boot.autoconfigure.AutoConfigurationImportFilter"

"org.springframework.boot.diagnostics.FailureAnalyzer"

"org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector"

"org.springframework.context.ApplicationListener"

"org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider"

"org.springframework.boot.sql.init.dependency.DatabaseInitializerDetector"

"org.springframework.context.ApplicationContextInitializer"

"org.springframework.boot.env.EnvironmentPostProcessor"

"org.springframework.boot.diagnostics.FailureAnalysisReporter"

"org.springframework.boot.ApplicationContextFactory"

"org.springframework.boot.SpringApplicationRunListener"

"org.springframework.boot.logging.LoggingSystemFactory"

"org.springframework.boot.env.PropertySourceLoader"

"org.springframework.core.io.ProtocolResolver"

"org.springframework.boot.SpringBootExceptionReporter"

"org.springframework.boot.context.config.ConfigDataLocationResolver"

"org.springframework.boot.context.config.ConfigDataLoader"

"org.springframework.beans.factory.generator.BeanRegistrationContributionProvider"

3.3 加载并实例化

在加载到配置之后,我们会得到SpringFactoriesLoader,紧接着会进行类的加载,即调用load的方法进行加载

SpringFactoriesLoader.forDefaultResourceLocation(getClassLoader()).load(type, argumentResolver);

这里的type分三次调用,有三个,分别是

BootstrapRegistryInitializer.class

ApplicationContextInitializer.class

ApplicationListener.class

load方法的调用如下:

public <T> List<T> load(Class<T> factoryType, @Nullable ArgumentResolver argumentResolver) {
		return load(factoryType, argumentResolver, null);
	}

public <T> List<T> load(Class<T> factoryType, @Nullable ArgumentResolver argumentResolver,
			@Nullable FailureHandler failureHandler) {

		Assert.notNull(factoryType, "'factoryType' must not be null");
        // 在SpringFactoriesLoader的factories中查找接口对应的实现
		List<String> implementationNames = loadFactoryNames(factoryType);
		logger.trace(LogMessage.format("Loaded [%s] names: %s", factoryType.getName(), implementationNames));
		List<T> result = new ArrayList<>(implementationNames.size());
		FailureHandler failureHandlerToUse = (failureHandler != null) ? failureHandler : THROWING_FAILURE_HANDLER;
		// 通过反射进行实例化
        for (String implementationName : implementationNames) {
			T factory = instantiateFactory(implementationName, factoryType, argumentResolver, failureHandlerToUse);
			if (factory != null) {
				result.add(factory);
			}
		}
        // 排序
		AnnotationAwareOrderComparator.sort(result);
		return result;
	}

这里loadFactoryNames其实查找的即使刚刚解析META-INF下的那些配置形成的K-V

private List<String> loadFactoryNames(Class<?> factoryType) {
		return this.factories.getOrDefault(factoryType.getName(), Collections.emptyList());
	}

在实例化完成之后,会对结果进行排序

AnnotationAwareOrderComparator.sort(result);

也就是获取实现类上的@order 或 @priority注解,按其指定的值进行排序。这里为什么要进行排序呢?因为这些实现可能有先后顺序,必须严格按顺序执行,这里排序之后后面拿到直接执行就可以了。举个不太严谨的例子:程序中要先自己创建一个数据库,然后再新增数据,此时两者有前后关系,你如果是先新增数据,再去创建数据库,这不乱套了吗

在处理完BootstrapRegistryInitializer.class、ApplicationContextInitializer.class、ApplicationListener.class并将其实现类的实例设置到SpringApplication之后,

会将运行main方法的class也设置进去,最终完成了整个SpringApplication的实例化

this.mainApplicationClass = deduceMainApplicationClass();

到此,SpringApplication实例化完毕。最后,大家在想想,怎么自己实现BootstrapRegistryInitializer.class、ApplicationContextInitializer.class、ApplicationListener.class这几个扩展点并运行项目,动动手,好记性不如烂笔头。