SpringBoot源代码分析之启动原理分析


SpringBoot源代码分析之启动原理分析

引言

这是一系列关于SpringBoot源代码的相关分析文章,主要参考《SpringBoot编程思想-核心篇》的章节,在参考SpringBoot源代码分析而来,希望对你有所帮助

准备工作

参考项目为地址为:https://github.com/agmtopy/SpringBootExample

启动类概览

在项目中,启动类为SpringDemoApplication,这个类应该是一切的开始,下面我们就来根据这个run方法来进行分析

  • SpringDemoApplication
@SpringBootApplication
public class SpringDemoApplication {

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

}
  • 执行链路

可以看到执行链路为:

这里有一个小细节,在没有提前初始化SpringApplication()时是不能使用arthas进行插入代码,显示错误为图一

图1

查看相关类信息也没有看到类加载

图2

经过分析可以知道这时候SpringApplication类还没有加载

构造方法

  • SpringApplication()
   
   //1. 启动方法  
   public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	return new SpringApplication(primarySources).run(args);
}

   //2. 构造方法
   public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
       //1. 初始化ResourceLoader
	this.resourceLoader = resourceLoader;
	Assert.notNull(primarySources, "PrimarySources must not be null");
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	this.bootstrapRegistryInitializers = new ArrayList<>(
			getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	this.mainApplicationClass = deduceMainApplicationClass();
}

SpringApplication初始化的方法中可以看到几个关键的方法3/4/5,分别是获取到Spring工厂的实例后在将工厂实例进行传递;

下面就详细的探究一下这几个方法的作用

  • getSpringFactoriesInstances()

通过Springboot的源代码可以看到getSpringFactoriesInstances()最后调用到了Spring的loadFactoryNames()方法

  • loadFactoryNames

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

主要是获取this.factories这个Map对象中的值,this.factories是SpringFactoriesLoader在进行初始化时产生的,回到SpringBoot中可以看到是通过forDefaultResourceLocation()方法

  • forDefaultResourceLocation()

// FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
public static SpringFactoriesLoader forDefaultResourceLocation(@Nullable ClassLoader classLoader) {
	return forResourceLocation(FACTORIES_RESOURCE_LOCATION, classLoader);
}

//实际调用这个方法
public static SpringFactoriesLoader forResourceLocation(String resourceLocation, @Nullable ClassLoader classLoader) {
	Assert.hasText(resourceLocation, "'resourceLocation' must not be empty");
	ClassLoader resourceClassLoader = (classLoader != null ? classLoader :
			SpringFactoriesLoader.class.getClassLoader());
	Map<String, SpringFactoriesLoader> loaders = cache.computeIfAbsent(
			resourceClassLoader, key -> new ConcurrentReferenceHashMap<>());
	return loaders.computeIfAbsent(resourceLocation, key ->
	// 首次初始化时使用的是loadFactoriesResource()方法来生成FactoryName
			new SpringFactoriesLoader(classLoader, loadFactoriesResource(resourceClassLoader, resourceLocation)));
}

// 生成FactoryName
protected static Map<String, List<String>> loadFactoriesResource(ClassLoader classLoader, String resourceLocation) {
	Map<String, List<String>> result = new LinkedHashMap<>();
	try {
		Enumeration<URL> urls = classLoader.getResources(resourceLocation);
		while (urls.hasMoreElements()) {
			UrlResource resource = new UrlResource(urls.nextElement());
			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
			properties.forEach((name, value) -> {
				List<String> implementations = result.computeIfAbsent(((String) name).trim(), key -> new ArrayList<>());
				Arrays.stream(StringUtils.commaDelimitedListToStringArray((String) value))
						.map(String::trim).forEach(implementations::add);
			});
		}
		result.replaceAll(SpringFactoriesLoader::toDistinctUnmodifiableList);
	}
	catch (IOException ex) {
		throw new IllegalArgumentException("Unable to load factories from location [" + resourceLocation + "]", ex);
	}
	return Collections.unmodifiableMap(result);
}
// 这一段方法的作用就是将传入的"META-INF/spring.factories"中的配置解析成为key-value形式的结果并返回

run()方法最后是执行SpringApplication的初始化方法:

SpringApplication.run()

  • SpringApplication.run()
/**
 * 运行Spring应用程序,创建并刷新新的容器
 */
public ConfigurableApplicationContext run(String... args) {
	//1. 执行需要在初始化开始时执行的钩子方法 -- 已经是过时的方法了在V3.0.0中间hook的实现挪到了SpringApplicationRunListener中,也就是在步骤5中进行执行
	SpringApplicationHooks.hooks().preRun(this);
	//2. 获取初始化时间
	long startTime = System.nanoTime();
	//3.初始化BootStrapContext(引导上下文)
	DefaultBootstrapContext bootstrapContext = createBootstrapContext();
	ConfigurableApplicationContext context = null;
	//4. 配置head头属性
	configureHeadlessProperty();
	//5. 获取运行中的执行监听器
	SpringApplicationRunListeners listeners = getRunListeners(args);
	//6. 启动监听器
	listeners.starting(bootstrapContext, this.mainApplicationClass);
	try {
		//7. 初始化应用程序参数
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		//8. 准备运行环境
		ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
		//9. 设置需要忽略的bean
		configureIgnoreBeanInfo(environment);
		//10. 打印Banner信息
		Banner printedBanner = printBanner(environment);
		//11. 创建ApplicationContext
		context = createApplicationContext();
		//12. 设置应用程序启动器(用于收集启动过程中的一些信息)
		context.setApplicationStartup(this.applicationStartup);
		//13. 准备启动ApplicationContext
		prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
		//14. 判断是否需要重新刷新容器
		if (refreshContext(context)) {
			//15. 后置刷新容器方法
			afterRefresh(context, applicationArguments);
			//16. 记录时间
			Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(),
						timeTakenToStartup);
			}
			//17. 设置监听时间
			listeners.started(context, timeTakenToStartup);
			//18. 执行Runner方法
			callRunners(context, applicationArguments);
		}
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, listeners);
		throw new IllegalStateException(ex);
	}
	try {
		if (context.isRunning()) {
			Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
			listeners.ready(context, timeTakenToReady);
		}
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, null);
		throw new IllegalStateException(ex);
	}
	//19. 执行后置Hook后置方法
	SpringApplicationHooks.hooks().postRun(this, context);
	return context;
}

可以看到run方法的执行过程分为几个阶段:

  • 前置准备阶段

    • 执行前置钩子方法(步骤一)
    • 初始化引导上下文(步骤二\步骤三\步骤四)
    • 设置启动监听器 (步骤五\步骤六)
  • 启动容器阶段

    • 初始化应用参数(步骤七\八\九\十)
    • 创建容器(步骤十一\十二\十三)
  • 刷新容器阶段

    • 刷新容器(步骤十四)
    • 创建容器(步骤十五)
  • 执行后置方法阶段

    • 执行Runner(步骤十八)
    • 执行hook(步骤十九)

前置准备阶段

前置准备阶段主要是设置一些容器上下文和钩子函数,下面详细分析一下这几个功能的实现

执行前置钩子方法

//设置钩子方法
SpringApplicationHooks.hooks().preRun(this);

这个方法是在针对于AOT的启动过程中提供的钩子方法,最早应用于v3.0.0-M4版本中,commit,但是在2022.09调整过一次代码结构,将hook重写为基于SpringApplicationRunListener来进行实现了

下面来继续分析SpringApplicationHooks的执行流程

  • org.springframework.boot.SpringApplication#getRunListeners

//1. 注册到Listeners中
private SpringApplicationRunListeners getRunListeners(String[] args) {
		ArgumentResolver argumentResolver = ArgumentResolver.of(SpringApplication.class, this);
		argumentResolver = argumentResolver.and(String[].class, args);
		List<SpringApplicationRunListener> listeners = getSpringFactoriesInstances(SpringApplicationRunListener.class,
				argumentResolver);
		//获取applicationHook对象
		SpringApplicationHook hook = applicationHook.get();
		SpringApplicationRunListener hookListener = (hook != null) ? hook.getRunListener(this) : null;
		if (hookListener != null) {
			listeners = new ArrayList<>(listeners);
			//将applicationHookListener添加到
			listeners.add(hookListener);
		}
		return new SpringApplicationRunListeners(logger, listeners, this.applicationStartup);
}

//2. 将Hook设置到applicationHook中
public static <T> T withHook(SpringApplicationHook hook, ThrowingSupplier<T> action) {
	applicationHook.set(hook);
	try {
		return action.get();
	}
	finally {
		applicationHook.set(null);
	}
}

//3. applicationHook
private static final ThreadLocal<SpringApplicationHook> applicationHook = new ThreadLocal<>();

可以看到applicationHook是放在一个ThreadLocal中的变量,由创建application对象输入;

SpringApplicationHook只有一个方法,就是设置Listener

public interface SpringApplicationHook {
	SpringApplicationRunListener getRunListener(SpringApplication springApplication);
}

设置主类

listeners.starting(bootstrapContext, this.mainApplicationClass);

准备运行环境

  • prepareEnvironment
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
		DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
	//1.初始化创建Environment对象
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	//2. 配置Environment对象
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	//3. 先检查配置属性源
	ConfigurationPropertySources.attach(environment);
	//4.执行environmentPrepared方法(扩展点1)
	listeners.environmentPrepared(bootstrapContext, environment);
	//5. 移除掉默认属性
	DefaultPropertiesPropertySource.moveToEnd(environment);
	Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
			"Environment prefix cannot be set via properties.");
	//6. 绑定"spring.main"为this
	bindToSpringApplication(environment);
	if (!this.isCustomEnvironment) {
		//7. 转换Environment
		EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
		environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
	}
	ConfigurationPropertySources.attach(environment);
	return environment;
}

prepareEnvironment的大概方法就是执行

  1. 初始化配置Environment对象
  2. 执行的SpringApplicationRunListeners.environmentPrepared方法(扩展点1)
  3. 绑定相关属性

这里面最重要的当属listeners.environmentPrepared

  • org.springframework.boot.SpringApplicationRunListeners#environmentPrepared
void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
	doWithListeners("spring.boot.application.environment-prepared",
			(listener) -> listener.environmentPrepared(bootstrapContext, environment));
}

可以看到会回调注册为配置文件中配置的类的相关方法

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener,\
org.springframework.boot.context.event.SpringApplicationRunListenerTest

SpringApplicationRunListeners里面执行方法也比较有意思,就是直接执行方法listener的具体方法

初始化ApplicationContext

  • createApplicationContext()
protected ConfigurableApplicationContext createApplicationContext() {
	return this.applicationContextFactory.create(this.webApplicationType);
}


//ServletWebServerApplicationContextFactory.java
@Override
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
	return (webApplicationType != WebApplicationType.SERVLET) ? null : createContext();
}

private ConfigurableApplicationContext createContext() {
	//判断是否需要激活AOT
	if (!AotDetector.useGeneratedArtifacts()) {
		return new AnnotationConfigServletWebServerApplicationContext();
	}
	//返回ServletContext
	return new ServletWebServerApplicationContext();
}

初始化ApplicationContex方法就是根据应用类型,方法对应的ApplicationContex

预处理容器

  • prepareContext()
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
	if (this.beanNameGenerator != null) {
		context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
				this.beanNameGenerator);
	}
	if (this.resourceLoader != null) {
		if (context instanceof GenericApplicationContext genericApplicationContext) {
			//设置ResourceLoader
			genericApplicationContext.setResourceLoader(this.resourceLoader);
		}
		if (context instanceof DefaultResourceLoader defaultResourceLoader) {
			//设置ClassLoader
			defaultResourceLoader.setClassLoader(this.resourceLoader.getClassLoader());
		}
	}
	if (this.addConversionService) {
		context.getBeanFactory().setConversionService(context.getEnvironment().getConversionService());
	}
}

prepareContext用来设置setResourceLoader>、setClassLoader
这里有一个没搞懂的

context instanceof DefaultResourceLoader defaultResourceLoader
context会是DefaultResourceLoader的实现类嘛?这里没有搞懂

  • applyInitializers()

执行初始化方法,这是第二个扩展点

protected void applyInitializers(ConfigurableApplicationContext context) {
	//遍历全部初始化容器<ApplicationContextInitializer>
	for (ApplicationContextInitializer initializer : getInitializers()) {
		Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
				ApplicationContextInitializer.class);
		Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
		//执行初始化方法
		initializer.initialize(context);
	}
}

//setInitializers
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
	this.initializers = new ArrayList<>(initializers);
}

//在SpringApplication()的构造方法中完成设置
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

我们看一下ContextIdApplicationContextInitializer是做什么的?

  • ContextIdApplicationContextInitializer
private String getApplicationId(ConfigurableEnvironment environment) {
	String name = environment.getProperty("spring.application.name");
	return StringUtils.hasText(name) ? name : "application";
}

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
	ContextId contextId = getContextId(applicationContext);
	applicationContext.setId(contextId.getId());
	applicationContext.getBeanFactory().registerSingleton(ContextId.class.getName(), contextId);
}

可以看到这个类主要是想context中设置contextId的动作,也可以把它看做是设置context的一种扩展点

  • 设置BeanFactory
//获取Bean Factory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
	beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof AbstractAutowireCapableBeanFactory autowireCapableBeanFactory) {
	//设置BeanFactory是否允许循环依赖
	autowireCapableBeanFactory.setAllowCircularReferences(this.allowCircularReferences);
	if (beanFactory instanceof DefaultListableBeanFactory listableBeanFactory) {
		//设置是否允许BeanDefinition重写
		listableBeanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
	}
}

对BeanFactory设置关键属性是否可以重载BeanDefinition是否允许循环依赖

  • addBeanFactoryPostProcessor

向容器中添加BeanFactoryPostProcessor,

//是否延迟加载
if (this.lazyInitialization) {
	//设置BeanFactoryPostProcessor
	context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
//add PropertySourceOrderingBeanFactoryPostProcessor
context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
//最后还有listeners.contextLoaded
listeners.contextLoaded(context);

刷新容器

在SpringBoot中刷新容器反而比较简单,只需要调用Spring的refresh()方法

protected void refresh(ConfigurableApplicationContext applicationContext) {
	applicationContext.refresh();
}


protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}

//listeners.started
listeners.started(context, timeTakenToStartup);

在SpringContext refresh完成后还有一个afterRefresh方法,但是这个方法是空的,不会进行处理;

然后在往下就是一个listeners.started的调用

小结

1.创建计时器并开始
​2.创建启动器上下文并执行监听器BootstrapRegistryInitializer的initialize方法
​3.创建上下文ConfigurableApplicationContext变量并设置环境一些属性
​4.获取SpringApplicationRunListener监听器并执行其starting方法
​5.将命令行参数封装到ApplicationArguments里面来
​6.初始化应用上下文环境
​7.处理忽略Bean的信息
​8.打印Banner信息
​9.根据当前应用类型来创建context上下文
​10.设置记录器
​11.为刷新上下前做准备
​12.刷新上下文,最重要的功能在这的
​13.刷新后的扩展方法,其实里面什么都没有
​14.计时器结束
​15.记录log
​16.SpringApplicationRunListener监听器回调started
​17.SpringApplicationRunListener监听器回调running

注意这个过程中的扩展点:

  1. listeners
  2. ApplicationContextInitializer

和对BeanFactory属性的设置;


  TOC