源码解析tomcat回调springmvc servlet init方法的原理

我们知道Springmvc启动时会创建并注册servlet,然后tomcat会扫描并回调servlet的init方法。

那么tomcat是如何回调springmvc生成的servlet的init方法的呢,具体细节和时机呢。

下面首先给出结论:Tomcat是调用自身的StandWrapper的initServlet方法,最终回调到springmvc的HttpServletBean的init方法=》 initServletBean方法

这里需要搞清楚两件事:

1. Springmvc生成的DispatcherServlet如何加载到Tomcat上下文

2. Tomcat如何回调DispatcherServlet的init方法

备注:本文讲解的是springmvc xml零配置启动方式,也就是通过注解/编程方式实现springmvc启动,而不是传统的xml文件配置方式。

根据Servlet 3.0规范的要求: springmvc启动是会查找META-INF/services/路径下ServletContainerInitializer接口的实现类: SpringServletContainerInitializer

而SpringServletContainerInitializer执行的时候又会扫描所有WebApplicationInitializer

的实现类或子类 ,然后在SpringServletContainerInitializer的onStartup方法中执行

WebApplicationInitializer的实现类或子类的onStartup方法:

所以springmvc真正的启动入口是:WebApplicationInitializer的onStartup方法:

 选择onStartup接口的实现方法为: AbstractDispatcherServletInitializer

 继续跟进里面的registerDispatcherServlet方法:

 备注:super.onStartup方法是创建spingmvc的根容器也就是父容器,该容器提供dipatcherservlet运行所需要的service,Dao等Bean对象

 

 registerDispatcherServlet方法里面:

   a. 创建了springmvc子容器:WebApplicationContext

   b. 使用WebApplicationContext创建dispatcherServelet

   c.  通过addServlet方法将dispatcherServelet添加到tomcat上下文

 

 以上完成了Springmvc生成的DispatcherServlet加载到Tomcat上下文这件事

调用链路:

WebApplicationInitializer:onStartup =》AbstractDispatcherServletInitializer:onStartup:registerDispatcherServlet =》registerDispatcherServlet:createDispatcherServlet =》FrameworkServlet: init => HttpServletBean: init

深入拓展:addServlet方法具体是如何将servlet添加到tomcat上下文的?具体机制是什么?

跟进servletContext.addServlet方法:

 这里进入的addServlet接口实现是ApplicationContextFade,这里涉及到外观设计模式

外观(Facade)模式:又称为门面模式,它定义了一个高层接口(即为多个子系统提供统一的门面),客户端与通过 门面 与子系统通信,使这些子系统更加容易被访问。

外部客户端不关心内部子系统的具体细节,这样大大降低应用程序的复杂度,提高了程序的可维护性。

 简单来说就是:解耦,对外提供统一的接口,屏蔽内部的实现细节,客户端只与 门面 通信,通过 门面 来隐藏系统的复杂性,降低耦合度。

然后继续跟进ApplicationContextFade的addServlet方法

 此时来到了外观模式背后真正的实现者ApplicationContext,接着继续跟进它的addServlet方法: 

 这里发现:tomcat在添加servlet之前会首先查询一下:这个servlet对应的wrapper是否存在,

如果不存在,才会创建一个wrapper对象,并将servlet的名字传进去,然后通过addChild方法把这个新的wrapper添加到tomcat容器

于是继续跟进addChild方法:

 发现addChild方法会调用父类ContainerBase的addChild方法:

 发现addChild=>addChildInternal=>最终走到了children.put方法

这里很像是将servlet添加到一个Map当中

然后看一下这个children的定义,果然是一个HashMap,key就是servlet的名称

 /**
     * The child Containers belonging to this Container, keyed by name.
     */
    protected final HashMap<String, Container> children = new HashMap<>();

Tomcat的addServlet方法:实际就是将被添加的servlet对象包装成wrapper,然后存储到HashMap,key是原始servlet的名称,value是这个wrapper的实例

至此:Tomcat的addServlet方法就分析完毕了

备注:addChild(Container child)方法中入参child实际就是前面的Wrapper, 之所以可以归结到Container类,是因为Container是它的父接口,参考下面的类图继承关系就清楚了:

而下面一个问题又是如何完成的呢:

Tomcat如何回调DispatcherServlet的init方法

要搞清楚这个问题,需要参考我写的另外一篇文章:

从tomcat源码分析它的启动流程是如何初始化servlet的

tomcat源码中ContainerBase的startInternal方法里面有个findChildren方法

 

 

 findChildren是找到Engine底下所有的嵌套容器:包括Host,Context, Wrapper等,因为Children是一个HashMap,key是容器名,而它的value就是容器实例。

而StardardWrapper就是Wrapper的实现类,它定义的就是一个servlet容器:

根据之前的分析tomcat的addServlet方法会将dispatcherServlet所在的wrapper添加到children这个hashMap中,那么findChildren方法也必然能找到之前的那个dispatcherServlet所在的wrapper。

继续跟进findChildren后面的代码,可以发现:这里会遍历四层嵌套容器,并通过线程池启动他们的start方法    :

 // Start our child containers, if any
        Container children[] = findChildren();
        List<Future<Void>> results = new ArrayList<>();
        for (Container child : children) {
            // 遍历四层嵌套容器,并通过线程池启动他们的start方法
            results.add(startStopExecutor.submit(new StartChild(child)));

   }

可以看到StartChild是一个Callable函数式接口的实现类,构造器参数就是Container实例,并重写了call方法,在该方法里面执行container容器的start方法。

而Wrapper是由它父级容器Context启动的,所以从加载的因果关系来看:我们应该先进入到Context的实现类StandardContext的start方法:

它的start方法执行会按照以下的模板方法套路:

自身start方法  =》 Lifecycle的start方法=》LifecycleBase的start方法=》  StandardContext的 startInternal方法

所以我们跳过这些重复的模板方法,直接进入StandardContext的 startInternal方法
 

StandardContext 作为wrapper的父容器会通过 startInternal方法层层调用=》loadOnStartup方法=》wrapper.load方法=》initServlet方法

最终调用到了initServlet方法,也就是调用到了Servlet的init方法:

 但是Servlet(javax.servlet.Servlet)只是一个接口,根据下面的类图结构:

因为dispatcherServlet的类型实际是FrameworkSevlet, 调用init方法时最终会调用到它的父类HttpServletBean的init方法,换句话说tomcat最终应该调用到HttpServletBean的init方法

 

而 HttpServletBean的init方法会最终调用到initServletBean方法

以下 断点调试也证明了:tomcat的StandardWrapper通过调用自身的initServlet方法,最终调用到了HttpServletBean的init方法

 

到此已经完整分析了:Springmvc启动时tomcat如何回调其Servlet init方法的原理

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值