我们知道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方法的原理