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进行插入代码,显示错误为图一
查看相关类信息也没有看到类加载
经过分析可以知道这时候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的大概方法就是执行
- 初始化配置Environment对象
- 执行的SpringApplicationRunListeners.environmentPrepared方法(扩展点1)
- 绑定相关属性
这里面最重要的当属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
注意这个过程中的扩展点:
- listeners
- ApplicationContextInitializer
和对BeanFactory属性的设置;