spring與tomcat的關係逆襲前後的設計分析

簡介

​ 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了。算是逆襲吧!

包含
包含
包含
TOMCAT
應用一
應用二
應用三
暴露
局部暴露
暴露
核心業務應用
協議服務
協議二服務
協議三服務

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多,可以專門繼承一個用啊?

發佈了38 篇原創文章 · 獲贊 6 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章