SpringBoot启动原理源码分析-第一篇

2022-11-16 21:47:28 浏览数 (1)

TOC

Spring boot启动原理分析(源码精读)

以IDEA通过Spring Initializer方法创建的Spring boot项目为基础,分析Spring boot的启动原理。

0x01_入口类的注解 源码分析

启动类代码:SpringApplication.run方法是整个SpringBoot启动最核心的方法。

代码语言:java复制
package com.bones;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Springbootpro4Application {

    public static void main(String[] args) {
        SpringApplication.run(Springbootpro4Application.class, args);
    }

}

没有注解@SpringBootApplication,是无法启动的。

所以来看注解@SpringBootApplication的源码:

代码语言:java复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

注解@SpringBootApplication标记在某个类上说明这个类是SpringBoot的主要配置类,SpringBoot就应该运行这个类的主要方法来启动SpringBoot应用

除了元注解之外,还有3个注解。可以理解为这个类是3个注解@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan的组合。

@SpringBootConfiguration注解

这个注解:标记在某个类上,表示一个类提供了Spring Boot应用程序

@SpringBootConfiguration源码:

代码语言:java复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {

	
	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;

}

这个注解源码中可以看到还有注解@Configuration,对于这个注解,学习过Spring零XML配置的应该不陌生。要使得项目不用XML配置,那么可以自己书写配置类,要使这个类成为一个配置类,就得加上@Configuration注解。

可以详细来看一下@Configuration注解的源码:

代码语言:java复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {

	boolean proxyBeanMethods() default true;
}

可以简单理解为:Spring中你书写的配置类也好,Spring boot中的“配置类”也好,都相当于Spring中的配置文件,对于XML类型的配置文件,一定都没少配置过吧。applicationContext.xml,web.xml,springmvc.xml等等。。。

配置类也是容器中的一个组件,所以,在@Configuration注解上,有注解@Component,这就代表容器也会创建配置类的对象。

最后再注意一下:@SpringBootConfiguration注解和@Configuration注解都有一个属性:

代码语言:java复制
boolean proxyBeanMethods() default true;

默认值是true,说明这个类会被代理。(这里的代理是指用CGLIB代理)

proxyBeanMethods配置类是用来指定@Bean注解标注的方法是否使用代理,默认是true使用代理,直接从IOC容器之中取得对象;如果设置为false,也就是不使用注解,每次调用@Bean标注的方法获取到的对象和IOC容器中的都不一样,是一个新的对象,所以我们可以将此属性设置为false来提高性能

@EnableAutoConfiguration注解

这个注解的源码:

代码语言:java复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	Class<?>[] exclude() default {};

	String[] excludeName() default {};

}

这个注解告诉SpringBoot开启自动配置功能,这样自动配置才能生效

借助@import,扫描并实例化满足条件的自动配置的bean,然后加载到IOC容器中(或者说借助@Import的支持,收集和注册特定场景相关的bean定义。)


除了元注解,这个类有另外2个注解:

@AutoConfigurationPackage自动配置包注解

当没有base packages或者base package classes指定的时候,有这个注解的类就会被注册。

简单来说,这个注解就是用来包扫描的。

这个注解的源码:

代码语言:java复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
	String[] basePackages() default {};
	Class<?>[] basePackageClasses() default {};
}

这里需要关注的是:@Import(AutoConfigurationPackages.Registrar.class)这个注解:

看一下Registrar这个类的源码:它是AutoConfigurationPackages这个抽象类的一个静态内部类,

代码语言:java复制
	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}

	}

可以打上断点看一下:

image-20221115142136047image-20221115142136047

对于这个参数:new PackageImports(metadata).getPackageNames()可以计算一下:

image-20221115142226594image-20221115142226594

<img src="https://wechat01.oss-cn-hangzhou.aliyuncs.com/img/image-20221115142310589.png" alt="image-20221115142310589" style="zoom:50%;" />

其实到这里@AutoConfigurationPackage这个注解的阅读在Spring boot启动原理这块就可以了。

简单可以理解为:

@AutoConfigurationPackage自动配置包是用来做包扫描的,里面import了AutoConfigurationPackages.Registrar.class,这里面有一个静态内部类,会指定扫描我们项目的哪个包。

@EnableAutoConfiguration还有导入了一个EnableAutoConfigurationImportSelector.class

@Import(EnableAutoConfigurationImportSelector.class):给容器中导入组件

这个类有一堆属性和方法,这里源码就不粘贴了,只看一个关键的方法:getCandidateConfigurations

代码语言:java复制
	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = new ArrayList<>(
				SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
		ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
		Assert.notEmpty(configurations,
				"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
						  "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

使用select选择挑选满足条件的文件,并且使用SpringFactoriesLoader进行实例化。最后加载到IOC容器里面,即ApplicationContext中。

这里我把断点打在了:

image-20221115143654920image-20221115143654920

首先跳在了抽象类AutoConfigurationPackages内部类的RegistrarregisterBeanDefinitions方法上:

代码语言:java复制
	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}

	}

其中metadata是我当前的启动类:

image-20221115144307346image-20221115144307346

然后走了

代码语言:java复制
	public static void register(BeanDefinitionRegistry registry, String... packageNames) {
		if (registry.containsBeanDefinition(BEAN)) {
			BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN);
			beanDefinition.addBasePackages(packageNames);
		}
		else {
			registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
		}
	}

走的是else分支,其中,packageNames是当前我项目的com.bones这个包。

然后走了ImportBeanDefinitionRegistrar这个类:(注意Registrar这个静态内部类本来就实现了ImportBeanDefinitionRegistrar这个接口),registerBeanDefinitions这个方法(ImportBeanDefinitionRegistrar的一个方法)也走完了。

然后走了:ConfigurationClassBeanDefinitionReader这个类,这个类一看就是读取配置信息的。走的方法是loadBeanDefinitionsFromRegistrars:

代码语言:java复制
	private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
		registrars.forEach((registrar, metadata) ->
				registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
	}
image-20221115144846015image-20221115144846015

调用registrar.registerBeanDefinitions方法,再调用loadBeanDefinitionsForConfigurationClassloadBeanDefinitions方法,然后看到了我想看到的内容:

image-20221115145133846image-20221115145133846

这里的ConfigurationClass正好对应的就是getCandidateConfigurations这个方法中写到的META-INF/spring.factories中的内容:

META-INF/spring.factories在哪里?

找找引入的依赖:

image-20221115145351065image-20221115145351065

这个spring-boot-autoconfiguration没有再pom.xml中导入,怎么来的呢?这个就是涉及到Spring boot的配置的问题了:其实它来自spring-boot-starter-web依赖(这个依赖是我们项目导入在pom.xml中的)

image-20221115145541416image-20221115145541416

回到正题:我个人认为注解@Import(AutoConfigurationImportSelector.class)最为重要,这个注解使得SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。

接着往下读:刚才走到的方法是loadBeanDefinitions

代码语言:txt复制
	public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
		TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
		for (ConfigurationClass configClass : configurationModel) {
			loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
		}
	}

里面的for循环将遍历configurationModel这个LinkedHashSet,对每一个配置执行loadBeanDefinitionsForConfigurationClass方法

for循环走完之后这个方法走完,回到了processConfigBeanDefinitions方法,接着走这个方法里面的alreadyParsed方法:

image-20221115151144775image-20221115151144775

可以看到这里的configClasses就是刚才for循环读取的。

@ComponentScan包扫描注解

最后来看SpringBootApplication注解的最后一个注解:

代码语言:java复制
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IOC容器中去 。

0x02_入口类run方法

回到入口类:

代码语言:java复制
@SpringBootApplication
public class Springbootpro4Application {

    public static void main(String[] args) {
        SpringApplication.run(Springbootpro4Application.class, args);
    }

}

上面看的是启动类的注解@SpringBootApplication,下面主要看SpringApplication.run方法。

SpringApplication初始化

这个类可用于从Java main方法引导和启动Spring应用程序。默认情况下,类将执行以下步骤来引导应用程序:

  • 创建一个合适的ApplicationContext实例(取决于你的classpath) ——创建一个容器
  • 注册一个CommandLinePropertySource,将命令行参数公开为Spring属性
  • 刷新应用程序上下文,加载所有单例bean
  • 触发任何CommandLineRunner bean

这个类有一些重要属性和方法,列举如下:

代码语言:java复制
private ResourceLoader resourceLoader;//资源加载器

Resource接口是用来对配置文件进行封装的。ResourceLoader是资源加载器。

其构造方法:

代码语言:java复制
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");//资源加载类不能为 null
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));//// 初始化加载资源类集合并去重(用了set)
		this.webApplicationType = WebApplicationType.deduceFromClasspath();//// 推断应用程序是不是web应用
		this.bootstrapRegistryInitializers = new ArrayList<>(
				getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));//// 设置初始化器(Initializer)
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));//// 设置监听器 
		this.mainApplicationClass = deduceMainApplicationClass();//// 推断出主应用入口类
	}
deduceFromClasspath()

这个方法推断应用程序是不是web应用。

下面开始debug,断点打在启动类的run方法上:

image-20221115153205426image-20221115153205426

会走到构造方法的deduceFromClasspath

image-20221115153258923image-20221115153258923

此时primarySources只有一个:当前的启动类

image-20221115153358879image-20221115153358879

下面走到deduceFromClasspath方法中:

image-20221115153501810image-20221115153501810
代码语言:java复制
	static WebApplicationType deduceFromClasspath() {
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}

这里首先要拓展一下:

创建SpringBoot项目时,如果不选择starter-web,创建的SpringBoot项目可以正常运行,但运行结束程序便终止了。如果配置starter-web,则正常启动web应用。那么,SpringBoot是如何分辨出来当前应用是为web应用还是其他类型的应用呢?这就要靠枚举类WebApplicationType.

枚举类取值有3个:

NONE SERVLET:最常用的类型 REACTIVE响应式web容器

SpringBoot启动时,在创建SpringApplication的构造方法内会调用枚举WebApplicationTypededuceFromClasspath方法获得应用类型并设置当前应用是普通web应用、响应式web应用还是非web应用。

回到debug的WebApplicationType.deduceFromClasspath方法:

首先不走if分支:(因为不是响应式web应用)

代码语言:java复制
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;//判断为响应式web应用
		}

这里有几个常量,定义在WebApplicationType中:

private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler"; private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet"; private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

如果应用程序存在DispatcherHandler并且不存在DispatcherServletServletContainer则为响应式web应用,需加载并启动内嵌的响应式web服务。 如果应用程序不包含Servlet和ConfigurableWebApplicationContext则为普通应用程序。 其他情况则为基于servlet的web应用,需加载并启动内嵌的web服务。

代码语言:java复制
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}

推断的过程中重点调用了ClassUtils.isPresent()方法,用来判断指定类名的类是否存在,是否可以进行加载。ClassUtils.isPresent()方法源代码如下:

代码语言:java复制
    public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
        try {
            forName(className, classLoader);
            return true;
        } catch (IllegalAccessError var3) {
            throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class ["   className   "]: "   var3.getMessage(), var3);
        } catch (Throwable var4) {
            return false;
        }
    }

isPresent方法会调用forName方法,判断目标类是否存在。

forName方法获得类加载器,尝试直接加载类,如果失败则尝试加载该类的内部类,如果依旧失败,则抛出异常。这里具体不再看了(不是重点)。

if分支判断完不是响应式web应用,接着对SERVLET_INDICATOR_CLASSES进行遍历,SERVLET_INDICATOR_CLASSES在这个类中进行了定义:

代码语言:java复制
	private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
			"org.springframework.web.context.ConfigurableWebApplicationContext" };

如果应用程序不包含ServletConfigurableWebApplicationContext则为普通应用程序,返回WebApplicationType.NONE;

如果if不满足,for循环也判断了没有返回,即既不是响应式web应用,也不是普通的应用程序,那么就是基于servlet的web应用。

最终deduceFromClasspath方法返回类型就是 WebApplicationType.SERVLET

bootstrapRegistryInitializers()

接着,获取初始启动器,若spring.factories存在BootstrapRegistryInitializer,则创建实例

代码语言:java复制
this.bootstrapRegistryInitializers = new ArrayList<>(
				getSpringFactoriesInstances(BootstrapRegistryInitializer.class));

spring.factories是没有BootstrapRegistryInitializer的:

image-20221115162607790image-20221115162607790
setInitializers()

然后获取启动器,若spring.factories存在ApplicationContextInitializer,则创建实例

代码语言:java复制
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

显然spring.factories是有ApplicationContextInitializer的:

image-20221115162640087image-20221115162640087
代码语言:java复制
	public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
		this.initializers = new ArrayList<>(initializers);
	}

实现了ApplicationContextInitializer接口,这个接口的源码如下

代码语言:java复制
@FunctionalInterface
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

	/**
	 * Initialize the given application context.
	 * @param applicationContext the application to configure
	 */
	void initialize(C applicationContext);

}

initialize方法将ApplicationContextInitializer的实现类放到了context中:

image-20221115163333480image-20221115163333480

ApplicationContextInitializer接口的作用,在Spring上下文被刷新之前进行初始化的操作。典型地比如在Web应用中,注册Property Sources或者是激活Profiles。Property Sources比较好理解,就是配置文件。Profiles是Spring为了在不同环境下(如DEV,TEST,PRODUCTION等),加载不同的配置项而抽象出来的一个实体。 调用initialize()方法,把初始化的ApplicationContextInitializer实现加载到SpringApplication中,而getSpringFactoriesInstances(ApplicationListener.class))方法就是用来获得实现类的。

setListeners()

然后就是获得监听器:

代码语言:java复制
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

spring.factories中也是有ApplicationListener的:

image-20221115163723484image-20221115163723484

setListeners源码:

代码语言:java复制
	public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
		this.listeners = new ArrayList<>(listeners);
	}

继承自ApplicationListenerApplicationListener的源码:

代码语言:java复制
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	/**
	 * Handle an application event.
	 * @param event the event to respond to
	 */
	void onApplicationEvent(E event);

	static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer) {
		return event -> consumer.accept(event.getPayload());
	}

onApplicationEvent方法用了观察者模式。当被观察者发生变化的时候,观察者需要做一些操作。

deduceMainApplicationClass()

最后是this.mainApplicationClass = deduceMainApplicationClass();

image-20221115164209577image-20221115164209577
代码语言:java复制
	private Class<?> deduceMainApplicationClass() {
		try {
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
			for (StackTraceElement stackTraceElement : stackTrace) {
				if ("main".equals(stackTraceElement.getMethodName())) {
					return Class.forName(stackTraceElement.getClassName());
				}
			}
		}
		catch (ClassNotFoundException ex) {
			// Swallow and continue
		}
		return null;
	}

首先构造了一个异常类,并且获得栈帧的信息:

StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();

image-20221115164602719image-20221115164602719

image-20221115164834220image-20221115164834220

for循环一顿遍历,找到栈帧中methodName是"main"的进行反射实例化。

image-20221115165037515image-20221115165037515
image-20221115165106182image-20221115165106182

到此为止SpringApplication的构造方法走完了,一些属性初始化完毕。然后开始走run方法:

image-20221115165238278image-20221115165238278

run方法

源码在进入try之前有一些操作:

代码语言:java复制
		long startTime = System.nanoTime();
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting(bootstrapContext, this.mainApplicationClass);

System.nanoTime();获得系统当前时间。便于在运行启动类时,打印初始化容器的时间。

createBootstrapContext

createBootstrapContext方法:(这个方法比较简单,就是创建一个对象,然后调用初始化方法)

private DefaultBootstrapContext createBootstrapContext() { DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext(); this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext)); return bootstrapContext; }

创建一个DefaultBootstrapContext对象(可以叫它“启动上下文”,是new出来的),并且启动它。(对初始化所收集到的所有 bootstrapRegistryInitializers 执行 initialize 方法,将 context 作为该方法的参数)一般来说context对象就是用来存储配置信息的。

重点看一下这一句Lambda表达式:

this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));

启动扩展的初始化方法,其实构造器初始化扩展的时候,没有这个扩展:(就是SpringApplication构造方法中的这一句)

this.bootstrapRegistryInitializers = new ArrayList<>( getSpringFactoriesInstances(BootstrapRegistryInitializer.class));

因为当时spring.factories中没有。

configureHeadlessProperty

undefined

configureHeadlessProperty方法:配置无头属性

Headless模式是在缺少显示屏、键盘或者鼠标的系统配置。在java.awt.toolkitjava.awt.graphicsenvironment类中有许多方法,除了对字体、图形和打印的操作外还可以调用显示器、键盘和鼠标的方法。但是有一些类中,比如CanvasPanel,可以在headless模式下执行。

Headless模式虽然不是我们愿意见到的,但事实上我们却常常需要在该模式下工作,尤其服务器端程序开发者。因为服务器往往可能缺少前述设备,但又需要使用他们提供的功能,生成相应的数据,以提供给客户端。

一般可以用如下方式配置,这个要注意一下如果用到了java.awt包下与图形相关的类型请不要设置为无头模式:

JAVA_OPTS="-Djava.awt.headless=true"

具体看一下这个configureHeadlessProperty方法:(开启了Headless模式)

private void configureHeadlessProperty() { System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless))); }

其中SYSTEM_PROPERTY_JAVA_AWT_HEADLESSjava.awt.headless。设置系统属性java.awt.headless的值,默认为true

undefined

设置 java.awt.headless,如果未进行过自定义设置(如:application.setHeadless(false)System.setProperties("java.awt.headless", false)或者 JVM 参数:java -Djava.awt.headless=true),则默认设置为 true。如果项目为非 GUI 类型的,如 console 或者 server 类型,建议设置为 true,否则设置为 false;

这个方法有几个地方可以借鉴的:

定义了有意义的常量 在set之前先get以避免覆盖用户自定义配置 允许用户在程序中配置headless项,所以有了this.headless变量 Boolean.toString这样的写法也是极好的,起到了规范统一的作用

从spring的代码中就可以看到设计者对我们这些普通Developer的友好。

什么时候使用java.awt.headless模式?

对于开发者来讲常常需要在该模式下工作。因为服务器(如提供Web服务的)往往可能缺少前述设备,但又需要使用他们提供的功能,生成相应的数据,以提供给客户端(如浏览器所在的配有相关的、键盘和的主机)。

getRunListeners

getRunListeners:去spring.factroies中读取了SpringApplicationRunListener的组件, 就是用来发布事件或者运行监听器

private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args), this.applicationStartup); }

从 META-INF/spring.factories 中获取所有的配置类,并将其封装到 SpringApplicationRunListeners 对象中去,主要创建的对象为 EventPublishingRunListener

image-20221115222938938image-20221115222938938

image-20221115223840974image-20221115223840974

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { //获取当前的类加载器 resourceLoader的类加载器优先 ClassLoader classLoader = getClassLoader(); // Use names and ensure unique to protect against duplicates // 借助SpringFactoriesLoader来获取所有扩展的扩展名字 Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); // 创建所有扩展的对象,这个方法是createSpringFactoriesInstances List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); // 根据Order对扩展对象进行排序 AnnotationAwareOrderComparator.sort(instances); return instances; }

对于getClassLoader这一步:为什么是resourceLoader优先呢?看下面的源码

public ClassLoader getClassLoader() { if (this.resourceLoader != null) { return this.resourceLoader.getClassLoader(); } return ClassUtils.getDefaultClassLoader(); }

image-20221115230324171image-20221115230324171

创建扩展的对象的方法createSpringFactoriesInstances

代码语言:java复制
	private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
			ClassLoader classLoader, Object[] args, Set<String> names) {
		List<T> instances = new ArrayList<>(names.size());
		for (String name : names) {
			try {
        //ClassUtils.forName()这个方法通过反射机制实现动态加载
				Class<?> instanceClass = ClassUtils.forName(name, classLoader);
				Assert.isAssignable(type, instanceClass);
				Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
				T instance = (T) BeanUtils.instantiateClass(constructor, args);
				instances.add(instance);
			}
			catch (Throwable ex) {
				throw new IllegalArgumentException("Cannot instantiate "   type   " : "   name, ex);
			}
		}
		return instances;
	}

这个方法通过ClassUtils.forName()方法传入要加载的类的类名,和响应的类加载器,来加载类,也就是EventPublishingRunListener

image-20221115230951132image-20221115230951132

try分支中其实也用到了反射创建实例(根据构造方法创建实例,因为调用的是instanceClass.getDeclaredConstructor

注意:方法传入的参数之一:

  • type:Class<T>类型,这里是SpringApplicationRunListener接口。

Assert.isAssignable(type, instanceClass);的第2个参数instanceClass这里是EventPublishingRunListener。这个断言的isAssignable方法是用来检查类型的。经过源码得知:

这个断言会返回true.(其实就是判断是不是指定接口)

然后还有就是在BeanUtils.instantiateClass中创建实例的时候修改了构造器的可见性范围,并提供了对Kotlin的支持。

对于这个方法的参数parameterTypes其实是一路传过来的:

getRunListeners中有:

代码语言:java复制
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };

这个types变量一路穿过来:

getRunListeners---》getSpringFactoriesInstances(XXX,types,XXX,XXX)----》createSpringFactoriesInstances(XXX,parameterTypes,XXX,XXX)这样传过来了。

那么BeanUtils.instantiateClass中创建实例的时候修改构造器的可见性范围是怎么看出来的呢?

来看instanceClass.getDeclaredConstructor(parameterTypes)的一系列调用:

首先是getDeclaredConstructor方法:

image-20221115232927124image-20221115232927124

里面会调用getConstructor0方法

image-20221115232706112image-20221115232706112

对于传入的2个参数:

  • parameterTypes就是刚才说的2个String.classSpringApplication.class
  • which:是getDeclaredConstructor方法中传过来的Member.DECLARED,其定义是:
代码语言:java复制
public static final int DECLARED = 1;

这里简单补充一下Member这个接口里面的2个静态常量:

public static final int PUBLIC = 0; public static final int DECLARED = 1;

PUBLIC 会包括所有的 public 方法,包括父类的方法,而 DECLARED 会包括所有自己定义的方法,public,protected,private 都在此,但是不包括父类的方法。这也正是 getMethodgetDeclaredMethod 的区别。

回到getConstructor0方法:

首先执行:

代码语言:java复制
Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));

显然which == Member.PUBLIC返回的是false,privateGetDeclaredConstructors(false)将返回该类所有构造函数

而当前这个类应该是EventPublishingRunListener,

image-20221115234807978image-20221115234807978

根据实际情况,其实也就之后一个public类型的构造方法。

image-20221115234903027image-20221115234903027

所以:

代码语言:txt复制
Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));

返回的Constructor类型的数组就只有一项:

image-20221115235013544image-20221115235013544

然后是一个for循环,

这里的arrayContentsEq方法:

代码语言:java复制
private static boolean arrayContentsEq(Object[] a1, Object[] a2) {
    if (a1 == null) {
        return a2 == null || a2.length == 0;
    }

    if (a2 == null) {
        return a1.length == 0;
    }

    if (a1.length != a2.length) {
        return false;
    }

    for (int i = 0; i < a1.length; i  ) {
        if (a1[i] != a2[i]) {
            return false;
        }
    }

    return true;
}

这是一个静态方法,其实就是轮循数组寻找参数列表类型匹配的方法

这里插一句话:

逐个比较两个Object数组(实际是Class数组)的对应元素。反射机制无法实现类型兼容的源头正在此处,因为arrayContentsEq方法中是直接用等号比对两个Class对象,因此继承或实现接口等关系就被完全忽略了

这里显然是一致的:

image-20221115235813409image-20221115235813409

找到匹配后,通过ReflectionFactory.copyConstructor复制一份constructor返回;

再回到createSpringFactoriesInstances方法:

image-20221116000026877image-20221116000026877

instanceClass.getDeclaredConstructor(parameterTypes)最后得到的就是EventPublishingRunListener的唯一的构造方法。

然后执行:

代码语言:txt复制
T instance = (T) BeanUtils.instantiateClass(constructor, args);

首先对于kotlin的支持:

image-20221116000507103image-20221116000507103

通过下面的方法获得参数列表:

代码语言:java复制
Class<?>[] parameterTypes = ctor.getParameterTypes();

这里参数列表就是SpringApplicationString类型的。关于EventPublishingRunListener的构造方法:

代码语言:java复制
	public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.application = application;
		this.args = args;
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
		for (ApplicationListener<?> listener : application.getListeners()) {
			this.initialMulticaster.addApplicationListener(listener);
		}
	}

关于这个instantiateClass方法不细看了,最后会创建EventPublishingRunListener实例:

image-20221116000957309image-20221116000957309

实例:

image-20221116001147206image-20221116001147206

再回到createSpringFactoriesInstances方法:

image-20221116001318446image-20221116001318446

最后一步是将得到的唯一的EventPublishingRunListener实例加到List中。最终返回这个list(也就是源码中的instances)。

再回到:getSpringFactoriesInstances

image-20221116002859871image-20221116002859871

最后还要排序,其实没必要了,因为只有一个元素:

image-20221116002949820image-20221116002949820

再回到最一开始的:getRunListeners

image-20221116003056435image-20221116003056435

然后执行new一个SpringApplicationRunListeners实例了,传入参数:

代码语言:txt复制
Log log, Collection<? extends SpringApplicationRunListener> listeners,
			ApplicationStartup applicationStartup

这里的listeners,就是实例化的EventPublishingRunListener实例。

调用构造方法:

image-20221116003308281image-20221116003308281

至此,getRunListeners方法结束了。

listeners.starting发布应用启动事件
代码语言:java复制
listeners.starting(bootstrapContext, this.mainApplicationClass);
image-20221116003539199image-20221116003539199

注意listeners这个类型和前面获取的扩展集合是不一样的要注意,这个类型后面多了个s,前面那个没有s 这个是用来存储与管理调用那个集合的,这里所说的集合就是之前SpringApplication构造方法得到的一堆listener:

image-20221116003847018image-20221116003847018

listeners.starting需要传2个参数:bootstrapContext,this.mainApplicationClass

bootstrapContext是DefaultBootstrapContext bootstrapContext = createBootstrapContext();这一步生成的,就是“启动上下文”对象.

this.mainApplicationClass:

image-20221116004119967image-20221116004119967

就是当前启动类的字节码。

来看starting方法的源码:这里涉及回调方法

代码语言:java复制
	void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
		doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
				(step) -> {
					if (mainApplicationClass != null) {
						step.tag("mainApplicationClass", mainApplicationClass.getName());
					}
				});
	}

调用doWithListeners这个方法:

代码语言:java复制
	private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
			Consumer<StartupStep> stepAction) {
		StartupStep step = this.applicationStartup.start(stepName);
		this.listeners.forEach(listenerAction);
		if (stepAction != null) {
			stepAction.accept(step);
		}
		step.end();
	}

然后需要注意:

代码语言:java复制
(listener) -> listener.starting(bootstrapContext)

来看listener.starting方法:

代码语言:java复制
	@Override
	public void starting(ConfigurableBootstrapContext bootstrapContext) {
		this.initialMulticaster
				.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
	}

就是广播事件。

这里的 this.initialMulticasterSimpleApplicationEventMulticaster简单事件广播器。

调用multicastEvent方法将new创建的ApplicationStartingEvent的传入。

这将涉及到Spring Event事件通知机制,这里不深究。实例化结果:

image-20221116005643184image-20221116005643184

然后来看multicastEvent方法:

代码语言:java复制
	@Override
	public void multicastEvent(ApplicationEvent event) {
		multicastEvent(event, resolveDefaultEventType(event));
	}

然后又调用了重载的multicastEvent的方法,在看这个方法之前来看一下resolveDefaultEventType,作用是获取当前事件对应的类型:

代码语言:java复制
	private ResolvableType resolveDefaultEventType(ApplicationEvent event) {
		return ResolvableType.forInstance(event);
	}

又将调用ResolvableType.forInstance(event);

代码语言:java复制
    public static ResolvableType forInstance(@Nullable Object instance) {
        if (instance instanceof ResolvableTypeProvider) {
            ResolvableType type = ((ResolvableTypeProvider)instance).getResolvableType();
            if (type != null) {
                return type;
            }
        }

        return instance != null ? forClass(instance.getClass()) : NONE;
    }

首先判断instance是不是ResolvableTypeProvider的实例,当前的instance是传入的event,是ApplicationStartingEvent类型的,

ApplicationStartingEvent extends SpringApplicationEvent SpringApplicationEvent extends ApplicationEvent ApplicationEvent extends EventObject EventObject implements java.io.Serializable

显然这里if分支进不了,将走:

return instance != null ? forClass(instance.getClass()) : NONE;

instance不是null,所以返回forClass(instance.getClass())

代码语言:java复制
	public static ResolvableType forClass(@Nullable Class<?> clazz) {
		return new ResolvableType(clazz);
	}

其实就是调用了ResolvableType的构造函数,返回了一个ResolvableType对象,传入的是ApplicationStartingEvent的字节码。

ResolvableType的构造方法:

代码语言:java复制
	private ResolvableType(@Nullable Class<?> clazz) {
		this.resolved = (clazz != null ? clazz : Object.class);
		this.type = this.resolved;
		this.typeProvider = null;
		this.variableResolver = null;
		this.componentType = null;
		this.hash = null;
	}

初始化完成:

image-20221116132720404image-20221116132720404

就是给type属性赋值为ApplicationStartingEvent的字节码,其他属性值都是null。

回到multicastEvent方法:

image-20221116132932897image-20221116132932897

调用重载的multicastEvent方法:

applicationStartingEvent 是 SpringBoot 框架最早执行的监听器,在该监听器执行 started 方法时,会继续发布事件,主要是基于 Spring 的事件机制

代码语言:java复制
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
   ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
   Executor executor = getTaskExecutor();
   for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
      if (executor != null) {
         executor.execute(() -> invokeListener(listener, event));
      }
      else {
         invokeListener(listener, event);
      }
   }
}

参数列表:

  • event:ApplicationStartingEvent的实例
  • eventType:ResolvableType实例,其type属性是ApplicationStartingEvent的字节码。

首先执行:

代码语言:java复制
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));

eventType非空,所以:

代码语言:txt复制
ResolvableType type = eventType = ResolvableType实例,其type属性是ApplicationStartingEvent的字节码

然后执行:获取线程池

代码语言:java复制
Executor executor = getTaskExecutor();
image-20221116134110431image-20221116134110431

此时,SimpleApplicationEventMulticastertaskExecutor还是null。

然后会进入for循环:

代码语言:java复制
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}

先来看getApplicationListeners方法:返回与给定事件类型匹配的ApplicationListeners集合。

代码语言:java复制
	protected Collection<ApplicationListener<?>> getApplicationListeners(
			ApplicationEvent event, ResolvableType eventType) {

		Object source = event.getSource();
		Class<?> sourceType = (source != null ? source.getClass() : null);
		ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

		// Potential new retriever to populate
		CachedListenerRetriever newRetriever = null;

		// Quick check for existing entry on ConcurrentHashMap
		CachedListenerRetriever existingRetriever = this.retrieverCache.get(cacheKey);
		if (existingRetriever == null) {
			// Caching a new ListenerRetriever if possible
			if (this.beanClassLoader == null ||
					(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
							(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
				newRetriever = new CachedListenerRetriever();
				existingRetriever = this.retrieverCache.putIfAbsent(cacheKey, newRetriever);
				if (existingRetriever != null) {
					newRetriever = null;  // no need to populate it in retrieveApplicationListeners
				}
			}
		}

		if (existingRetriever != null) {
			Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();
			if (result != null) {
				return result;
			}
			// If result is null, the existing retriever is not fully populated yet by another thread.
			// Proceed like caching wasn't possible for this current local attempt.
		}

		return retrieveApplicationListeners(eventType, sourceType, newRetriever);
	}

返回3个listener:

<img src="https://wechat01.oss-cn-hangzhou.aliyuncs.com/img/image-20221116134940320.png" alt="image-20221116134940320" style="zoom:50%;" />

不是null的listener对每一个listener执行:异步调用执行监听器中的方法

代码语言:txt复制
executor.execute(() -> invokeListener(listener, event));

默认情况下,spring实现的观察者模式,同步阻塞的。(如果线程池为空,则同步发送事件,否则异步发送事件)

但是这里直接调用了监听器的方法:

image-20221116135357111image-20221116135357111
image-20221116135433149image-20221116135433149

3个监听器都是一样的。

starting方法就差不多了。

image-20221116135630162image-20221116135630162

再次回到run主线:

image-20221116135749930image-20221116135749930
new DefaultApplicationArguments(args)创建默认的应用参数类

先来说说DefaultApplicationArguments这个类:

public class DefaultApplicationArguments implements ApplicationArguments

ApplicationArguments接口提供对用于运行org.springframework.boot.SpringApplication的参数访问,此接口只有一个实现类DefaultApplicationArguments

public DefaultApplicationArguments(String... args) { Assert.notNull(args, "Args must not be null"); this.source = new Source(args); this.args = args; }

走上面的构造方法的时候,传过来的args[]

image-20221116140419578image-20221116140419578

创建完对象之后,这个DefaultApplicationArguments对象有2个属性:

  • source
  • args

在new 一个source实例的时候,会调用:

代码语言:java复制
		Source(String[] args) {
			super(args);
		}

调用Source类父类的构造方法:

代码语言:txt复制
private static class Source extends SimpleCommandLinePropertySource 

即调用:

代码语言:txt复制
	public SimpleCommandLinePropertySource(String... args) {
		super(new SimpleCommandLineArgsParser().parse(args));
	}

SimpleCommandLineArgsParser实例被传入SimpleCommandLinePropertySource的构造方法了。

代码语言:java复制
	public CommandLinePropertySource(T source) {
		super(COMMAND_LINE_PROPERTY_SOURCE_NAME, source);
	}
代码语言:java复制
public EnumerablePropertySource(String name, T source) {
   super(name, source);
}
代码语言:java复制
	public PropertySource(String name, T source) {
		Assert.hasText(name, "Property source name must contain at least one character");
		Assert.notNull(source, "Property source must not be null");
		this.name = name;
		this.source = source;
	}

最后,实例创建完毕:

image-20221116140533065image-20221116140533065

关于PropertySource这个抽象类:

public abstract class PropertySource<T>

Spring框架需要处理来自各种环境的配置参数信息,比如System Property(系统属性)、Properties Property(配置文件属性)、 CommandLine Property(命令行属性)、Servlet Property(Servlet属性)等。这个带泛型参数接口的作用就是为不同环境参数定义的,其中name属性就是环境的key,source就是对应环境的具体参数信息对象。注意这个抽象类实现了equals和hashCode方法,关键属性是name,当保存到集合中时,就会以name作为主元素进行匹配和查找,这点后面就可以看到,这可能就是选择定义为抽象类而不是接口的原因之一吧。

这里不需要特别关注,因为创建这个实例用于解析简单的命令行参数,但此时传来的args是[].

prepareEnvironment准备Spring应用环境

run方法接下来要执行:

代码语言:java复制
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

传入3个参数:

  • listeners
  • bootstrapContext
  • applicationArguments
image-20221116141506182image-20221116141506182
代码语言:java复制
	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
		// Create and configure the environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		ConfigurationPropertySources.attach(environment);
		listeners.environmentPrepared(bootstrapContext, environment);
		DefaultPropertiesPropertySource.moveToEnd(environment);
		Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
				"Environment prefix cannot be set via properties.");
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
			environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

第一步:根据前面的webApplicationType决定实例化哪个环境实现类

ConfigurableEnvironment environment = getOrCreateEnvironment();

创建或者获取应用环境

代码语言:java复制
private ConfigurableEnvironment getOrCreateEnvironment() {
   if (this.environment != null) {
      return this.environment;
   }
   switch (this.webApplicationType) {
      case SERVLET:
         return new ApplicationServletEnvironment();
      case REACTIVE:
         return new ApplicationReactiveWebEnvironment();
      default:
         return new ApplicationEnvironment();
   }
}

这里environment传进来是null,所以要根据web的类型创建对应的environment:

显然走的case是SERVLET,会返回一个新创建的:ApplicationServletEnvironment();

image-20221116141906321image-20221116141906321

创建的细节不深究,得到的实例如下:

image-20221116142033123image-20221116142033123

第2步:注册默认的转换器、格式化组件

代码语言:txt复制
configureEnvironment(environment, applicationArguments.getSourceArgs());

传入的applicationArguments.getSourceArgs()其实是:

<img src="https://wechat01.oss-cn-hangzhou.aliyuncs.com/img/image-20221116142246165.png" alt="image-20221116142246165" style="zoom:50%;" />

来看具体的方法:(这是一个模版方法)

configureEnvironment
代码语言:java复制
	protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
		if (this.addConversionService) {
			environment.setConversionService(new ApplicationConversionService());
		}
		configurePropertySources(environment, args);
		configureProfiles(environment, args);
	}

首先this.addConversionService默认是true:

image-20221116142404094image-20221116142404094

所以将执行:

代码语言:txt复制
environment.setConversionService(new ApplicationConversionService());

先创建ApplicationConversionService对象:

代码语言:txt复制
public class ApplicationConversionService extends FormattingConversionService
public class FormattingConversionService extends GenericConversionService
		implements FormatterRegistry, EmbeddedValueResolverAware
public class GenericConversionService implements ConfigurableConversionService
public interface ConfigurableConversionService extends ConversionService, ConverterRegistry
public interface ConversionService

ConversionService是一个顶级接口,是类型转换器

实例化过程:

代码语言:java复制
public ApplicationConversionService() {
		this(null);
}
代码语言:java复制
	public ApplicationConversionService(StringValueResolver embeddedValueResolver) {
		this(embeddedValueResolver, false);
	}

这里的embeddedValueResolver是null

代码语言:java复制
	private ApplicationConversionService(StringValueResolver embeddedValueResolver, boolean unmodifiable) {
    // embeddedValueResolver是null,if不走
		if (embeddedValueResolver != null) {
			setEmbeddedValueResolver(embeddedValueResolver);
		}
		configure(this);
		this.unmodifiable = unmodifiable;
	}

configure(this); 注册装换器、格式化组件,源码如下

public static void configure(FormatterRegistry registry) { // 注册默认转换器组件 DefaultConversionService.addDefaultConverters(registry); // 注册默认格式化器组件 DefaultFormattingConversionService.addDefaultFormatters(registry); // 注册应用程序默认格式化器组件 addApplicationFormatters(registry); // 注册应用程序默认转换器组件 addApplicationConverters(registry); }

image-20221116144418347image-20221116144418347

然后赋值 :

代码语言:txt复制
this.unmodifiable = unmodifiable;
image-20221116144510247image-20221116144510247

这个false是前面方法中传进来的。

ApplicationConversionService实例化完毕:

image-20221116144617176image-20221116144617176

再次回到:

代码语言:java复制
	protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
		if (this.addConversionService) {
			environment.setConversionService(new ApplicationConversionService());
		}
		configurePropertySources(environment, args);
		configureProfiles(environment, args);
	}

然后执行environment.setConversionService

代码语言:java复制
	public void setConversionService(ConfigurableConversionService conversionService) {
		this.propertyResolver.setConversionService(conversionService);
	}
代码语言:java复制
	public void setConversionService(ConfigurableConversionService conversionService) {
		// 非空的判断
    Assert.notNull(conversionService, "ConversionService must not be null");
		this.conversionService = conversionService;
	}

然后执行:configurePropertySources(environment, args);(装配main方法参数)

image-20221116144846236image-20221116144846236
代码语言:java复制
	protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
    // environment.getPropertySources();此时会直接返回,作为PropertySource接口的容器
		MutablePropertySources sources = environment.getPropertySources();
    // this.defaultProperties = null
    // 也就是!CollectionUtils.isEmpty(this.defaultProperties = false
    // 这个if不走
		if (!CollectionUtils.isEmpty(this.defaultProperties)) {
			DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);
		}
    // this.addCommandLineProperties  = true
    // (args.length > 0) = false,此时args = []
    // (this.addCommandLineProperties && args.length > 0) = false,这个if分支也不走
		if (this.addCommandLineProperties && args.length > 0) {
			String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
			if (sources.contains(name)) {
				PropertySource<?> source = sources.get(name);
				CompositePropertySource composite = new CompositePropertySource(name);
				composite.addPropertySource(
						new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
				composite.addPropertySource(source);
				sources.replace(name, composite);
			}
			else {
				sources.addFirst(new SimpleCommandLinePropertySource(args));
			}
		}
	}

MutablePropertySources sources = environment.getPropertySources();

image-20221116145324623image-20221116145324623

简单来说上面的这个configurePropertySources方法的作用是:

  • 如果设置默认属性,则添加进去,这里没有设置(第一个if分支)
  • (第二个分支)如果有命令行参数,这里将命令行参数封装后添加到key为springApplicationCommandLineArgs的MutablePropertySources属性List<PropertySource<?>> propertySourceList

因为此时,没什么默认参数,也没有命令行参数,就不需要做什么。

然后回到configureEnvironment,走configureProfiles(environment, args);(装配Profiles配置)

代码语言:java复制
	protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
	}

发现只是平庸的实现。这样configureEnvironment方法就走完了。

回到prepareEnvironment方法,接下来ConfigurationPropertySources.attach(environment);

image-20221116150506601image-20221116150506601
ConfigurationPropertySources.attach(environment);

这个方法用于:附加指定的环境配置

来看源码:

代码语言:java复制
private static final String ATTACHED_PROPERTY_SOURCE_NAME = "configurationProperties";
public static void attach(Environment environment) {
  // environment是ApplicationServletEnvironment的实例
  // class ApplicationServletEnvironment extends StandardServletEnvironment
  // public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment
  // public interface ConfigurableWebEnvironment extends ConfigurableEnvironment
  // 所以isInstanceOf为true
   Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
  // 获取servlet和系统的一些参数,这里是4个参数,2个servlet的参数,2个系统参数(下面截图了)
   MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
  // attached源码看下面,逻辑解释在下面了,这里attached = null,因为没有name是configurationProperties的source
   PropertySource<?> attached = getAttached(sources);
  // isUsingSources源码在下面,isUsingSources(attached, sources) = false,所以if分支要进
   if (attached == null || !isUsingSources(attached, sources)) {
     // 将sources封装成ConfigurationPropertySourcesPropertySource对象,name为configurationProperties,创建完成的对象截图在下面
      attached = new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
            new SpringConfigurationPropertySources(sources));
   }
  // 此时sources还是4个元素,就是上面说的servlet2个,系统的sources2个
  // 所以实际上不需要remove,执行完remove方法sources还是4个元素
   sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
  // 并把ConfigurationPropertySourcesPropertySource对象放到sources的第一位置
  // 执行完addFirst,此时就有5个sources了,具体哪五个下面记录了
   sources.addFirst(attached);
}

这个attach方法:

ConfigurationPropertySource支持附加到指定的环境。将环境管理的每个PropertySource适配到ConfigurationPropertySource,并允许使用配置属性名来解析经典PropertySourcesPropertyResolver调用。 附加的解析器将动态跟踪从底层环境属性源中添加或删除的任何内容。

image-20221116151821699image-20221116151821699

这里看一下((ConfigurableEnvironment) environment).getPropertySources()到底是哪4个:

image-20221116152329517image-20221116152329517

所以关于attach方法:获取name为configurationProperties的sources

代码语言:java复制
	static PropertySource<?> getAttached(MutablePropertySources sources) {
		return (sources != null) ? sources.get(ATTACHED_PROPERTY_SOURCE_NAME) : null;
	}
其中:
  private static final String ATTACHED_PROPERTY_SOURCE_NAME = "configurationProperties";

传入的4个source的信息如下:

image-20221116152748737image-20221116152748737

显然没有name是configurationProperties的source,所以getAttached方法返回null


代码语言:java复制
	private static boolean isUsingSources(PropertySource<?> attached, MutablePropertySources sources) {
    // attached是null ,直接返回false了
		return attached instanceof ConfigurationPropertySourcesPropertySource
				&& ((SpringConfigurationPropertySources) attached.getSource()).isUsingSources(sources);
	}

代码语言:java复制
		if (attached == null || !isUsingSources(attached, sources)) {
			attached = new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
					new SpringConfigurationPropertySources(sources));
		}

创建的过程如下:

首先创建SpringConfigurationPropertySources,对sourcess属性进行赋值。

代码语言:java复制
	SpringConfigurationPropertySources(Iterable<PropertySource<?>> sources) {
		Assert.notNull(sources, "Sources must not be null");
		this.sources = sources;
	}

然后创建ConfigurationPropertySourcesPropertySource

代码语言:java复制
	ConfigurationPropertySourcesPropertySource(String name, Iterable<ConfigurationPropertySource> source) {
		super(name, source);
	}

调用父类的的构造方法:

代码语言:java复制
	public PropertySource(String name, T source) {
		Assert.hasText(name, "Property source name must contain at least one character");
		Assert.notNull(source, "Property source must not be null");
		this.name = name;
		this.source = source;
	}

创建得到的ConfigurationPropertySourcesPropertySource对象:

image-20221116153555858image-20221116153555858

代码语言:txt复制
sources.addFirst(attached);

这一步执行完之后,sources就有5个值了,

如下所示:

代码语言:txt复制
[ConfigurationPropertySourcesPropertySource {name='configurationProperties'}, StubPropertySource {name='servletConfigInitParams'}, 
StubPropertySource {name='servletContextInitParams'}, 
PropertiesPropertySource {name='systemProperties'}, 
SystemEnvironmentPropertySource {name='systemEnvironment'}]

第一个就是name是ATTACHED_PROPERTY_SOURCE_NAME=configurationProperties的。

也就是相比之前环境配置新增了:ConfigurationPropertySourcesPropertySource {name='configurationProperties'}并且放在了第一个。

listeners.environmentPrepared(bootstrapContext, environment);

这个方法的功能:listeners 环境准备,广播 ApplicationEnvironmentPreparedEvent(通知所有监听器,环境变量已准备就绪;)

这里的listeners类型是SpringApplicationRunListeners

代码语言:java复制
	void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
		doWithListeners("spring.boot.application.environment-prepared",
				(listener) -> listener.environmentPrepared(bootstrapContext, environment));
	}
代码语言:java复制
	private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction) {
		doWithListeners(stepName, listenerAction, null);
	}
代码语言:java复制
	private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
			Consumer<StartupStep> stepAction) {
		StartupStep step = this.applicationStartup.start(stepName);
		this.listeners.forEach(listenerAction);
		if (stepAction != null) {
			stepAction.accept(step);
		}
		step.end();
	}

这里面的stepName是“spring.boot.application.environment-prepared”

DefaultPropertiesPropertySource.moveToEnd(environment);环境配置绑定
代码语言:java复制
	public static void moveToEnd(ConfigurableEnvironment environment) {
		moveToEnd(environment.getPropertySources());
	}

这里environment.getPropertySources()有6个值:

代码语言:txt复制
[ConfigurationPropertySourcesPropertySource {name='configurationProperties'}, StubPropertySource {name='servletConfigInitParams'}, 
StubPropertySource {name='servletContextInitParams'}, 
PropertiesPropertySource {name='systemProperties'}, OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}, RandomValuePropertySource {name='random'}]
代码语言:java复制
public static final String NAME = "defaultProperties";	
public static void moveToEnd(MutablePropertySources propertySources) {
		PropertySource<?> propertySource = propertySources.remove(NAME);
		if (propertySource != null) {
			propertySources.addLast(propertySource);
		}
	}

首先会找name是defaultProperties,存在则加入到MutablePropertySources列表最后

public PropertySource<?> remove(String name) { synchronized (this.propertySourceList) { int index = this.propertySourceList.indexOf(PropertySource.named(name)); return (index != -1 ? this.propertySourceList.remove(index) : null); } }

这个remove方法并不会移除,就是将找到的返回。

执行完之后:

代码语言:txt复制
[ConfigurationPropertySourcesPropertySource {name='configurationProperties'}, StubPropertySource {name='servletConfigInitParams'}, 
StubPropertySource {name='servletContextInitParams'}, 
PropertiesPropertySource {name='systemProperties'}, OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}, RandomValuePropertySource {name='random'}]

其实没啥变化。

然后执行:

代码语言:java复制
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
				"Environment prefix cannot be set via properties.");
bindToSpringApplication(environment);

这个方法底层逻辑非常多,主要逻辑是处理给定的配置开头以及要绑定的对象进行不同规则解析后进行反射赋值操作。完全是单独的配置项解析组件,后续可以作为单独章节去详细说明。

这里会重新创建一个Binder的实例,通过Binder将环境变量与SpringApplication绑定。使用Binder是因为可以实现一些动态绑定转换的功能,后面会写一篇文章专门介绍这个Binder。

代码语言:java复制
	protected void bindToSpringApplication(ConfigurableEnvironment environment) {
		try {
			Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
		}
		catch (Exception ex) {
			throw new IllegalStateException("Cannot bind to SpringApplication", ex);
		}
	}

首先关注此时,SpringApplication.source什么都没有

image-20221116185144565image-20221116185144565

首先先来看Bindable.ofInstance(this):

代码语言:java复制
public static <T> Bindable<T> ofInstance(T instance) {
   Assert.notNull(instance, "Instance must not be null");
   Class<T> type = (Class<T>) instance.getClass();
   return of(type).withExistingValue(instance);
}

这里的instance就是SpringApplication,首先Assert.notNull(instance, "Instance must not be null");判断非空,然后Class<T> type = (Class<T>) instance.getClass();获取其字节码文件,

image-20221116185425726image-20221116185425726
代码语言:java复制
	public Bindable<T> withExistingValue(T existingValue) {
		Assert.isTrue(
				existingValue == null || this.type.isArray() || this.boxedType.resolve().isInstance(existingValue),
				() -> "ExistingValue must be an instance of "   this.type);
		Supplier<T> value = (existingValue != null) ? () -> existingValue : null;
		return new Bindable<>(this.type, this.boxedType, value, this.annotations, this.bindRestrictions);
	}

此时existingValue非null值,会将existingValue赋值给value,最终返回一个Bindable的对象,构造方法如下:

代码语言:java复制
	private Bindable(ResolvableType type, ResolvableType boxedType, Supplier<T> value, Annotation[] annotations,
			EnumSet<BindRestriction> bindRestrictions) {
		this.type = type;
		this.boxedType = boxedType;
		this.value = value;
		this.annotations = annotations;
		this.bindRestrictions = bindRestrictions;
	}
代码语言:java复制
	public <T> BindResult<T> bind(String name, Bindable<T> target) {
		return bind(ConfigurationPropertyName.of(name), target, null);
	}
代码语言:java复制
	public <T> BindResult<T> bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler) {
		T bound = bind(name, target, handler, false);
		return BindResult.of(bound);
	}
代码语言:java复制
	private <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, boolean create) {
		Assert.notNull(name, "Name must not be null");
		Assert.notNull(target, "Target must not be null");
		handler = (handler != null) ? handler : this.defaultBindHandler;
		Context context = new Context();
		return bind(name, target, handler, context, false, create);
	}

很奇怪的是,我的没有绑定上去,不过看到一些大佬读源码的时候也一样没有绑定上去,貌似也是不重要的逻辑,不深究了。

然后在prepareEnvironment方法中有一句if判断:

代码语言:java复制
		if (!this.isCustomEnvironment) {//是不是自定义环境
			EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
			environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
代码语言:txt复制
this.isCustomEnvironment = false  所以要进if
这个参数一开始设置了false
private boolean isCustomEnvironment = false;
但是set方法又设为了true(当然我们没有走过set方法)
	public void setEnvironment(ConfigurableEnvironment environment) {
		this.isCustomEnvironment = true;
		this.environment = environment;
	}

if中执行了2句:首先利用类加载器,创建一个环境转换器

代码语言:txt复制
EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());

这行作用:根据spring.main.web-application-type配置判断是否需要转换环境

首先会执行deduceEnvironmentClass()方法

代码语言:java复制
	private Class<? extends StandardEnvironment> deduceEnvironmentClass() {
		switch (this.webApplicationType) {
			case SERVLET:
				return ApplicationServletEnvironment.class;
			case REACTIVE:
				return ApplicationReactiveWebEnvironment.class;
			default:
				return ApplicationEnvironment.class;
		}
	}

此时,this.webApplicationType不用多说就是SERVLET.

所以会返回return ApplicationServletEnvironment.class;

然后执行:environmentConverter.convertEnvironmentIfNecessary

代码语言:java复制
	StandardEnvironment convertEnvironmentIfNecessary(ConfigurableEnvironment environment,
			Class<? extends StandardEnvironment> type) {
		if (type.equals(environment.getClass())) {
			return (StandardEnvironment) environment;
		}
		return convertEnvironment(environment, type);
	}

此处的type就是刚才得到的ApplicationServletEnvironment.class.

首先根据刚才获取的class:ApplicationServletEnvironment.class获取标准环境,然后设置转换服务convertEnvironment(environment, type)其实就是将环境对象转换成用户设置的WebApplicationType相关类型(这里是SERVLET类型)。

ConfigurationPropertySources.attach(environment);

重新添加ConfigurationPropertySource支持,配置 propertySource 对它自己的递归依赖

代码语言:java复制
private static final String ATTACHED_PROPERTY_SOURCE_NAME = "configurationProperties";
public static void attach(Environment environment) {
		Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
		MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
		PropertySource<?> attached = getAttached(sources);
		if (attached == null || !isUsingSources(attached, sources)) {
			attached = new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
					new SpringConfigurationPropertySources(sources));
		}
		sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
		sources.addFirst(attached);
	}

这个代码前面度过了,这里不重复写了。

添加 一套配置,配置文件到List开头,也就是默认添加了一个configurationProperties名字的PropertySource,或者说添加了一个Environment,因为Environment封装了PropertySource,这个配置从注释上看,可能是为了动态的添加和配置一些值用的,知道attach是补充了一个配置文件的对象封装就可以了,其他的暂时不是重点;

到这里prepareEnvironment方法就基本读完了。

总结

prepareEnvironment方法:

代码语言:java复制
	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
		// Create and configure the environment
    // 根据前面的webApplicationType决定实例化哪个环境实现类,此处这里的类型应该是SERVLET
		ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 注册装换器、格式化组件,注册完装配,还有装配main方法参数,装配Profiles配置
		configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 附加指定的环境配置
		ConfigurationPropertySources.attach(environment);
    // 发布环境准备事件
		listeners.environmentPrepared(bootstrapContext, environment);
    // 环境配置绑定
		DefaultPropertiesPropertySource.moveToEnd(environment);
		Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
				"Environment prefix cannot be set via properties.");
    // 将获取到的environment中的spring.main配置绑定到SpringApplication的source中
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
      // 创建一个环境转换器
			EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
      // 将环境对象转换成用户设置的WebApplicationType相关类型(这里是SERVLET类型)
			environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
    // 补充了一个配置文件的对象封装
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

0 人点赞