一、spring是如何启动的
springboot的启动代码非常简洁优雅,通常只需一个注解@SpringBootApplication
和一行代码就能将应用启动起来:SpringApplication.run(App.class, args);
主线程执行完后,由于有其他非daemon线程还存活着(例如tomcat的线程),所以整个应用在没有发生重启的情况下能实现7*24不间断运行。这一个run方法最后进入源码其实就是这一行代码new SpringApplication(primarySources).run(args);
,可以看到主要做了两件事:
- 构造SpringApplication对象并初始化成员变量
- 进入springboot启动过程中的生命周期
二、SpringApplication的初始化
构造方法中的初始化主要进行环境信息的推断,以及spring.factories的加载和相关类的实例化。
// org.springframework.boot.SpringApplication#SpringApplication
// 构造方法进行初始化
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// primarySources通常是Application.class
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 推断web环境,通常是servlet环境
this.webApplicationType = WebApplicationType.deduceFromClasspath();
/*
获取ApplicationContextInitializer实例对象,也是在这里开始首次加载spring.factories文件
如何加载的可以查看下面SPI与spring.factories小节
ApplicationContextInitializer和ApplicationListener来源于spring-boot-${ver}.jar中的factories
找到实现类后用无参构造方法实例化对象
*/
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 通过当前线程栈帧找到main方法所在的类
this.mainApplicationClass = deduceMainApplicationClass();
}
来通过下面几个小节详细看下相应的几个方法
1. 推断环境
WebApplicationType主要有三种环境NONE、SERVLET和REACTIVE,spring通过判断类路径下是否存在某些类来推断环境类型。
若存在org.springframework.web.reactive.DispatcherHandler且不存在org.springframework.web.servlet.DispatcherServlet和org.glassfish.jersey.servlet.ServletContainer则当前web环境是REACTIVE;
若类路径不存在javax.servlet.Servlet或者org.springframework.web.context.ConfigurableWebApplicationContext则当前web环境是NONE;
其他情况则是SERVLET环境
// org.springframework.boot.WebApplicationType#deduceFromClasspath
static WebApplicationType deduceFromClasspath() {
// 该方法比较关键,相当于Class.forName("xxx"),如果抛出ClassNotFoundException这类异常就说明不存在。
if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", 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;
}
还有一个推断方法是deduceMainApplicationClass,这个方法用于推断拥有main方法的入口类,推断的过程也非常有趣。它是通过getStackTrace找到当前线程的栈帧,在栈帧中找到main方法及其class对象。
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;
}
2. SPI与spring.factories
首先SPI(Service Provider Interface),即服务提供者接口,是一种机制用于发现接口的具体实现类。简单来说就是底层框架定义接口,上层应用实现接口,并通过某种规定的方式使底层框架获取到接口实现类。JDBC是Java中SPI非常典型的一个场景。
JDBC中定义了java.sql.Driver
接口,该接口的实现是由各个数据库厂商(例如MySQL、Oracle等)完成的。java.sql.DriverManager
在类加载的时候利用java.util.ServiceLoader<S>
来扫描jar包中实现java.sql.Driver接口的具体实现类,实现厂商需要在jar包的META-INF/services/目录下放置一个名为java.sql.Driver的文件(如上图所示),该文件中的每一行都是该接口的实现类类名,例如com.mysql.cj.jdbc.Driver。ServiceLoader读取到这个类名后,会存储起来,然后通过懒加载的方式加载这个类,即调用到ServiceLoader迭代器的next方法。具体源码可以参见java.sql.DriverManager#loadInitialDrivers
方法。
spring则借鉴了这一思想,在应用启动时它会扫描并加载类路径下所有的"META-INF/spring.factories"文件,该文件是一个properties格式的文件,key为接口名,value为接口的实现类(可以有多个,用逗号分隔,如上图所示)。通过这种方式可以在启动过程中实现动态插入新的功能需求,降低了耦合的同时也提供了很大的扩展性,因为上层调用者只需要增加spring.factories配置文件就行了,很多自定义starter也是根据这个特性实现的。
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
// 若命中缓存则直接返回
if (result != null) {
return result;
}
try {
// 关键方法,获取类路径下包含META-INF/spring.factories的所有url
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources("META-INF/spring.factories") :
ClassLoader.getSystemResources("META-INF/spring.factories"));
// 一个value是List类型的Map
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
/*
这个方法比较关键,做了两件事情:
1. new Properties(),该Properties是JDK中的类
2. 调用Properties的load()方法将spring.factories的键值对加载进来
*/
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
// 将properties的内容存入缓存
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
3. spring.factories类的实例化
在扫描完spring.factories中ApplicationContextInitializer和ApplicationListener的实现类后,会进入这些类的实例化阶段。源码如下,这个阶段的入参中,type为接口类,names为实现类的全类名,无参数。
// org.springframework.boot.SpringApplication#createSpringFactoriesInstances
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 {
// 获得实现类的class对象
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
// 断言是type的派生类
Assert.isAssignable(type, instanceClass);
// parameterTypes为空,因此获得无参构造方法
Constructor<?> constructor = instanceClass
.getDeclaredConstructor(parameterTypes);
// 相当于constructor.newInstance(args),通过反射的方法实例化对象
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
三、进入springboot的启动生命周期
下面这个run方法可以说是整个启动过程中最重要的一段代码了,所有生命周期的执行包括配置文件的读取和bean的装配等等都是在这里发散开来的。因此这边只做一个简单的介绍,后续在其他文章中再对细节进行探究。下面代码中有调用到getRunListeners方法,这个方法也是通过spring.factories找到SpringApplicationRunListener接口类(默认只有EventPublishingRunListener
),这个接口类中定义了七个生命周期钩子方法,并贯穿在run的执行过程中。从类名中也可以发现,这个过程是观察者模式的一个实现场景,被观察的对象是每个生命周期时间点产生的事件,观察者则是一个个ApplicationListener(可在spring.factories中注册)。
- starting:应用刚启动时时候,也就是SpringApplication初始化完后便会执行的方法。一般在此周期内spring会初始化日志系统、启动后台预加载等。对应的事件类是
ApplicationStartingEvent
- environmentPrepared:环境准备完的时候,一般在这个周期内,spring会加载外部配置、决定使用哪个profile的配置文件等。对应的事件类是
ApplicationEnvironmentPreparedEvent
- contextPrepared:上下文准备完毕。对应的事件类是
ApplicationContextInitializedEvent
- contextLoaded:上下文加载完毕。对应的事件类是
ApplicationPreparedEvent
- started:应用启动完毕。对应的事件类是
ApplicationStartedEvent
- running:应用正在执行。对应的事件类是
ApplicationReadyEvent
- failed:启动过程中出现异常的时候。对应的事件类是
ApplicationFailedEvent
// org.springframework.boot.SpringApplication#run(java.lang.String...)
public ConfigurableApplicationContext run(String... args) {
// 秒表计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
// 启动过程中的异常处理器
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 可以理解为开启非GUI模式
configureHeadlessProperty();
// 通过SPI方式获取SpringApplicationRunListener的实例
SpringApplicationRunListeners listeners = getRunListeners(args);
// 第一个生命周期回调
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 准备环境并执行第二个生命周期回调
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
// 打印springboot的横幅
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 准备上下文环境,并执行第三个、第四个生命周期回调
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// 应用启动完毕,执行第五个生命周期回调
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// 出现异常则调用异常报告器,以及failed的生命周期回调方法
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 应用已经运行,执行第六个生命周期回调
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}