spring与tomcat的关系逆袭前后的设计分析

https://blog.csdn.net/herriman/article/details/103646985

 

简介
​ 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());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
出现的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);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2.3 产生WebServer工厂的过程
​ 仅会生成特定的WebServer,并产生一个初始化工具(ServletContextInitializer实现类)给它。有点像新成立一个分公司,却只派了一个业务指导过去。

​ 看工具接口名字就知道是用来初始化ServletContext的。ServletContext的层次在Tomcat中并不高,上面还有container,且看分析。

//1。createWebServer();中主要有这两句,用一个工厂来生成WebServer,工厂包含mock的共有4种。同时把getSelfInitializer得到的一个实现了ServletContextInitializer的初始化工具给它。
    ServletWebServerFactory factory = getWebServerFactory();
    this.webServer = factory.getWebServer(getSelfInitializer());
1
2
3
//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是一个接口,不过是其内部调用的,作用不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
​ 上面产生一个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也都可以工作了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

该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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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 {
1
2
3
4
5
6
7
8
没看到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;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
​ 上面两个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多,可以专门继承一个用啊?
 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值