SpringBoot运行源代码分析
我们知道SpringBoot是基于“约定优于配置”,也知道可以根据starters自动加载和配置相应的服务,那么SpringBoot底层是怎么实现这些操作呢?这篇文章带大家通过源码分析,了解SpringBoot运行原理。
SpringApplication的拆解
通常创建SpringBoot项目之后,默认的启动代码只有一行,通过默认的配置基本上可以完成大多数的功能,但如果需要对启动流程的扩展,就需要对SpringBoot的启动方法进行拆解。其实这个操作在《SpringBoot基础之banner玩法解析》这篇文章中已经实践过。通过扩展来设置banner相关操作。我们还可以通过其他set操作来设置其他扩展。
@SpringBootApplication
public class SpringLearnApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(SpringLearnApplication.class);
app.setXXX(...);// 自定义扩展
app.run(args);
}
}
SpringBoot实例化
先来看看SpringBoot实例化时都做了些什么:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 1.判断应用的类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 2.加载扫描到的Initializer
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 3.设置**ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 4.推断并设置main方法的定义类
this.mainApplicationClass = deduceMainApplicationClass();
}
通过代码中的注释,可以了解到SpringApplication初始化可分为四部:
- 判断应用的类型;
- 加载扫描到的Initializer;
- 加载扫描到的Listener;
- 推断并设置main方法的定义类。
下面针对四个步骤再看一下相应的源代码实现。
判断应用的类型
此功能在枚举类WebApplicationType中实现,根据INDICATOR_CLASSES的类型来判断当前web应用是什么类型的,分别有:REACTIVE(reactive web应用)、SERVLET(基于servlet的web应用)、NONE(不启动web应用)。
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;
}
加载扫描到的Initializer
使用SpringFactoriesLoader扫描加载classpath下的META-INF/spring.factories文件中所有可用的Initializer。
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
其中spring.factories默认在spring-boot-autoconfigure包下,文件的内容格式为:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
# 省略其他的
加载扫描到的ApplicationListener
同样是使用SpringFactoriesLoader扫描加载classpath下的META-INF/spring.factories文件中所有可用的Listener。对应文件类的Listener为:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
推断并设置main方法的定义类
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;
}
run方法的基本操作
当实例化对象之后,会调用run方法。下面通过源代码来了解一下run方法都做了些什么。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 创建SpringApplicationRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
// 遍历并调用SpringApplicationRunListener的starting方法
listeners.starting();
try {
// 创建参数配置
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 环境参数准备
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
// 打印banner
Banner printedBanner = printBanner(environment);
// 创建ApplicationContext
context = createApplicationContext();
// 获得SpringFactories实例
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 设置Environment,加载相关配置
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 刷新ApplicationContext
refreshContext(context);
// 刷新context之后调用此方法
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// 刷新context,启动application
listeners.started(context);
// 启动程序
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 发出running消息,告知程序已启动
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
如果查看当前自动配置
如果想查看当前启动了哪些自动配置有以下几种方式。
(1)运行jar时增加–debug参数。
java -jar xx.jar --debug
(2)在application.properties进行配置。
debug=true
(3)在IDE运行程序中添加VM参数。
-Ddebug
控制台会输出对应日志。已启动的自动配置日志如下:
Positive matches:
-----------------
CodecsAutoConfiguration matched:
- @ConditionalOnClass found required class 'org.springframework.http.codec.CodecConfigurer' (OnClassCondition)
CodecsAutoConfiguration.JacksonCodecConfiguration matched:
- @ConditionalOnClass found required class 'com.fasterxml.jackson.databind.ObjectMapper' (OnClassCondition)
未启动的自动配置日志为:
Negative matches:
-----------------
ActiveMQAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)
AopAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'org.aspectj.lang.annotation.Aspect' (OnClassCondition)
小结
本篇文章我们简单介绍了springboot启动时都做了些什么,后面我们会逐步讲到整个的运作原理。
关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台
除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接