簡介
Tomcat與spring是最常用的東東。本文以Tomcat代表webServer,對比了從Tomcat這樣的webServer,來啓動spring應用,和最新的springboot啓動Tomcat的源碼實現過程。加深了對兩個系統的瞭解,從大的方向上學習了系統之間如何組合及設計考量。
學習了很多相關技術的貼子並閱讀了源碼,但目前沒看到全面分析對比的文章。
本文以功能爲本,注重核心類與接口的關係,有助於整體上把握大系統的設計。不會有太多的代碼,更不會分析不太重要的接口,不會有細節的類圖與泳道圖。本文以Servlet 3.0+環境爲主,就不介紹太早的web.xml配置了。我看的springboot是2.2.0.BUILD-SNAPSHOT。
tomcat啓動多個包含的應用 **VS** 一個spring應用通過web服務器展示
感覺spring從規範tomcat下的一個應用,到了以應用爲主,通過各種途經暴露自己的核心應用了,甚至react方式繞過servlet了。算是逆襲吧!
1. 從Tomcat啓動spring
1.1 tomcat給外部系統的機會
主要提供有ServletContainerInitializer接口與其上的@HandlesTypes註解類。從名字上可以知道,讓外部提供一個參與初始化ServletContainer的類。該
接口方法onStartup(Set<Class<?>> c, ServletContext ctx)。參數是所有實現@HandlesTypes指明接口的實現類,與ServletContext 。
Tomcat提供的機會就這麼多,它會從meta-inf/serivces/ServletContainerInitializer文件中找到具體實現了SpringServletContainerInitializer的類A,還會找到實現類上@HandlesTypes()註解裏的接口的所有實現類Bs。最後調用A的onstartup方法,參數是Bs與給它的ServletContext 。
有一點奇怪,爲何tomcat不少管一點,只調用A,讓A自己找所有的Bs?畢竟B類型自己說了算的。
1.2 spring如何對接
-
對接ServletContainerInitializer
Spring-web中的meta-inf裏面的文本文件裏寫的org.springframework.web.SpringServletContainerInitializer。這個類的註解是@HandlesTypes(WebApplicationInitializer.class) ,它的onStartup方法就是實現化所有的WebApplicationInitializer.class實現類,並調用它們的initializer.onStartup(servletContext);
servletContext是tomcat給過來的,現在交給了所有的WebApplicationInitializer.class實現類。它並沒有核心功能,如同一個中介一樣。
-
對接@HandlesTypes(WebApplicationInitializer.class)
Spring爲了方便使用,引入了一個抽象類來實現WebApplicationInitializer.class,也就是 org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer 。因爲我們最終要按自己的配置要求擴展它,代替web.xml的配置就是在這裏。
因此當部署到 Servlet 3.0 容器中的時候,容器通過@HandlesTypes會自動發現它,通過中介SpringServletContainerInitializer,來配置Servlet上下文servletContext。
-
AbstractAnnotationConfigDispatcherServletInitializer要你實現什麼哪些抽象方法?
方法一:Class<?>[] getRootConfigClasses();
得到@Configuration註解的類,給createRootApplicationContext()來用。我們知道這個註解通常可以生成AnnotationConfigWebApplicationContext類型的一個spring容器。這個是根容器。
方法二:Class<?>[] getServletConfigClasses();
得到@Configuration註解的類,給createServletApplicationContext()用。也是用來生成AnnotationConfigWebApplicationContext類型的spring容器,這個會是一個Servlet所屬的子容器。目前還沒有和根容器關聯。
-
AbstractAnnotationConfigDispatcherServletInitializer類機制是怎麼的?
補全了抽象方法,我們還是要知道這個類被中介調用的,中介調它onStartup方法,傳入servletContext。這個方法主要有兩段功能組合,一個是父類中,一個是本類中。
//<!------------------------onStartup方法解析(兩段功能):-----------------> @Override public void onStartup(ServletContext servletContext) throws ServletException { super.onStartup(servletContext); registerDispatcherServlet(servletContext); } //----父類super.onStartup功能: //先產生根spring容器,再把容器被包裝進一個對servletContext監聽的ContextLoaderListener。它實現接口的contextInitialized與contextDestroyed兩個動作由servletContext觸發。前者會把根spring容器作爲servletContext中的一個KV項。 但servletContext何時初始化要等先被配置好。web.xml出有ContextLoaderListener的配置。 WebApplicationContext rootAppContext = createRootApplicationContext(); if (rootAppContext != null) { ContextLoaderListener listener = new ContextLoaderListener(rootAppContext); //getRootApplicationContextInitializers(),一般不用。 listener.setContextInitializers(getRootApplicationContextInitializers()); servletContext.addListener(listener); } //----子類中registerDispatcherServlet的功能: //產生dispatcherServlet與它的MVC容器。最後把dispatcherServlet註冊進servletContext。並設置啓動,mapping等信息。在WEB.XML中也有這樣的設置項目。 WebApplicationContext servletAppContext = createServletApplicationContext(); FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);//new一個dispatcherServlet。 dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers()); ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet); registration.setLoadOnStartup(1); registration.addMapping(getServletMappings()); registration.setAsyncSupported(isAsyncSupported());
-
出現的DispatcherServlet特別說明一下
Servlet一般有init(),service(req,res),destroy()等方法。DispatcherServlet持有MVC容器,很好利用容器中的mapping與controller對象處理業務了。先關注一下init();真正的功能在initServletBean()中的initWebApplicationContext()。它註冊進了servletContext,就可以從中拿到root窗口,並用setParent(rootContext)方法設置好它所持有的mvc容器的父級spring容器,這樣@controller對象就好從父級中找到@service對象了。
-
上面把web服務啓動後,主要功能都設置好了。不過servletContext的contextInitialized與DispatcherServlet的init()方法,只是配置好了功能,還需要等時機來trigger起來。參考spring中類被自動裝配好才init,所以servletContext應該也等相關的配置(Servlet類型的,還有Listener、Filter類型)都好就,就可以init()了。 servlet的初始化有兩種策略:lazy-loading和eager-loading;前者當第一個請求過來的時候纔會調用init,後者是容器初始化的時候就調用init。反正是先根容器好了,纔可以執行掛着子容器的操作。
1.3 總結
目標
Tomcat只給外部應用一個時機,讓外部配置servletContext。而spring的目標是把兩種容器(共用根容器與每個servlet的單獨子容器)及DispatcherServlet等東東加進servletContext去,讓DispatcherServlet用作請求轉發處理。
實現
WebServer啓動時,會啓動一個文本文件中SPI的ServletContainerInitializer實現,把servletContext給它處理。這個spring的中介會實例化最終用戶配置的一個AbstractDispatcherServletInitializer,把servletContext給它處理。
AbstractDispatcherServletInitializer處理時會根據配置類產生根容器,並使用一個監聽在servletContext.init()時把根容器加爲servletContext中的一個KV項。
然後它根據另一個MVC配置類產生子容器與持有它的DispatcherServlet,並註冊到servletContext。等DispatcherServlet.init()時會關聯上面說的父容器。
1.4 引申
前面介紹的AbstractAnnotationConfigDispatcherServletInitializer用起來很簡單,只要設置兩個spring容器的配置類就可以了,父子容器就都有了,用着爽。但要自己進行些處理就麻煩點了,你可以繼承抽象類的父類多些靈活性。另外這個文章:https://my.oschina.net/521cy/blog/702864【零配置即使用Java Config + Annotation】中,沒有去繼承的抽象類,自己實現了相關接口,並詳細介紹了與web.xml的對比進行配置,可以參考。其核心的功能還是一樣的。如果你想配置多個Servlet,或者DispatcherServlet,都是比較容易實現的了。
這個文章中的方法:onStartup(ServletContext container),後面的container名稱不妥,ServletContainer與ServletContext是不同層次的東西,前者更大,這樣寫名不副實。
2. 從spring啓動Tomcat
這個就是springboot的方式,用main啓動,使用內嵌Tomcat。
2.1 spring的反客爲主的思路
spring的根容器中都是核心業務,按說Tomcat只是一個暴露通訊方式,即可以用Tomcat,也可以用其它Servlet容器。還可以不用Servlet容器,比如WebApplicationType.REACTIVE類型,會繞過servlet容器。按說它還可以進一步適配各種通訊協議供外部使用核心業務。這就是反客爲主。
注:看過一個區塊鏈教程中,HTTPService httpService = new HTTPService(blockService, p2pService);用http通訊服務去整合核心服務與P2P通訊,明顯不妥當。應該用核心去整合通訊方式並適配多種方式纔是穩定的。
既然以spring中的業務爲主,它就會在啓動中帶動相關的其它外部應用模塊,比如Tomcat容器的啓動。
2.2 springboot的啓動
通常我們的應用中,在有@SpringBootApplication的主類的main中調用:SpringApplication.run(*.class),進而調用到下面的方法:
//1。啓動後調用的方法:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
//配置一個new出來的SpringApplication,並運行,產生一個springIoc容器。
return new SpringApplication(primarySources).run(args);
}
//2。上面的方法中的run裏面:就是生成一個容器及常見的操作:prepareContext,refreshContext,afterRefresh
//而new 操作,根據類判斷,可以產生三種容器,一般是servlet類型的特殊spring容器AnnotationConfigServletWebServerApplicationContext,這裏還沒確定是tomcat或者jetty呢
...
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
...
//3。AnnotationConfigServletWebServerApplicationContext父類的refresh中會調用onRefresh,裏面有一句createWebServer();這裏就開始生成Web服務器了。
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
2.3 產生WebServer工廠的過程
僅會生成特定的WebServer,併產生一個初始化工具(ServletContextInitializer實現類)給它。有點像新成立一個分公司,卻只派了一個業務指導過去。
看工具接口名字就知道是用來初始化ServletContext的。ServletContext的層次在Tomcat中並不高,上面還有container,且看分析。
//1。createWebServer();中主要有這兩句,用一個工廠來生成WebServer,工廠包含mock的共有4種。同時把getSelfInitializer得到的一個實現了ServletContextInitializer的初始化工具給它。
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
//2。這個工具的寫法有點獨特this::selfInitialize,主要是實現了onStartup(是ServletContext servletContext接口)方法。方法體如下,卻看不到方法名字:
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
//3。上面的方法中有兩個地方說明一下:
prepareWebApplicationContext(servletContext):主要是把自己這個spring根容器註冊到servletContext中,簡單粗暴,不象前者要在監聽servletContext時才設置。
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this);
getServletContextInitializerBeans():
//方法中說明爲:By default this method will first attempt to find
* {@link ServletContextInitializer}, {@link Servlet}, {@link Filter} and certain
* {@link EventListener} beans.
//這就是從根容器中找到所有實現了ServletContextInitializer接口的類,用來註冊servlet/filter等東東進入ServletContext。與上面的selfInitialize是一個接口,不過是其內部調用的,作用不同。
上面產生一個WebServer的工廠,另外就是傳入了一個工具。這個工具被執行時,除了自己處理外,又從spring容器中找了一堆其它的工具來處理。所有的這兩層工具都實現了ServletContextInitializer,不過前者註冊根spring容器,後者註冊servlet等東西。這些工具都在等着onStartup才 運行。onStartup後面會講到。
2.4 產生TomcatServer的過程
工具們傳了進來,具體又傳給了誰?
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);
connector.setThrowOnFailure(true);
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);
}
//1。前面都是產生嵌入的Tomcat及它的內部對象。後面兩句是重點。先分析第一個。
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
//2。再產生了一個Web應用,放在host中。工具initializers又傳給了它。
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
...
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
...
File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase");
context.setDocBase(docBase.getAbsolutePath());
...
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
host.addChild(context);
//3。這句是重點,繼續傳initializers進去。
configureContext(context, initializersToUse);
postProcessContext(context);
}
//4。configureContext()的主要內容如下,產生了一個ServletContainerInitializer接口的實現TomcatStarter,它賦給了TomcatEmbeddedContext的目的用來監聽WebServer的啓動的,而那些工具給了它備用。
TomcatStarter starter = new TomcatStarter(initializers);
if (context instanceof TomcatEmbeddedContext) {
TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
embeddedContext.setStarter(starter);
embeddedContext.setFailCtxIfServletStartFails(true);
}
context.addServletContainerInitializer(starter, NO_CLASSES);
//5。TomcatStarter作爲ServletContainerInitializer,被WebServer啓動調用其onStartup,同時會得到servletContext,最外面傳入的工具備用在此,就是來初始化servletContext的。前面設置KV根少了一個監控ServletContext的東東,這裏又多了一個監控WebServer的東東(叫ServletContainerInitializer這個名字表明瞭監控的目的,感覺完整的應該叫InitializeServletContainer_ServerListener吧:)。
//6。return getTomcatWebServer(tomcat);
//這句之前都是處理tomcat內部的host->TomcatEmbeddedContext->TomcatStarter。這裏是外部包了一層,通常我們適配多種產品,都會分別外包一層,以抽象出公共的對象,讓外部無感使用。這句內部有一句:
this.tomcat.start();
//正式啓動了tomcat了。前面配置好的ServletContainerInitializer開始工作了,傳過來ServletContext了,ServletContextInitializer也都可以工作了。
該springboot中的接口
ServletContextInitializer
和前面介紹的Spring Web
的另外一個接口WebApplicationInitializer
看起來幾乎一模一樣。而且都被不同的ServletContainerInitializer接口類使用。但二者使用目的不同,初始化的目標不一樣。Spring Web
中,WebApplicationInitializer
也是針對Servlet 3.0+
環境,設計用於程序化配置ServletContext
,跟傳統的web.xml
相對或者配合使用,WebApplicationInitializer
實現類會被SpringServletContainerInitializer
標識,從而被tomcat自動檢測和調用。
2.5 第二層次的ServletContextInitializer工具們都在哪,怎麼用?
獨特this::selfInitialize所產生的第一層次工具,把根spring容器記入ServletContext的KV項,又從容器中找二層工具。哪麼都有哪些二層工具呢?怎麼用呢?
springboot是自動配置機制的,重點關注這三個:
1.EmbeddedServletContainerAutoConfiguration
注入容器bean,根據當前包掃描,默認tomcat
2.DispatcherServletAutoConfiguration
默認dispatchServlet配置
3.WebMvcAutoConfiguration
看看package org.springframework.boot.autoconfigure.web.servlet中的:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
...
//DispatcherServlet,不再介紹了。
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
...
return dispatcherServlet;
}
//DispatcherServletRegistrationBean
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
...
}
//----------------------------------------------------------
//DispatcherServletRegistrationBean的基父類中實現了ServletContextInitializer,所以它是二層工具。本類主要設置path。
public class DispatcherServletRegistrationBean extends ServletRegistrationBean<DispatcherServlet>
public class ServletRegistrationBean<T extends Servlet> extends DynamicRegistrationBean<ServletRegistration.Dynamic> {
public abstract class DynamicRegistrationBean<D extends Registration.Dynamic> extends RegistrationBean {
public abstract class RegistrationBean implements ServletContextInitializer, Ordered {
//本質還是ServletContextInitializer,onStartup方法被調用。
這些類的調用如下:
onStartup方法-->
register(description, servletContext);-->
D registration = addRegistration(description, servletContext);-->
servletContext.addServlet(name, this.servlet);
DispatcherServlet是實現implements ApplicationContextAware接口的,當然就會自動感知spring容器。它持有的就是根容器。不再是之前說的mvc子容器了。
2.6 springmvc容器沒有了?
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
沒看到SpringMvc容器的代碼,以下文章都提到了一個容器的問題:
https://segmentfault.com/a/1190000017327469【深入Spring Boot:Spring Context的繼承關係和影響】
https://www.jianshu.com/p/6a869eabfe78【SpringMvc在SpringBoot環境和Web環境中上下文的關係】
在Web環境中是由Spring和SpringMvc兩個容器組成的,在SpringBoot環境中只有一個容器AnnotationConfigEmbeddedWebApplicationContext。
2.2.0.BUILD-SNAPSHOT中的根容器叫AnnotationConfigServletWebServerApplicationContext,1.5.2中還是叫AnnotationConfigEmbeddedWebApplicationContext,看到都有WebApplicationContext,是爲web應用而生吧。自動配置就只用這麼一個容器了。
2.7 引申(多個容器)
默認是一個容器,也可以搞多個SpringMvc容器的,當你需要:
Spring Boot with multiple DispatcherServlet, each having their own @Controllers時。
https://stackoverflow.com/questions/30670327
@SpringBootApplication(exclude=DispatcherServletAutoConfiguration.class)
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
@Bean
public ServletRegistrationBean foo() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(FooConfig.class);
dispatcherServlet.setApplicationContext(applicationContext);
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(dispatcherServlet, "/foo/*");
servletRegistrationBean.setName("foo");
return servletRegistrationBean;
}
@Bean
public ServletRegistrationBean bar() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(BarConfig.class);
dispatcherServlet.setApplicationContext(applicationContext);
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(dispatcherServlet, "/bar/*");
servletRegistrationBean.setName("bar");
return servletRegistrationBean;
}
}
上面兩個SpringMvc容器,不同的配置文件,不同的DispatcherServlet,不同的mapping地址,不同的名字。不過在DispatcherServlet的init()時,都會找到共用的根容器的。注意要排除DispatcherServletAutoConfiguration,不能去自動配置了。
2.8 總結
目標
在產生根spring容器的同時,產生一個嵌入式的tomcat對象,把初始化ServletContext的所有兩個層次的ServletContextInitializer工具們傳進去。一層工具把根容器註冊好,二層工具註冊dispatcherServlet等東東。
實現
ServletContextInitializer工具們被傳遞到tomcat內部比較深的地方,由監聽Webserver啓動的ServletContainerInitializer的實現類TomcatStarter所持有。
這過程中要判斷spring容器類型是servlet,再判斷用Tomcat,然後真正工廠來實現化Tomcat及它內部的一些類,還要擴展內部的一個類,通過它才能把帶着工具的TomcatStarter放進去。等Tomcat真正啓動了,TomcatStarter用工具處理給它的ServletContext。
3. 回顧
前面分析了一通,但還需要從總體上分析一下作者的設計思路。
3.1 對象與生命週期
主要對象:
ServletContext:並非一個servlet對應一個ServletContext。而是一個web應用(webApp)對應一個ServletContext實例,這個實例是應用部署啓動後,servlet容器爲應用創建的。ServletContext實例包含了所有servlet共享的資源信息。通過提供一組方法給servlet使用,用來和servlet容器通訊,比如獲取文件的MIME類型、分發請求、記錄日誌等。
ServletContainer:可以當作提供servlet功能的WebServer。
生命週期處理:
WebServer啓動spring 情況下,調用ServletContainerInitializer的啓動方法onStartup,給了外部一個配置這個web應用對應的ServletContext的機會。
- 當onStartup時,產生了根容器,但具體讓一個ContextLoaderListener去監聽ServletContext的初始化完成操作時候寫入根容器到ROOT的KV值。等於是產生了根容器,但要等時機。(讓我配置它,我先準備好材料,等contextInitialized這個時機點配置上去)
- onStartup時,接着產生dispatcherServlet和它的容器,dispatcherServlet帶着它的容器並註冊到ServletContext中去。dispatcherServlet也有一個init()機會,這時候去找根容器,作爲自己帶的容器的父容器。(我先把材料放進去,你用它的時候初始化一下它就可以用了)
說明ServletContext初始化完成,要早於dispatcherServlet的init();。初始化應該晚於放servlet進去,加材料只是配置。一般設計一個類的生命週期,參考spring,有配置,再初始化及正式運行,最後銷燬,重要的生命節點要通知監聽者。簡單的說就是先裝配好,再監聽,適當時候再進一步處理。
疑問:
- 爲何不在裝配時設置KV?我們知道ServletContext加KV值實際上就是webApp的全局共享變量,隨時可以加,所以這操作都不能算在初始化中吧。
- 但tomcat啓動,通知一個ServletContainerInitializer實現類來處理ServletContext。既然是配置context,爲何不叫ServletContextInitializer呢?也許啓動給不同的webApp都同時進行ServletContext設置,多個context就不能叫ServletContextInitializer了,或者加個s,或者按更上層對象來命名吧。
在springboot中,它爲Tomcat準備了一堆ServletContextInitializer對象,這是spring裏用來處理context的接口,很明顯這些要是處理一個WebApp的ServletContext的,只關心這個。
啓動Tomcat前,給它要求生成了Connector/getHost等內部對象。還生成一個TomcatEmbeddedContext對象,host.addChild(context);這句加入到host中,然後通過它爲中介,把一個ServletContainerInitializer接口對象TomcatStarter設置進去,這是前面提到過的接口,都是監控Webserver啓動的。Tomcat啓動了就通知到TomcatStarter了,並給它一個真正的context來處理,TomcatStarter早就持有一個多層次的ServletContextInitializer對象,就可以兩個層次處理ServletContext了。
3.2 設計思考
兩種情況下,真正設置tomcat的ServletContext都是從ServletContainerInitializer的被調用onStartup開始的。
前者由Tomcat啓動從文件中找的對象,再通過實現了WebApplicationInitializer的AbstractAnnotationConfigDispatcherServletInitializer處理;它的接口名字與類名字感覺差別比較大,給tomcat調用是爲了初始化WebApplication的,而在這個過程中又要產生Servlet並配置進ServletContext,名字如果叫WebApplication2ServletContextInitializer更準確吧。設置KV值還要通過監聽來等個機會進行不知道爲啥這麻煩?不過KV一定要放在ServletContext,被可能的多個MVC容器共享使用。
後者由springboot啓動tomcat並設置ServletContainerInitializer實現類進去,再由tomcat反過來調用ServletContainerInitializer實現類的onStartup()方法開始真正配置ServletContext。後面處理ServletContext的多個類的接口統一叫ServletContextInitializer名字很準確,功能專一。也不再監聽去等ServletContext的初始化後的機會設置KV值了,其中一個ServletContextInitializer直接一步就設置好了,其它的ServletContextInitializer都從總容器中拿。複雜的是onStartup前面的過程。
兩種情況下,dispatcherServlet一旦被註冊進了ServletContext,就由tomcat接手了三個生命週期。不同的是,前者dispatcherServlet帶着新生成的mvc容器,並在init時找到父容器。後者因爲aware了根容器了,就帶着根容器進去的,父容器還是自己了。可能因爲有了springboot自動配置機制的便利吧,一個容器就很好按條件自動配置內部的Bean了,之前爲啥不搞一個容器呢?擔心mvc容器特殊Bean多,可以專門繼承一個用啊?