前言
Spring MVC 讓開發(fā)者不用了解 Servlet 細(xì)節(jié),專注于 Controller 編寫 API 接口。Spring Boot 更是采用約定大于配置的設(shè)計(jì)思想,通過內(nèi)嵌 Tomcat 的方式讓開發(fā)者可以快速構(gòu)建并部署一個(gè) Web 應(yīng)用。怎么做到的呢?
Tomcat啟動(dòng)方式
早期的開發(fā),一般是基于 Spring 和 Spring MVC 構(gòu)建我們的應(yīng)用,然后把項(xiàng)目打成 War 包。在服務(wù)器上安裝 Tomcat,把我們的 War 包放到對應(yīng)的 webapp 目錄下,啟動(dòng) Tomcat 服務(wù)就可以訪問了。
其實(shí)要部署我們的服務(wù),沒必要這么繁瑣,通過代碼啟動(dòng) Tomcat 早就不是新鮮事了。
我這里寫一個(gè)示例,只引入 Spring MVC 和 Tomcat 依賴:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.31</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.83</version>
</dependency>
</dependencies>
編寫我們的 Controller
@RestController
public class HelloContrller {
@RequestMapping("hello")
public String hello() {
return "hello world!";
}
}
再編寫我們的啟動(dòng)類,手動(dòng)把 Tomcat 給啟動(dòng)起來并注冊 DispatcherServlet。
@Configuration
@ComponentScan
public class Application {
public static void main(String[] args) throws Exception {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(Application.class);
context.refresh();
Tomcat tomcat = new Tomcat();
Connector connector = new Connector();
connector.setPort(8080);
tomcat.getService().addConnector(connector);
final String contextPath = "";
StandardContext standardContext = new StandardContext();
standardContext.setPath(contextPath);
standardContext.addLifecycleListener(new Tomcat.FixContextListener());
tomcat.getHost().addChild(standardContext);
standardContext.addServletContainerInitializer(new ServletContainerInitializer() {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
System.err.println("Servlet容器初始化...");
DispatcherServlet servlet = new DispatcherServlet(context);
tomcat.addServlet(contextPath, "DispatcherServlet", servlet);
standardContext.addServletMappingDecoded("/*", "DispatcherServlet");
}
}, Collections.EMPTY_SET);
tomcat.start();
}
}
運(yùn)行 Application 類,即可訪問服務(wù)
curl localhost:8080/hello
hello world!
Spring Boot 底層其實(shí)也是這么干的,一起來分析下吧。
設(shè)計(jì)實(shí)現(xiàn)
回到程序啟動(dòng)的入口,為什么執(zhí)行下面一行代碼,Web 服務(wù)就起來了。
SpringApplication.run(Application.class, args);
Spring Boot 首先會(huì)實(shí)例化一個(gè) SpringApplication 對象,在構(gòu)造函數(shù)里,首先要推導(dǎo)出 Web 應(yīng)用類型,才好啟對應(yīng)的服務(wù)。
public enum WebApplicationType {
NONE,
SERVLET,
REACTIVE;
}
- NONE:無需啟動(dòng) Web 服務(wù)
- SERVLET:基于 Servlet 容器的 Web 應(yīng)用
- REACTIVE:響應(yīng)時(shí) Web 應(yīng)用
推導(dǎo)的方法是WebApplicationType#deduceFromClasspath
,原理是檢查 ClassPath 路徑下是否存在對應(yīng)的類。比如:存在org.springframework.web.reactive.DispatcherHandler
類那就是 SERVLET 類型(不絕對)
private static final String[] SERVLET_INDICATOR_CLASSES = { "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";
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;
}
Spring Boot 本質(zhì)還是一個(gè) Spring 應(yīng)用,所以它肯定是要依賴上下文容器對象的。所以在run()
里它會(huì)調(diào)用createApplicationContext()
根據(jù) Web 應(yīng)用類型創(chuàng)建對應(yīng)的 ConfigurableApplicationContext。不同的 Web 應(yīng)用類型對應(yīng)不同的實(shí)現(xiàn)類,創(chuàng)建職責(zé)交給了DefaultApplicationContextFactory#create
,它會(huì)去解析META-INF/spring.factories
文件里配置的工廠類,然后判斷哪個(gè)工廠類支持創(chuàng)建。
private <T> T getFromSpringFactories(WebApplicationType webApplicationType,
BiFunction<ApplicationContextFactory, WebApplicationType, T> action, Supplier<T> defaultResult) {
for (ApplicationContextFactory candidate : SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class,
getClass().getClassLoader())) {
// 實(shí)例化 AnnotationConfigServletWebServerApplicationContext
T result = action.apply(candidate, webApplicationType);
if (result != null) {
return result;
}
}
return (defaultResult != null) ? defaultResult.get() : null;
}
默認(rèn)配置的工廠類:
org.springframework.boot.ApplicationContextFactory=\
org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext.Factory,\
org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext.Factory
默認(rèn)是 Servlet 環(huán)境,所以會(huì)使用 AnnotationConfigServletWebServerApplicationContext.Factory 工廠類,創(chuàng)建的上下文對象是 AnnotationConfigServletWebServerApplicationContext。
實(shí)例化上下文對象后,緊接著就是調(diào)用其refresh()
刷新上下文,這是個(gè)模板方法,流程在分析 Spring 源碼時(shí)已經(jīng)說過了,這里就略過了。
我們這里要重點(diǎn)關(guān)注的是子類重寫后的擴(kuò)展方法ServletWebServerApplicationContext#onRefresh
,它會(huì)在父類準(zhǔn)備好整個(gè)環(huán)境后創(chuàng)建 Web 服務(wù)。
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
createWebServer()
首先要獲取 ServletWebServerFactory 工廠對象,默認(rèn)的 Servlet 容器是 Tomcat,所以工廠類是 TomcatServletWebServerFactory。在實(shí)例化工廠類時(shí)要求傳入一組 ServletContextInitializer,Spring 在初始化 Servlet 容器時(shí)會(huì)調(diào)用它的onStartup()
用于注冊 Servlet。
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {// 默認(rèn)走這里
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
ServletWebServerFactory factory = getWebServerFactory();
createWebServer.tag("factory", factory.getClass().toString());
// 通過工廠獲取WebServer,會(huì)直接啟動(dòng)
this.webServer = factory.getWebServer(getSelfInitializer());
createWebServer.end();
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
......
}
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
// Spring初始化Servlet容器時(shí)觸發(fā)
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
TomcatServletWebServerFactory#getWebServer
會(huì)實(shí)例化 Tomcat 并啟動(dòng)。
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
// 基礎(chǔ)目錄
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
for (LifecycleListener listener : this.serverLifecycleListeners) {
tomcat.getServer().addLifecycleListener(listener);
}
// 連接器
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
// 配置Engine
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
/**
* 配置上下文,這里會(huì)把ServletContextInitializer封裝成TomcatStarter
* 并設(shè)置到Host.Context
*/
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
配置 Tomcat 是個(gè)復(fù)雜的過程,這里不贅述,與我們最相關(guān)的就是 Port、ContextPath、Servlet 的配置,我們重點(diǎn)關(guān)注 Servlet 的配置。
我們知道,Spring MVC 的核心是 DispatcherServlet,它是何時(shí)被注冊到 Tomcat 的呢???這就不得不提到另一個(gè)組件 ServletContainerInitializer。
ServletContainerInitializer 是 Servlet 3.0 提供的用來初始化 Servlet 容器的接口,通過實(shí)現(xiàn)這個(gè)接口可以讓第三方組件有機(jī)會(huì)來對容器做一些初始化的工作,比如動(dòng)態(tài)的注冊 Servlet、Filter 等等。
顯然,Spring Boot 需要注冊 DispatcherServlet。所以 Spring Boot 首先會(huì)把容器內(nèi)的所有 ServletContextInitializer Bean 統(tǒng)一封裝成 TomcatStarter,而 TomcatStarter 恰恰就是 ServletContainerInitializer 的實(shí)現(xiàn)類。所以 Tomcat 啟動(dòng)時(shí)會(huì)觸發(fā)其onStartup()
@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
try {
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
}
catch (Exception ex) {
}
}
代碼很簡單,就是挨個(gè)調(diào)用ServletContextInitializer#onStartup
,其中有個(gè)最關(guān)鍵的實(shí)現(xiàn)類就是 DispatcherServletRegistrationBean,顧名思義,它就是用來注冊 DispatcherServlet。
注冊的方法是ServletRegistrationBean#addRegistration
,這里就會(huì)注冊我們最關(guān)心的 DispatcherServlet
@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
String name = getServletName();
return servletContext.addServlet(name, this.servlet);
}
為了便于理解,這里畫了一張流程圖:文章來源:http://www.zghlxwxcb.cn/news/detail-784318.html
尾巴
Spring Boot 本身也是個(gè) Spring 應(yīng)用,它也要依賴于上下文容器對象,如果我們構(gòu)建的是 Web 應(yīng)用,它就會(huì)創(chuàng)建適用于 Web 環(huán)境的上下文容器,例如 ServletWebServerApplicationContext,然后通過父類的模板方法來 refresh,只不過它重寫了 onRefresh 方法,等待父類準(zhǔn)備好環(huán)境后會(huì)創(chuàng)建 WebServer,啟動(dòng)我們的 Web 服務(wù),默認(rèn)啟動(dòng)的是 Tomcat,然后通過實(shí)現(xiàn) ServletContainerInitializer 的方式來注冊 DispatcherServlet。這就是 Spring Boot 內(nèi)嵌 Tomcat 的秘密。文章來源地址http://www.zghlxwxcb.cn/news/detail-784318.html
到了這里,關(guān)于SpringBoot內(nèi)嵌Tomcat啟動(dòng)流程的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!