环境
window 10
Intellij IDEA:2021.1
springboot:2.4.3
流程图
启动步骤
根据上面的流程图,启动步骤我们可以简化为:
- 从
jar包
中meta-inf/spring.factores
获取键值对属性,并创建初始化器和监听器。 - 获取监听器并广播启动-事件。
- 准备容器环境,并广播环境准备完成-事件。
- 根据环境类型创建应用上下文(
spring容器
)。 - spring容器的前置处理。
- 刷新容器。
- spring容器的后置处理。
- 广播应用上下文启动完成-事件
- 执行callRunners
- 广播应用上下文可运行-事件
demo
package com.example.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BootApplication {
public static void main(String[] args) {
SpringApplication.run(BootApplication.class, args);
}
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
我们开始分析源码:
// primarySource就是BootApplication的Class对象
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
接着执行new SpringApplication(primarySources).run(args)
,我们先看下其构造器:
// 默认进来时,resourceLoader为null。
// 猜测:resourceLoader是用来指定加载器的。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//项目启动类BootApplication.class设置为属性存储起来
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//从加载路径中推断当前项目的类型;
//设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)
//还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//设置引导类
this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
//设置初始化器(Initializer),最后会调用这些初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//记录main方法的类的class信息
this.mainApplicationClass = deduceMainApplicationClass();
}
所谓的
初始化器
就是org.springframework.context.ApplicationContextInitializer
的实现类,在Spring
上下文被刷新之前进行初始化的操作。
构造方法执行完成后,就开始执行run
方法了;
SpringApplication.run方法
启动方法
public ConfigurableApplicationContext run(String... args) {
// 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
StopWatch stopWatch = new StopWatch();
stopWatch.start();
//bootstrapContext上下文的作用是啥?
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
//初始化应用上下文和异常报告集合
ConfigurableApplicationContext context = null;
//设置无头模式:创建图像,不需要显示器和键盘
configureHeadlessProperty();
//获取并实例化SpringApplicationRunListener的实现类
SpringApplicationRunListeners listeners = getRunListeners(args);
//(1)-启动监听器
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
//创建 ApplicationArguments 对象 初始化默认应用参数类
// args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。如:--server.port=9000
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//(2)-项目运行环境的预配置
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
//配置要忽略的bean信息
configureIgnoreBeanInfo(environment);
//准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
Banner printedBanner = printBanner(environment);
//(3)-创建Spring容器
context = createApplicationContext();
//ApplicationStartup在应用程序启动期间标记步骤,并收集有关执行上下文或其处理时间的数据
context.setApplicationStartup(this.applicationStartup);
//(4)-Spring容器前置处理
//这一步主要是在容器刷新之前的准备动作。
//包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//(5)-刷新容器
refreshContext(context);
//(6)-Spring容器后置处理
//扩展接口,设计模式中的模板方法,默认为空实现。
//如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理
afterRefresh(context, applicationArguments);
//停止 StopWatch 统计时长
stopWatch.stop();
//打印 Spring Boot 启动的时长日志。
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//(7)-发出结束执行的事件通知
listeners.started(context);
// (8):执行Runners
//用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序
//Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。
//Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
//(9)发布应用上下文就绪事件
//表示在前面一切初始化启动都没有问题的情况下,
//使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext,
//这样整个Spring Boot项目就正式启动完成了。
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
//返回容器
return context;
}
注意:
PS2:
遍历调用所有的SpringApplicationRunListener
的environmentPrepared()
方法。
loadSpringFactories方法
启动时缓存的类:
Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();
其中Map<String, List<String>>
,key
是注解类名
或接口
,value
就是具体的实现类。
初始化缓存cache
的方法就是loadSpringFactories()
方法。其会读取META-INF/spring.factories
文件中官方事先配置的类。
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
总结
仅仅只是大致的聊了下,启动的大体流程,细节问题都没有讲,下篇讲。
有空做下下面几件事情:
- 自定义的
ApplicationListener
- 自定义的
BeanFactoryPostProcessor
- 自定义的
BeanPostProcessor
参考地址:
Spring Boot 2.2.6 源码之旅一SpringApplication启动流程一
SpringBoot 源码解析 (二)----- Spring Boot精髓:启动流程源码分析