tomcat启动加载Servlet源码浅析

总体架构

我们知道tomcat不只是web服务器,它还提供动态地内容是一个Servlet容器。tomcat的总体架构如图所示:

Server表示一个服务器实例,由Connector和Container组成Service服务。Connector用于接收请求,Container表示一个容器它主要包含四个子容器:

1)engine

2)host

3)context

4)wrapper

本文关注context和wrapper部分,其中context表示一个webapp项目,wrapper包装着一个servlet,一个context下将会有多个servlet实例对外提供服务。当Connector接收到请求以后,将会根据contextPath找到对应的context,再找到servlet实例并调用其service方法,传入ServletRequest和ServletResponse进行业务处理。

启动流程图

下面,我们看一看tomcat的启动流程图

启动流程主要分为两块内容:

1)初始化:解析Server.xml文件,生成Server等实例,并对Connector和Container等进行初始化操作。

2)启动:调用start()方法启动Connector和Container等。其中Container部分,context启动的时候会解析web.xml,并触发contextConfig这个监听器,从而创建wrapper容器,初始化Servlet实例,如果该Servlet标识了loadOnStartup那么在start()过程中将会调用其init()方法进行Servlet初始化。

从tomcat的启动流程图,我们可以看到它和我们日常编写webapp应用接入点就在于创建Servlet的容器,所以我们的业务代码就是基于Servlet编写,使用tomcat则无需关心Servlet如何对外提供服务,简化了开发流程。

源码解析

脚本

我们以启动脚本作为切入口,打开startup.sh文件,我们看到

该脚本执行了catalina.sh脚本并传入了start作为入参

打开catalina.sh我们看到

该脚本运行了Bootstrap.jar,并传入start入参

初始化

打开bootstrap类,首先看到其static静态块做了什么

静态块主要是获取了catalina_home和catalina_base两个文件目录对象,并将两个目录设置为了全局属性。catalina_home表示的是bin和lib的目录,catalina_base表示conf的目录,如果多个tomcat实例运行,那么每个tomcat实例都会有一个catalina_base,但是可以共享catalina_home目录。

再进入main方法看看,main方法主要包含两部分,第一部分如下

创建了一个Bootstrap启动引导对象,再接着调用了init()方法做初始化操作。我们看看init方法

init方法主要是创建了Catalina实例对象,并设置到当前的Bootstrap实例当中。

main方法的第二部分如下

执行start命令,调用load方法加载Server实例,并调用start()方法进行启动。这跟我们一开始所说的启动流程两步骤是一致的。我们看看Bootstrap的load方法做了什么

其实是调用了catalina的load方法,我们跟进到catalina的load方法看看,catalina.load()做了两件事,第一件事加载server.xml并解析成Server实例

第二件事,调用Server.init()方法初始化实例,Server的init方法由StandardServer的initInternal()方法实现

StandardServer的initInternal方法调用了Service的init方法,如

Service的init方法由StandardService的initInternal实现,如

我们看到,该方法调用了engine(Container)、executor和mapperListener以及connector的init方法。

到这里,我们大体了解了Bootstrap的load方法其实是解析server.xml并加载Server实例,同时调用init方法从而调用Service的init方法,最终把Service下的connector、executor、engine等做初始化。

启动

简单了解了初始化过程,我们再了解一下Bootstrap剩下的start()方法,回到Bootstrap的main方法中,从start的调用出点击进入,我们看到

Bootstrap的start方法,一样是调用了catalina的start方法,我们跟进catalina的start瞅瞅

catalina的start调用了Server的start,启动Server实例,Server的start由StandardServer的startInternal实现,如

跟init一样,server调用了service的start方法,并且service的start由StandardService的startInternal实现,如

到这里我们大概知道了BootStrap的start就是去调用Server的start,再调用service的start,从而调用engine、connector等的start方法。

加载Container

作为servlet容器,我们得关注一下servlet的加载过程。engine作为container的最外层,我们从engine的start方法进入看看,engine的start由StandardEngine的startInternal实现,如:

StandardEngine调用的是父级ContainerBase的startInternal方法

ContainerBase找到engine的子容器,并异步处理,看看StardChild干了啥

子容器也是调用了start方法,到这里我们大概了解了一个调用流程,就是从engine这个最外层Container开始,逐层向子容器递归调用start方法直到启动所有内嵌的Container,那么我们看看Container的UML图

我们看到engine、host、context、wrapper都继承了Container这个接口。而StandardEngine、StandardHost、StandardContext、StandardWrapper分别实现了这些接口。

它们之间的包含关系,如图所示

 所以加载engine这个Container,会递归加载子Container。从engine.start() -> host.start() -> context.start(),我们打开context的实现类StandardContext瞅瞅其startInternal方法

StandardContext做了很多事情,我们关注其中两点

1)加载servlet

2)调用servlet的init方法

加载servlet

在StandardContext的startInternal中,它触发了一个init的时候设置的监听器ContextConfig,这个监听器将进行Context的相关配置处理,如

我们点开contextConfig瞅瞅,事件触发的方法LifecycleEvent

该方法委托给了configureStart做处理

webConfig方法

configureStart有一个核心的方法webConfig

webapp的加载将从这里开始,webConfig方法首先做了web.xml的解析,如图

然后创建了一个ServletContext,设置到StandardContext中,如图

getServletContext创建了一个ApplicationContext,而ApplicationContext则是ServletContext的实现类。

紧接着基于Java的SPI机制,做无web.xml的处理(后面将会调用这些ServletContainerInitializers)

再把web.xml中配置的Servlet加载

我们主要看看configureContext方法,针对Servlet做了什么。

我么看到该方法创建了Wrapper,并包装了Servlet的相关配置信息。最后,wrapper会被包装到StandardContext中,如

到这里,我们大体了解到contextConfig这个监听器主要是做了Servlet初始化的前置处理:1)SPI机制的ServletContainerInitialiers发现;2)加载web.xml加载Servlet配置为wrapper;

创建Servlet实例对象

我们回到StandardContext的startInternal方法里,从FireLifeCycleEvent往下看

ServletContainerInitializers将被循环调用onStartup方法,传入ServletContext,你可以往ServletContext中加入Servlet实例等相关内容。

再往下,通过wrapper容器,加载Servlet,如

loadOnStartup方法首先挑选出要被加载的Servlet

紧接着调用wrapper的load方法

我们看看StandardWrapper的load方法做了啥,首先加载并返回一个Servlet实例,设置到当前Wrapper的Servlet成员变量中。

其次,如果没有初始化过,那么调用initServlet初始化

loadServlet首先反射做了Servlet实例化

然后向下调用了initServlet方法

initServlet方法中,调用了Servlet的init方法做初始化

这里的facade可以看做ServletConfig,它是wrapper的门面,包含了wrapper实例,而wrapper的getServletContext方法则从父级StandardContext中的getServletContext获取Context;至此,web.xml中的Servlet配置被包装成wrapper并存放于StandardContext中。

猜你喜欢

转载自www.cnblogs.com/lay2017/p/11068710.html