javascript
springboot启动没反应_新特性:Tomcat和Jetty如何处理Spring Boot应用?
為了方便開發(fā)和部署,Spring Boot 在內(nèi)部啟動(dòng)了一個(gè)嵌入式的 Web 容器。我們知道 Tomcat 和 Jetty 是組件化的設(shè)計(jì),要啟動(dòng) Tomcat 或者 Jetty 其實(shí)就是啟動(dòng)這些組件。在 Tomcat 獨(dú)立部署的模式下,我們通過 startup 腳本來啟動(dòng) Tomcat,Tomcat 中的 Bootstrap 和 Catalina 會(huì)負(fù)責(zé)初始化類加載器,并解析server.xml和啟動(dòng)這些組件。
在內(nèi)嵌式的模式下,Bootstrap 和 Catalina 的工作就由 Spring Boot 來做了,Spring Boot 調(diào)用了 Tomcat 和 Jetty 的 API 來啟動(dòng)這些組件。那 Spring Boot 具體是怎么做的呢?而作為程序員,我們?nèi)绾蜗?SpringBoot 中的 Tomcat 注冊 Servlet 或者 Filter 呢?我們又如何定制內(nèi)嵌式的 Tomcat?今天我們就來聊聊這些話題。
Spring Boot 中 Web 容器相關(guān)的接口
既然要支持多種 Web 容器,Spring Boot 對(duì)內(nèi)嵌式 Web 容器進(jìn)行了抽象,定義了WebServer接口:
public interface WebServer { void start() throws WebServerException; void stop() throws WebServerException; int getPort();}復(fù)制代碼各種 Web 容器比如 Tomcat 和 Jetty 需要去實(shí)現(xiàn)這個(gè)接口。
Spring Boot 還定義了一個(gè)工廠ServletWebServerFactory來創(chuàng)建 Web 容器,返回的對(duì)象就是上面提到的 WebServer。
public interface ServletWebServerFactory { WebServer getWebServer(ServletContextInitializer... initializers);}復(fù)制代碼可以看到 getWebServer 有個(gè)參數(shù),類型是ServletContextInitializer。它表示 ServletContext 的初始化器,用于 ServletContext 中的一些配置:
public interface ServletContextInitializer { void onStartup(ServletContext servletContext) throws ServletException;}復(fù)制代碼這里請(qǐng)注意,上面提到的 getWebServer 方法會(huì)調(diào)用 ServletContextInitializer 的 onStartup 方法,也就是說如果你想在 Servlet 容器啟動(dòng)時(shí)做一些事情,比如注冊你自己的 Servlet,可以實(shí)現(xiàn)一個(gè) ServletContextInitializer,在 Web 容器啟動(dòng)時(shí),Spring Boot 會(huì)把所有實(shí)現(xiàn)了 ServletContextInitializer 接口的類收集起來,統(tǒng)一調(diào)它們的 onStartup 方法。
為了支持對(duì)內(nèi)嵌式 Web 容器的定制化,Spring Boot 還定義了WebServerFactoryCustomizerBeanPostProcessor接口,它是一個(gè) BeanPostProcessor,它在 postProcessBeforeInitialization 過程中去尋找 Spring 容器中 WebServerFactoryCustomizer
類型的 Bean,并依次調(diào)用 WebServerFactoryCustomizer
接口的 customize 方法做一些定制化。
public interface WebServerFactoryCustomizer<T extends WebServerFactory> { void customize(T factory);}復(fù)制代碼內(nèi)嵌式 Web 容器的創(chuàng)建和啟動(dòng)
鋪墊了這些接口,我們再來看看 Spring Boot 是如何實(shí)例化和啟動(dòng)一個(gè) Web 容器的。我們知道,Spring 的核心是一個(gè) ApplicationContext,它的抽象實(shí)現(xiàn)類 AbstractApplicationContext
實(shí)現(xiàn)了著名的refresh方法,它用來新建或者刷新一個(gè) ApplicationContext,在 refresh 方法中會(huì)調(diào)用 onRefresh 方法,AbstractApplicationContext 的子類可以重寫這個(gè)方法 onRefresh 方法,來實(shí)現(xiàn)特定 Context 的刷新邏輯,因此 ServletWebServerApplicationContext 就是通過重寫 onRefresh 方法來創(chuàng)建內(nèi)嵌式的 Web 容器,具體創(chuàng)建過程是這樣的:
@Overrideprotected void onRefresh() { super.onRefresh(); try { // 重寫 onRefresh 方法,調(diào)用 createWebServer 創(chuàng)建和啟動(dòng) Tomcat createWebServer(); } catch (Throwable ex) { }} //createWebServer 的具體實(shí)現(xiàn)private void createWebServer() { // 這里 WebServer 是 Spring Boot 抽象出來的接口,具體實(shí)現(xiàn)類就是不同的 Web 容器 WebServer webServer = this.webServer; ServletContext servletContext = this.getServletContext(); // 如果 Web 容器還沒創(chuàng)建 if (webServer == null && servletContext == null) { // 通過 Web 容器工廠來創(chuàng)建 ServletWebServerFactory factory = this.getWebServerFactory(); // 注意傳入了一個(gè) "SelfInitializer" this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()}); } else if (servletContext != null) { try { this.getSelfInitializer().onStartup(servletContext); } catch (ServletException var4) { ... } } this.initPropertySources();}復(fù)制代碼再來看看 getWebSever 具體做了什么,以 Tomcat 為例,主要調(diào)用 Tomcat 的 API 去創(chuàng)建各種組件:
public WebServer getWebServer(ServletContextInitializer... initializers) { //1. 實(shí)例化一個(gè) Tomcat,可以理解為 Server 組件。 Tomcat tomcat = new Tomcat(); //2. 創(chuàng)建一個(gè)臨時(shí)目錄 File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); //3. 初始化各種組件 Connector connector = new Connector(this.protocol); tomcat.getService().addConnector(connector); this.customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); this.configureEngine(tomcat.getEngine()); //4. 創(chuàng)建定制版的 "Context" 組件。 this.prepareContext(tomcat.getHost(), initializers); return this.getTomcatWebServer(tomcat);}復(fù)制代碼你可能好奇 prepareContext 方法是做什么的呢?這里的 Context 是指Tomcat 中的 Context 組件,為了方便控制 Context 組件的行為,Spring Boot 定義了自己的 TomcatEmbeddedContext,它擴(kuò)展了 Tomcat 的 StandardContext:
class TomcatEmbeddedContext extends StandardContext {}復(fù)制代碼注冊 Servlet 的三種方式
1. Servlet 注解
在 Spring Boot 啟動(dòng)類上加上 @ServletComponentScan 注解后,使用 @WebServlet、@WebFilter、@WebListener 標(biāo)記的 Servlet、Filter、Listener 就可以自動(dòng)注冊到 Servlet 容器中,無需其他代碼,我們通過下面的代碼示例來理解一下。
@SpringBootApplication@ServletComponentScanpublic class xxxApplication{}復(fù)制代碼 @WebServlet("/hello")public class HelloServlet extends HttpServlet {}復(fù)制代碼在 Web 應(yīng)用的入口類上加上 @ServletComponentScan, 并且在 Servlet 類上加上 @WebServlet,這樣 SpringBoot 會(huì)負(fù)責(zé)將 Servlet 注冊到內(nèi)嵌的 Tomcat 中。
2. ServletRegistrationBean
同時(shí) Spring Boot 也提供了 ServletRegistrationBean、FilterRegistrationBean 和 ServletListenerRegistrationBean 這三個(gè)類分別用來注冊 Servlet、Filter、Listener。假如要注冊一個(gè) Servlet,可以這樣做:
@Beanpublic ServletRegistrationBean servletRegistrationBean() { return new ServletRegistrationBean(new HelloServlet(),"/hello");}復(fù)制代碼這段代碼實(shí)現(xiàn)的方法返回一個(gè) ServletRegistrationBean,并將它當(dāng)作 Bean 注冊到 Spring 中,因此你需要把這段代碼放到 Spring Boot 自動(dòng)掃描的目錄中,或者放到 @Configuration 標(biāo)識(shí)的類中。
3. 動(dòng)態(tài)注冊
你還可以創(chuàng)建一個(gè)類去實(shí)現(xiàn)前面提到的 ServletContextInitializer 接口,并把它注冊為一個(gè) Bean,Spring Boot 會(huì)負(fù)責(zé)調(diào)用這個(gè)接口的 onStartup 方法。
@Componentpublic class MyServletRegister implements ServletContextInitializer { @Override public void onStartup(ServletContext servletContext) { //Servlet 3.0 規(guī)范新的 API ServletRegistration myServlet = servletContext .addServlet("HelloServlet", HelloServlet.class); myServlet.addMapping("/hello"); myServlet.setInitParameter("name", "Hello Servlet"); } }復(fù)制代碼這里請(qǐng)注意兩點(diǎn):
- ServletRegistrationBean 其實(shí)也是通過 ServletContextInitializer 來實(shí)現(xiàn)的,它實(shí)現(xiàn)了 ServletContextInitializer 接口。
- 注意到 onStartup 方法的參數(shù)是我們熟悉的 ServletContext,可以通過調(diào)用它的 addServlet 方法來動(dòng)態(tài)注冊新的 Servlet,這是 Servlet 3.0 以后才有的功能。
Web 容器的定制
我們再來考慮一個(gè)問題,那就是如何在 Spring Boot 中定制 Web 容器。在 Spring Boot 2.0 中,我們可以通過兩種方式來定制 Web 容器。
第一種方式是通過通用的 Web 容器工廠 ConfigurableServletWebServerFactory,來定制一些 Web 容器通用的參數(shù):
@Componentpublic class MyGeneralCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> { public void customize(ConfigurableServletWebServerFactory factory) { factory.setPort(8081); factory.setContextPath("/hello"); }}復(fù)制代碼第二種方式是通過特定 Web 容器的工廠比如 TomcatServletWebServerFactory 來進(jìn)一步定制。下面的例子里,我們給 Tomcat 增加一個(gè) Valve,這個(gè) Valve 的功能是向請(qǐng)求頭里添加 traceid,用于分布式追蹤。TraceValve 的定義如下:
class TraceValve extends ValveBase { @Override public void invoke(Request request, Response response) throws IOException, ServletException { request.getCoyoteRequest().getMimeHeaders(). addValue("traceid").setString("1234xxxxabcd"); Valve next = getNext(); if (null == next) { return; } next.invoke(request, response); } }復(fù)制代碼跟第一種方式類似,再添加一個(gè)定制器,代碼如下:
@Componentpublic class MyTomcatCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> { @Override public void customize(TomcatServletWebServerFactory factory) { factory.setPort(8081); factory.setContextPath("/hello"); factory.addEngineValves(new TraceValve() ); }}復(fù)制代碼本期精華
今天我們學(xué)習(xí)了 Spring Boot 如何利用 Web 容器的 API 來啟動(dòng) Web 容器、如何向 Web 容器注冊 Servlet,以及如何定制化 Web 容器,除了給 Web 容器配置參數(shù),還可以增加或者修改 Web 容器本身的組件。
課后思考
我在文章中提到,通過 ServletContextInitializer 接口可以向 Web 容器注冊 Servlet,那 ServletContextInitializer 跟 Tomcat 中的 ServletContainerInitializer 有什么區(qū)別和聯(lián)系呢?
不知道今天的內(nèi)容你消化得如何?如果還有疑問,請(qǐng)大膽的在留言區(qū)提問,也歡迎你把你的課后思考和心得記錄下來,與我和其他同學(xué)一起討論。如果你覺得今天有所收獲,歡迎你把它分享給你的朋友。
總結(jié)
以上是生活随笔為你收集整理的springboot启动没反应_新特性:Tomcat和Jetty如何处理Spring Boot应用?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 极光尔沃切片软件_极光尔沃3D打印机走进
- 下一篇: 接口隔离原则_设计模式之七大设计原则(上