前言
SpringBoot 的启动区别于传统的Spring需要搭建tomcat等相关的容器,SpringBoot默认是通过内置Tomcat启动,只需简单的运行java -jar xxx即可简单的启动一个SpringBoot工程。这里我们深入SpringBoot了解它是如何做到通过简简单单的一个jar包就把整个项目启动起来
SpringBoot jar加载流程
SrpingBoot 运行的时候是直接运行的一个jar文件
我们先看下jar文件解压后的目录结构

首先先简单介绍下这个目录结构
spring-boot-learn-0.0.1-SNAPSHOT
├── META-INF
│ └── MANIFEST.MF
├── BOOT-INF
│ ├── classes
│ │ └── 应用程序类
│ └── lib
│ └── 第三方依赖jar
└── org
└── springframework
└── boot
└── loader
└── springboot启动所需的class
在SpringBoot中会将所有所需的jar包都打包在BOOT-INF下的lib下面,而对于java中是无法加载jar中的jar,所以SpringBoot实现了自定义类加载器,这个类加载器通过org.springframework.boot.loader.JarLauncher创建了LaunchedURLClassLoader用以加载SpringBoot中的jar,在MANIFEST.MF中的Main-Class中有指定JarLauncher

JarLauncher
在一执行java -jar 就会来到JarLauncher中执行main方法
所有应用程序类文件均可通过/BOOT-INF/classes加载,所有依赖的第三方jar均可通过/BOOT-INF/lib加载。
public class JarLauncher extends ExecutableArchiveLauncher {
static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
static final String BOOT_INF_LIB = "BOOT-INF/lib/";
public JarLauncher() {
}
protected JarLauncher(Archive archive) {
super(archive);
}
@Override
protected boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals(BOOT_INF_CLASSES);
}
return entry.getName().startsWith(BOOT_INF_LIB);
}
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
}
protected void launch(String[] args) throws Exception {
JarFile.registerUrlProtocolHandler();
ClassLoader classLoader = createClassLoader(getClassPathArchives());
launch(args, getMainClass(), classLoader);
}
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<>(archives.size());
for (Archive archive : archives) {
urls.add(archive.getUrl());
}
return createClassLoader(urls.toArray(new URL[0]));
}
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
}
SpringBoot是如何知道要加载哪些类的呢?
这里我们来带Launcher#launch中createClassLoader(getClassPathArchives())方法
@Override
protected List<Archive> getClassPathArchives() throws Exception {
List<Archive> archives = new ArrayList<>(
this.archive.getNestedArchives(this::isNestedArchive));
postProcessClassPathArchives(archives);
return archives;
}
@Override
public List<Archive> getNestedArchives(EntryFilter filter) throws IOException {
List<Archive> nestedArchives = new ArrayList<>();
for (Entry entry : this) {
if (filter.matches(entry)) {
nestedArchives.add(getNestedArchive(entry));
}
}
return Collections.unmodifiableList(nestedArchives);
}
@Override
protected boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals(BOOT_INF_CLASSES);
}
return entry.getName().startsWith(BOOT_INF_LIB);
}
static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
static final String BOOT_INF_LIB = "BOOT-INF/lib/";
从这段代码就可以看出来它是加载的BOOT-INF/classes/和BOOT-INF/lib/,然后把获取到的archives List传到LaunchedURLClassLoader去加载
总结
- SpringBoot通过maven打包成一个fat jar后
- 通过fat jar中的JarLauncher生成LaunchedURLClassLoader来加载fat jar中的jar
- 通过getMainClass拿到MANIFEST.MF中的Start-Class也就是我们SpringBoot应用启动类,通过launch开启一个新线程来运行
SpringBoot 应用启动流程
这里我们通过SpringBoot启动类开始看他究竟在启动类上做了什么。
在SpringBoot中只有一段代码SpringApplication.run(MyApplication.class),那么这段代码做了哪些事情把Spring容器启动了呢?
我们点进run方法里面去看一路跟到new SpringApplication(primarySources).run(args);通过这一句明白了他就做了new了一个new SpringApplication,然后run了一下就没了
SpringApplication
// new SpringApplication会做的事
// primarySources 传进来的配置类
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 把传进来的配置类存在SpringApplication.primarySources中,后面Spring会解析这个类
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 根据classpath下是否存在某些类来判断当前web类型,REACTIVE,SERVLET,SpringBoot版本不一样这里类型会有差别,后面版本会有WebFlux
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 从spring.factories中获取ApplicationContextInitializer这个key
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 从spring.factories中获取ApplicationListener这个key
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 从调用堆栈中,通过方法名是否是main推算出mainApplicationClass
this.mainApplicationClass = deduceMainApplicationClass();
}
我们跟进getSpringFactoriesInstances#loadSpringFactories
发现这里它把所有的spring.factories中的key都存放在了一个cache中,包括之前的自动配置类,所以后面的自动配置类获取是直接从cache中拿的。

ApplicationContextInitializer

ApplicationListener

run
public ConfigurableApplicationContext run(String... args) {
// 用来记录当前springboot启动耗时,只是简单的记录下启动时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// spring上下文的接口,后面会根据web类型创建一个Spring上下文
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 开启HeadLess模式,Headless模式是系统的一种配置模式。在该模式下,系统缺少了显示设备、键盘或鼠标,正常运行
// 可查看官网解释:https://www.oracle.com/technical-resources/articles/javase/headless.html
configureHeadlessProperty();
// 从spring.factories中获取factroies中读取了SpringApplicationRunListener这个key
SpringApplicationRunListeners listeners = getRunListeners(args);
// 发布ApplicationStartingEvent事件
listeners.starting();
try {
// 实例化传入参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 读取环境变量,读取配置文件信息(基于监听器)
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
// 忽略beaninfo的bean
configureIgnoreBeanInfo(environment);
// 打印banner横幅
Banner printedBanner = printBanner(environment);
// 根据web环境创建Spring上下文,这里创建的是AnnotationConfigServletWebServerApplicationContext
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//预初始化spring上下文
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 加载spring ioc容器,使用AnnotationConfigServletWebServerApplicationContext启动的spring容器所以springboot对它做了扩展,这里会调用到AbstractApplicationContext#refresh方法,来到了spring最核心的地方创建Bean
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) {
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;
}
prepareEnvironment
初始化环境
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 根据web类型创建默认环境,创建就会读取java环境变量和系统环境变量
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 将命令行参数放到环境变量
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 发布了ApplicationEnvironmentPreparedEvent的监听器,读取了全局配置文件
listeners.environmentPrepared(environment);
// 将所有spring.main配置信息绑定SpringApplication
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
prepareContext
初始化上下文
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
// 拿到之前读取到所有ApplicationContextInitializer的组件调用initialize方法
applyInitializers(context);
applyInitializers(context);
// 发布事件,在我这个版本contextPrepared是空,没有发布任何事件,后面版本会有ApplicationContextInitializedEvent事件
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
// 获取当前spring上下文beanFactory (负责创建bean)
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 读取主启动类,将它注册为BeanDefinition
load(context, sources.toArray(new Object[0]));
// 发布ApplicationPreparedEvent事件
listeners.contextLoaded(context);
}
tomcat启动
在run方法中会初始化ApplicationContext上下文AnnotationConfigServletWebServerApplicationContext它继承了AbstractApplicationContext,在这其中AnnotationConfigServletWebServerApplicationContext重写了onRefresh方法,也就是在AbstractApplicationContext#refresh中的onRefresh,所以在new一个ApplicationContext上下文的时候会通过onRefresh创建tomcat,我们通过AnnotationConfigServletWebServerApplicationContext#onRefresh看看究竟做了什么
onRefresh
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
// 获取servletContext,如果有外部容器这里的servletContext就有值
ServletContext servletContext = getServletContext();
// 启用内部tomcat
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
}
// 启用外部tomcat
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}
getWebServerFactory
getWebServerFactory主要做的事是从bean工厂中获取ServletWebServerFactory类型的bean,
那么ServletWebServerFactory这个类型的类什么时候注入到spring容器中的呢?
我们可以看下ServletWebServerFactoryAutoConfiguration这个自动配置类
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
从自动配置类中发现import了一个EmbeddedTomcat类,而在这个类中又注入了bean TomcatServletWebServerFactory
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
}


getWebServer
getWebServer主要做的事是创建内嵌tomcat
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(),
servletContext);
// 获取所有servlet组件然后回调onStart方法,将serclet注册进tomcat容器中
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
factory.getWebServer(getSelfInitializer())这个方法中传入了getSelfInitializer,但并不会调用,会最后回调,回调完之后就创建了servlet组件,所以我们只需要关注它是如何回调的
这里是将传入的参一步步注入到TomcatStart中去然后将TomcatStart注入到TomcatEmbeddedContext中去。最后通过tomcat.start方法运行
大致调用栈是
TomcatServletWebServerFactory#getWebServer
-- TomcatServletWebServerFactory#prepareContext
-- TomcatServletWebServerFactory#configureContext
-- context.addServletContainerInitializer(starter, NO_CLASSES);
-- TomcatServletWebServerFactory#getTomcatWebServer
-- TomcatWebServer#initialize
-- this.tomcat.start();