SpringBootApplication是如何启动Tomcat的? | 破解SpringBoot Tomcat启动之谜 !
2019年08月14日 11:58:33 Moshow郑锴 阅读数 140更多
所属专栏: SpringBoot2启示录
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/moshowgame/article/details/99545081
前言
我们都知道,SpringBoot内置了容器Tomcat,可以直接启动WebServletServer,那么SpringBoot是如何启动Tomcat的?
本文从Main方法入手,从SpringApplication.run跟到ServletWebServerApplicationContext 再到TomcatServletWebServerFactory,破解SpringBoot Tomcat启动之谜 !!!
- springboot版本 :
2.0.5.RELEASE
- 追踪工具 :
IDEA
Main方法
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication .class,args);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
Run方法
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
* 解释 by zhengkai.blog.csdn.net
*/
public ConfigurableApplicationContext run(String... args) {
//zhengkai.blog.csdn.net
//定义个StopWatch,Wathch是表,定义个表来监控一下运行的性能,用了多少ms
StopWatch stopWatch = new StopWatch();
//开始计时
stopWatch.start();
//定义一个可配置的上下文,ConfigurableApplicationContext接口extends了ApplicationContext+Lifecycle+Closeable,可以被大多数的应用上下文实现,为配置应用上下文提供便利.
ConfigurableApplicationContext context = null;
//ExceptionReporter明显是一个错误报告,for SpringApplication的starup error,基本只是启动的报错(不包括启动后的报错)
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//RunListeners,运行监听器SpringApplicationRunListeners=List<SpringApplicationRunListener>,是个集合来的
//包括starting()environmentPrepared()contextPrepared()contextLoaded()started()running()failed()事件的监听
SpringApplicationRunListeners listeners = getRunListeners(args);
//进入监听器的staring阶段
listeners.starting();
try {
//获取SpringBootApplication的运行参数,如果你是java -jar xxx.jar --server.context-path=/mypath,则可以动态获取
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//开始准备环境,传入监听器和运行参数,获得环境变量,可以getSystemEnvironment和getSystemProperties,可以setActiveProfiles设置激活的配置
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//从spring.beaninfo.ignore获取需要忽然的bean列表
configureIgnoreBeanInfo(environment);
//打印banner,控制台输出,Mode有三种:OFF/console/log file
Banner printedBanner = printBanner(environment);
//***开始创建上下文,这个比较重点,下面列出来单独说
//根据webApplicationType,获取对应的CONTEXT_CLASS,分SERVLET(Web)/REACTIVE(webflux)/default(spring).开始创建上下文
context = createApplicationContext();
//里面实际上创建了SpringFactoriesInstances
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//这里才是真正创建上下文的地方:
//context.setEnvironment设置环境变量.setResourceLoader设置资源加载器,用context来初始化所有initializer
//开始logStartupInfo输出日志,registerArguments注册参数,registerBanner注册控制台输出
//createBeanDefinitionLoader创建bean定义加载器,最后load到所有监听器listener上
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//这里为context注册一个ShutdownHooK,也就是关掉应用的时候要做什么
refreshContext(context);
//这是一个空方法,可供改造???
afterRefresh(context, applicationArguments);
//好了,计时停止,看下启动用了多少ms
stopWatch.stop();
if (this.logStartupInfo) {
//输出SpringBoot那些启动信息,日志,用了多少ms等,你平常看到的那堆
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//进入监听器的启动完成事件
listeners.started(context);
//分ApplicationRunner和CommandLineRunner,callback run
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
//万一启动过程中有报错,就handleExitCode然后reportFailure,然后重新抛出RuntimeException
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
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
抓住createApplicationContext
大致过了一遍之后,我觉得我们应该关注这个方法,createApplicationContext()
/**
* Strategy method used to create the {@link ApplicationContext}. By default this
* method will respect any explicitly set application context or application context
* class before falling back to a suitable default.
* @return the application context (not yet refreshed)
* @see #setApplicationContextClass(Class)
*/
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
//DEFAULT_WEB_CONTEXT_CLASS=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
//org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
//org.springframework.context.annotation.AnnotationConfigApplicationContext
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
CONTEXT_CLASS分析
DEFAULT_WEB_CONTEXT_CLASS=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
从DEFAULT_WEB_CONTEXT_CLASS
一路跟踪到extends的类,直到发现新大陆:
//没什么可以看
public class AnnotationConfigServletWebServerApplicationContext
extends ServletWebServerApplicationContext implements AnnotationConfigRegistry{
}
//重点: 整理by zhengkai.blog.csdn.net
public class ServletWebServerApplicationContext extends GenericWebApplicationContext
implements ConfigurableWebServerApplicationContext{
//放眼一望,这个里面也相当丰富,很多内容
//发现WebServer
private volatile WebServer webServer;
//发现ServletConfig
private ServletConfig servletConfig;
//发现创建WebServer的方法
private void createWebServer() {
WebServer webServer = this.webServer;
//Servlet上下文
ServletContext servletContext = getServletContext();
//有server和上下文,则不需要创建,直接从ServletWebServer工厂里面拿
if (webServer == null && servletContext == null) {
//ServletWebServerFactory工厂,应该有很多东西,提到下面来分析
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
//没有话,从ServletContextInitializer开始一个吧
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}
}
//没啥子
public class GenericWebApplicationContext extends GenericApplicationContext
implements ConfigurableWebApplicationContext, ThemeSource {
}
//没啥子
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
}
//AbstractApplicationContext 这个类也还是挺大的,虽然是抽象类,但是很多方法,超级丰富,值得关注
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
ServletWebServerFactory工厂
IDEA下ctrl+alt+B,直接查看ServletWebServerFactory工厂的实现,发现:
Tomcat启动之谜
看到上面几个工厂类的时候,这个谜题已经破解了。同时Jetty,Undertow的启动之谜也揭晓了。
/**
* 创建并获取TomcatWebServer,TomcatServletWebServerFactory,整理 by zhengkai.blog.csdn.net
*/
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory
: createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
//getTomcatWebServer=return new TomcatWebServer(tomcat, getPort() >= 0);
return getTomcatWebServer(tomcat);
}
/**
* 创建并获取JettyWebServer,JettyServletWebServerFactory,整理 by zhengkai.blog.csdn.net
*/
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
JettyEmbeddedWebAppContext context = new JettyEmbeddedWebAppContext();
int port = (getPort() >= 0) ? getPort() : 0;
InetSocketAddress address = new InetSocketAddress(getAddress(), port);
Server server = createServer(address);
configureWebAppContext(context, initializers);
server.setHandler(addHandlerWrappers(context));
this.logger.info("Server initialized with port: " + port);
if (getSsl() != null && getSsl().isEnabled()) {
customizeSsl(server, address);
}
for (JettyServerCustomizer customizer : getServerCustomizers()) {
customizer.customize(server);
}
if (this.useForwardHeaders) {
new ForwardHeadersCustomizer().customize(server);
}
//getJettyWebServer=return new JettyWebServer(server, getPort() >= 0);
return getJettyWebServer(server);
}
/**
* 创建并获取UndertowWebServer,UndertowServletWebServerFactory,整理 by zhengkai.blog.csdn.net
*/
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
DeploymentManager manager = createDeploymentManager(initializers);
int port = getPort();
Builder builder = createBuilder(port);
//getUndertowWebServer=return new UndertowServletWebServer(builder, manager, getContextPath(),isUseForwardHeaders(), port >= 0, getCompression(), getServerHeader());
return getUndertowWebServer(builder, manager, port);
}
//Undertow做多了一层Builder的封装
private Builder createBuilder(int port) {
Builder builder = Undertow.builder();
if (this.bufferSize != null) {
builder.setBufferSize(this.bufferSize);
}
if (this.ioThreads != null) {
builder.setIoThreads(this.ioThreads);
}
if (this.workerThreads != null) {
builder.setWorkerThreads(this.workerThreads);
}
if (this.directBuffers != null) {
builder.setDirectBuffers(this.directBuffers);
}
if (getSsl() != null && getSsl().isEnabled()) {
customizeSsl(builder);
}
else {
builder.addHttpListener(port, getListenAddress());
}
for (UndertowBuilderCustomizer customizer : this.builderCustomizers) {
customizer.customize(builder);
}
return builder;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
Tomcat vs Jetty vs Undertow
每个Web容器都有自己的设计和组件:
- Tomcat是Connector
- Jetty是Handler
- Undertow是Builder
单纯比较 Tomcat 与 Jetty 的性能意义不是很大,只能说在某种使用场景下,它表现的各有差异。因为它们面向的使用场景不尽相同。从架构上来看 Tomcat 在处理少数非常繁忙的连接上更有优势
,也就是说连接的生命周期如果短的话,Tomcat 的总体性能更高。
而 Jetty 刚好相反,Jetty 可以同时处理大量连接而且可以长时间保持这些连接
。例如像一些 web 聊天应用非常适合用 Jetty 做服务器,像淘宝的 web 旺旺就是用 Jetty 作为 Servlet 引擎。
另外 Jetty 默认使用的是 NIO 技术,在处理 I/O 请求
上更占优势,Tomcat 默认使用的是 BIO,在处理静态资源
时,Tomcat 的性能不如 Jetty。
=。=Undertow可能大家陌生有点,是一个Java开发的灵活的高性能Web服务器,提供包括阻塞和基于NIO的非阻塞机制。Undertow是红帽公司的开源产品,是Wildfly默认的Web服务器。SpringBoot2中可以将Web服务器切换到Undertow来提高应用性能。
Undertow认为它的运用场景是在IO密集型
的系统应用中,简单点的讲,就是结合了Tomcat和Jetty的优点,类似Netty的强大
。