SpringBootApplication是如何啟動Tomcat的? | 破解SpringBoot Tomcat啟動之謎 !
前言
我們都知道,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);
}
}
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;
}
抓住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);
}
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 {
}
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;
}
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的強大
。
相關文章
- Tomcat在SpringBoot中是如何啟動的TomcatSpring Boot
- Tomcat啟動指令碼Tomcat指令碼
- tomcat 啟動失敗Tomcat
- 如何在linux下啟動tomcatLinuxTomcat
- Tomcat 7 啟動分析(一)啟動指令碼Tomcat指令碼
- SpringBoot 使用外部Tomcat方法及啟動原理Spring BootTomcat
- Tomcat啟動流程簡析Tomcat
- Spring Boot中Tomcat是怎麼啟動的Spring BootTomcat
- SpringBoot原始碼解析-內嵌Tomcat容器的啟動Spring Boot原始碼Tomcat
- Tomcat直接啟動正常,通過myeclipse啟動tomcat記憶體溢位TomcatEclipse記憶體溢位
- 深入淺出Tomcat/2 - Tomcat啟動和停止Tomcat
- Java Web之tomcat開機後臺啟動JavaWebTomcat
- Linux下Tomcat重新啟動LinuxTomcat
- Tomcat原始碼分析--啟動流程Tomcat原始碼
- tomcat在linux下自啟動TomcatLinux
- tomcat開機啟動指令碼Tomcat指令碼
- tomcat定時啟動指令碼Tomcat指令碼
- TOMCAT原始碼分析(啟動框架)Tomcat原始碼框架
- Linux之換源、Tomcat及jdk的安裝配置和設定Tomcat自動啟動LinuxTomcatJDK
- springboot2.0使用外部tomcat進行啟動方法Spring BootTomcat
- 詳解Tomcat系列(一)-從原始碼分析Tomcat的啟動Tomcat原始碼
- tomcat啟動超時以及啟動之後開啟網頁報404錯誤的解決方法Tomcat網頁
- 程式在Tomcat中的啟動順序Tomcat
- tomcat無法啟動的解決方法Tomcat
- Tomcat手動啟動/部署[非安裝版]Tomcat
- 如何同時啟動多個Tomcat伺服器Tomcat伺服器
- Tomcat 第二篇:啟動流程Tomcat
- liunx tomcat 啟動卡住解決方案Tomcat
- 在linux下啟動tomcat命令LinuxTomcat
- Linux tomcat 新增開機啟動LinuxTomcat
- eclipse下tomcat啟動不了拉EclipseTomcat
- Tomcat啟動過程(二):EndPoint解析Tomcat
- Spring Boot 啟動原理解析(二) Tomcat 啟動詳解Spring BootTomcat
- tomcat的啟動和網站的訪問Tomcat網站
- 將Tomcat配置到你的mac電腦上,命令列啟動tomcatTomcatMac命令列
- ubuntu15中tomcat開機自動啟動UbuntuTomcat
- springboot學習筆記:6.內建tomcat啟動和外部tomcat部署總結Spring Boot筆記Tomcat
- Tomcat 7 啟動分析(三)Digester 的使用Tomcat