1.引言
在工作中,虽然我们都在基于springboot框架开发项目,还是发现很多小伙伴熟悉业务开发,但是对框架的一些底层机制原理不够了解。因此有了这篇文章的分享。
本文我将从springboot启动入口开始,逐步跟踪源码,逐步分析,力求让大家通过本文能够了解springboot的整个启动流程,主要有以下几个关注点
-
springboot框架提供了哪些核心能力
-
springboot框架启动关键流程
- 内嵌支持哪些web容器
- 如何实现web容器的启动
- 如何在不同的web容器之间实现切换
前方高能预警,需要翻阅大量源代码,请做好心理准备!
2.springboot框架核心能力
大体上来看,springboot框架提供了四方面核心能力
- 统一依赖管理
- 自动装配
- 健康检查
- cli
在日常开发中,我们直接受益的有统一依赖管理、自动装配、健康检查。想象一下
- 平常开发,我们只需要引入spring-boot-starter-xxx,即可正常使用xxx提供的能力,不用再因为依赖包之间的兼容性,而花费大量的时间去调试兼容性问题了。这就是springboot统一依赖管理后,带来的直接收益!
- 相信使用spring框架,从xml配置时代过来的小伙伴,在使用springboot框架后,神清气爽了很多!毕竟每每想到大量的xml配置文件内容,心有余悸!这就是springboot自动装配后,带来的直接收益!
- 原来我们做应用监控,怎么做呢?需要在业务层面去开发一些监控接口,反正是要写代码!现在springboot直接提供了actuator,业务健康检查,是那么so easy的事情!甚至还可以扩展HealthIndicator接口,轻松就能实现新的指标监控检查。这就是springboot提供的actuator,带来的直接收益!
相比统一依赖管理、自动装配、健康检查,cli相对业界用的比较少。
3.springboot框架启动流程
3.1.内嵌支持的web容器
使用springboot框架以后,最直观的感受是,哪怕开发web应用,只需要
- 引入spring-boot-starter-web依赖
- 在application.yml文件中,增加配置与web容器相关的一些内容,比如端口、contextpath、静态资源等
- 开发springmvc相关的controller,开放端点
- 打成一个jar包
- 通过java -jar xxx.jar启动应用,然后即可以通过浏览器访问应用
就是这么简单,稍后我们在启动流程中详细分析,web容器是如何启动的,先看一看,springboot都内嵌了哪些web容器。为了支持web容器,springboot提供了一个接口
java
public interface WebServer {
// 启动容器
void start() throws WebServerException;
// 停止容器
void stop() throws WebServerException;
int getPort();
default void shutDownGracefully(GracefulShutdownCallback callback) {
callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
}
}
并为WebServer接口,提供了如下实现:
3.2.web容器启动流程
默认情况下,即直接引入spring-boot-starter-web依赖,springboot启动应用的是tomcat容器
接下来,我们就以springboot启动tomcat容器为例,来尝试启动流程源码分析。 启动入口:FollowMeSpringbootActuatorApplication
java
@SpringBootApplication
public class FollowMeSpringbootActuatorApplication {
public static void main(String[] args) {
//springboot应用启动入口, SpringApplication.run()方法 SpringApplication.run(FollowMeSpringbootActuatorApplication.class, args);
}
}
源码解析
- 对于springboot应用,分析它的源码,我们只需要从启动类开始即可
- 进入SpringApplication内部,便可一窥究竟!源码之下无秘密!
进入方法:SpringApplication.run
java
// 1.从启动类main方法,进入run方法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
// 调用了重载的run方法
return run(new Class[]{primarySource}, args);
}
// 2.重载的run方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
// new 了一个SpringApplication实例,并再次调用重载run方法
return (new SpringApplication(primarySources)).run(args);
}
// 3.真正启动spring应用的地方,返回ioc容器:ConfigurableApplicationContext
public ConfigurableApplicationContext run(String... args) {
......省略非关键代码......
try {
......省略非关键代码......
// 关键代码:创建spring应用ioc容器
context = this.createApplicationContext();
......省略非关键代码......
// 关键代码:刷新ioc容器
this.refreshContext(context);
......省略非关键代码......
} catch (Throwable var10) {
......省略非关键代码......
}
......省略非关键代码......
}
源码解析
-
在SpringApplication内部,有多个重载的run方法
-
我们需要一直跟踪到方法:run(String... args)
-
在该方法内部,有两行关键代码需要我们关注
- context = this.createApplicationContext():创建spring ioc容器
- this.refreshContext(context):刷新ioc容器,启动web容器具体内容的入口就是这里
进入方法:createApplicationContext
java
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
// 重点关注代码:根据webApplicationType应用类型
// 决定创建ioc容器的具体实现,取值有三类
// SERVLET:创建web容器AnnotationConfigServletWebServerApplicationContext
// REACTIVE:创建响应式web容器AnnotationConfigReactiveWebServerApplicationContext
// 默认:创建普通jar应用容器AnnotationConfigApplicationContext
switch(this.webApplicationType) {
case SERVLET:
contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
break;
case REACTIVE:
contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
break;
default:
contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
}
} catch (ClassNotFoundException var3) {
throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
}
}
return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}
源码解析
-
该方法,是一个关键方法,它决定了启动应用最终拿到的ApplicationContext具体实现
-
有三种情况,根据webApplicationType取值
- SERVLET:AnnotationConfigServletWebServerApplicationContext
- REACTIVE:AnnotationConfigReactiveWebServerApplicationContext
- 默认:AnnotationConfigApplicationContext
-
第三个我们很熟悉,在还没有springboot框架以前,通过注解方式配置ioc的ApplicationContext就是它
-
我们重点关注的应该是:AnnotationConfigServletWebServerApplicationContext,注意类名称中,有ServletWebServer关键字。有点意思了,跟web容器挂上钩了!
-
但是最关键的地方是,webApplicationType的取值,从根据什么来的呢?这才是重点!
枚举类:WebApplicationType
java
public enum WebApplicationType {
NONE,
SERVLET,
REACTIVE;
// 相关ioc容器ApplicationContext标识类常量定义,用于决定启动时,加载具体的ApplicationContext实现
private static final String[] SERVLET_INDICATOR_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"};
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
......省略非关键代码......
// 关键方法:从classpath下,决定加载ApplicationContext具体实现
static WebApplicationType deduceFromClasspath() {
// 响应式【暂时不关注它,不关注这个if判断】
if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {
return REACTIVE;
} else {
// 重点关注这里:
// 如果类路径classpath下,存在Servlet、存在ConfigurableWebApplicationContext,那么说明该应用是web应用
// 返回 SERVLET
String[] var0 = SERVLET_INDICATOR_CLASSES;
int var1 = var0.length;
for(int var2 = 0; var2 < var1; ++var2) {
String className = var0[var2];
if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
return NONE;
}
}
return SERVLET;
}
}
......省略非关键代码......
}
源码解析
-
WebApplicationType枚举类,用于从classpath下,是否存在Servlet、存在ConfigurableWebApplicationContext
-
来决定启动普通的spring应用,还是启动web应用
-
根据引入spring-boot-starter-web依赖以后
- 会自动引入内嵌的tomcat,即存在Servlet
- 会自动引入spring-web,即存在ConfigurableWebApplicationContext
- 因此最终返回 SERVLET
类:AnnotationConfigServletWebServerApplicationContext
源码解析
- 如上图所示,它的类层次结构说明一切!
小结:到此拿到容器(AnnotationConfigServletWebServerApplicationContext),它明确告诉我们了,这是一个web应用上下文容器。继续往下,看是如何加载创建webServer
此时,我们需要关注另外一行关键代码了,你还记得它吗?
java
// 关键代码:刷新ioc容器
this.refreshContext(context);
进入方法:SpringApplication.refreshContext/refresh
java
private void refreshContext(ConfigurableApplicationContext context) {
// 进入resresh方法
this.refresh((ApplicationContext)context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
} catch (AccessControlException var3) {
;
}
}
/** @deprecated */
@Deprecated
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext);
// 继续进入refresh
this.refresh((ConfigurableApplicationContext)applicationContext);
}
protected void refresh(ConfigurableApplicationContext applicationContext) {
// 关键代码:最终调用了ApplicationContext的refresh方法
applicationContext.refresh();
}
源码解析
- 通过一路源码跟踪,最终发现代码 applicationContext.refresh();
- 即实现了ApplicationContext的刷新操作,而我们从上面知道,此时的applicationContext,它是ServletWebServerApplicationContext
进入方法:ServletWebServerApplicationContext.refresh
java
public final void refresh() throws BeansException, IllegalStateException {
try {
// 继续调用父类refresh方法
super.refresh();
} catch (RuntimeException var3) {
WebServer webServer = this.webServer;
if (webServer != null) {
webServer.stop();
}
throw var3;
}
}
//抽闲父类:AbstractApplicationContext#refresh
public void refresh() throws BeansException, IllegalStateException {
......省略非关键代码......
try {
......省略非关键代码......
// 关键代码:调用onRefresh方法
this.onRefresh();
......省略非关键代码......
} catch (BeansException var9) {
......省略非关键代码......
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
// onRefresh方法,在父类中是一个空方法,即钩子方法
// 该方法的具体实现,留给子类实现
// 此时的具体实现子类是:ServletWebServerApplicationContext
protected void onRefresh() throws BeansException {
}
源码解析
- 一路追踪refresh方法,最终跟到了onRefresh方法
- 而onRefresh方法,在父类中是空实现,具体实现在子类
- 此时的子类是:ServletWebServerApplicationContext
进入方法:ServletWebServerApplicationContext.onRefresh
java
protected void onRefresh() {
super.onRefresh();
try {
// 关键代码:创建WebServer,高兴!快要柳暗花明了!
this.createWebServer();
} catch (Throwable var2) {
throw new ApplicationContextException("Unable to start web server", var2);
}
}
进入方法:ServletWebServerApplicationContext.createWebServer
java
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = this.getServletContext();
if (webServer == null && servletContext == null) {
// 关键代码:获取ServletWebServer工厂,该工厂用于创建WebServer
ServletWebServerFactory factory = this.getWebServerFactory();
this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
......省略非关键代码......
} else if (servletContext != null) {
......省略非关键代码......
}
......省略非关键代码......
}
protected ServletWebServerFactory getWebServerFactory() {
// 关键代码:从classpath中,检查加载具体的webServer:tomcat/jetty/netty/undertow
String[] beanNames = this.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
if (beanNames.length == 0) {
......省略非关键代码......
} else if (beanNames.length > 1) {
......省略非关键代码......
} else {
return (ServletWebServerFactory)this.getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
}
源码解析
- 到此,我们看到最终需要获取ServletWebServerFactory工厂,该工厂用于创建具体的ServletWebServer实例
- 即具体创建的web容器是:tomcat、或者jetty、或者netty、还是undertow,需要检查classpath类路径下的依赖来决定
- 即具体的web容器依赖来决定,存在谁,那么加载的就是谁
小结:到此,我们得到了这么几个信息
- 首先,已经明确这是一个web应用,获取到的ApplicationContext是ServletWebServerApplicationContext
- 其次,启动web应用,需要创建一个WebServer,该WebServer由具体的工厂来创建,该工厂是ServletWebServerFactory
- 最终,在ServletWebServerFactory工厂中,需要创建哪个具体的web容器(tomcat/jetty/netty/undertow),由类路径classpath依赖决定
最后的谜底,我们需要回到最开始介绍springboot提供的核心能力的知识点了,其中有一条是说自动装配。
最后来看一下,最终获取的ServletWebServerFactory,到底是谁?
找到spring-boot-autoconfigure依赖,并展开它,一直找到包
properties
org.springframework.boot.autoconfigure.web.embedded
并找到类
java
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnWebApplication
@EnableConfigurationProperties({ServerProperties.class})
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
public EmbeddedWebServerFactoryCustomizerAutoConfiguration() {
}
@Configuration(
proxyBeanMethods = false
)
// 如果classpath下存在HttpServer类,那么启用NettyWebServer
@ConditionalOnClass({HttpServer.class})
public static class NettyWebServerFactoryCustomizerConfiguration {
public NettyWebServerFactoryCustomizerConfiguration() {
}
@Bean
public NettyWebServerFactoryCustomizer nettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
return new NettyWebServerFactoryCustomizer(environment, serverProperties);
}
}
@Configuration(
proxyBeanMethods = false
)
// 如果classpath下存在Undertow,那么启用UndertowWebServer
@ConditionalOnClass({Undertow.class, SslClientAuthMode.class})
public static class UndertowWebServerFactoryCustomizerConfiguration {
public UndertowWebServerFactoryCustomizerConfiguration() {
}
@Bean
public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
}
}
@Configuration(
proxyBeanMethods = false
)
// 如果classpath下,存在Server类,那么启用JettyWebServer
@ConditionalOnClass({Server.class, Loader.class, WebAppContext.class})
public static class JettyWebServerFactoryCustomizerConfiguration {
public JettyWebServerFactoryCustomizerConfiguration() {
}
@Bean
public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
return new JettyWebServerFactoryCustomizer(environment, serverProperties);
}
}
@Configuration(
proxyBeanMethods = false
)
// 如果classpath下存在Tomcat类,那么启用TomcatWebServer
@ConditionalOnClass({Tomcat.class, UpgradeProtocol.class})
public static class TomcatWebServerFactoryCustomizerConfiguration {
public TomcatWebServerFactoryCustomizerConfiguration() {
}
@Bean
public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
}
}
}
源码解析
-
EmbeddedWebServerFactoryCustomizerAutoConfiguration,是一个自动装配类
-
它根据classpath下,是否存在HttpServer、Undertow、Server、Tomcat类,来决定启用哪个web容器
- HttpServer--->Netty
- Undertow--->Undertow
- Server--->Jetty
- Tomcat--->Tomcat
-
在我们当前应用中,classpath下存在的是Tomcat,因此启动的是Tomcat web容器
相信到这里,你可以理解springboot应用中,整个web应用的启动流程了。源码有点多,如有不适,请多看几遍!
3.3.不同web容器之间切换
从前面的内容,我们理清了springboot是如何启动web应用的,它启用不同的web容器关键点是,看classpath下依赖了谁?因此要想在不同的web容器之间切换,实现就非常简单,分两个步骤
- 首先在pom.xml文件中,排除tomcat依赖
- 加入其它web容器的依赖即可