首先要搞明白Web容器的架构:
(从容器的作用角度出发,从而解释为什么是这样的架构) 一个Web容器:
- 首先需要的是允许多种通信方式的请求,如HTTP,AJP(之后会讲AJP),所以我们需要一个Connector去负责处理多种协议,然后得到其中的信息并产生Request和Response对象存储这些信息; 举个栗子,如:http://localhost:8080/SecondServlet/HelloController,我们来看server.xml中是如何定义处理这个URL的Connector的,显然第一个是用于处理8080端口上的HTTP请求的Connector;
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
- 然后是对Connector处理得到的请求信息进行相应的处理程序的处理,所以我们要定位到相应的处理程序!如一个请求的URL是:http://localhost:8080/SecondServlet/HelloController localhost是主机名,SecondServlet是Web应用的名字,HelloController是Wrapper(对Servlet进行了一层封装),其在IDE里布局如下图:


即上图的三个域名经过DNS服务器查询返回的是同一个IP地址。 为了解决服务器存在三个“一对多”的现象,Tomcat架构据此进行了分层:

Connector和Engine被Service管理,一个Service对应多个Connector和一个Engine,因为Connector处理的多个协议得到的信息最后可能是通过同一个处理程序处理的;
那Tomcat是如何进行请求的向下传递的呢?答案是使用责任链模式(这个模式在HeadFirst设计模式中被放在了不受欢迎的一列,haha),这里就不展开讲了,这个模式挺简单的~
然后就是Tomcat是如何实现上面的这种流程的呢?(这个只能苦逼的看源码,对我来说还贼难,不过看完之后挺爽的)
- 首先我们来看在Tomcat中对上面四种容器的继承结构类图:

public final void invoke(Request request, Response response) throws IOException, ServletException {
Host host = request.getHost();//从请求对象中得到Host的名字
if (host != null) {
if (request.isAsyncSupported()) {//这里是看该请求是否支持异步
request.setAsyncSupported(host.getPipeline().isAsyncSupported());
}
host.getPipeline().getFirst().invoke(request, response);//然后调用相应Host的第一个invoke方法
}
}
此时请求已经转发到host中,具体代码如下:
public final void invoke(Request request, Response response) throws IOException, ServletException {
Context context = request.getContext();
//省略了非核心逻辑的校验代码
try {
if (!response.isErrorReportRequired())
{
<!--此处调用了请求所对应的Context的invoke方法-->
context.getPipeline().getFirst().invoke(request, response);
}
}
catch (Throwable var10) {
}
}
此时请求已经转发到context中,此时只要对应到相应的Wrapper(Servlet)即可:
wrapper.getPipeline().getFirst().invoke(request, response);
最后来看如何获得我们所需要的真正的处理对象实例!
public final void invoke(Request request, Response response) throws IOException, ServletException {
unavailable = false;
Servlet servlet = null;
StandardWrapper wrapper = (StandardWrapper)this.getContainer();
if (!context.getState().isAvailable()) {
response.sendError(503, sm.getString("standardContext.isUnavailable"));
unavailable = true;//HTTP相应码为503表示当前Web应用正忙
}
}
<!--如果当前Web应用空闲,则可以通过allocate()方法来获取Servlet对象-->
if (!unavailable) {
servlet = wrapper.allocate();
}
下面这段代码很精髓,所以没办法只提取出关键性的逻辑: (this指代的是StandardWrapper)
protected final AtomicInteger countAllocated = new AtomicInteger(0);
protected volatile Servlet instance = null;//用volatile保证多线程环境下的可见性
public Servlet allocate() throws ServletException {
if (this.unloading) {
throw new ServletException(sm.getString("standardWrapper.unloading", new Object[]{this.getName()}));//如果standWrapper已经被卸载,则抛出相应异常
} else {
boolean newInstance = false;
Stack var2;
//下面有点类似单例模式的双重校验实现方式,来保证多线程下只实例化一个Servlet对象
//singleThreadModel表示是单线程模式,默认为false,即默认为多线程模式,该模式下只能实例化一个相同的Servlet对象,该变量在当前servlet实例实现了singleThreadModel时变为true;
if (!this.singleThreadModel) {
if (this.instance == null || !this.instanceInitialized) {
synchronized(this) {
if (this.instance == null) {
try {
if (this.log.isDebugEnabled()) {
this.log.debug("Allocating non-STM instance");
}
//loadServlet用来加载相应的Servlet,下面有讲
this.instance = this.loadServlet();
newInstance = true;//改变这个变量说明此时已经有实例了
if (!this.singleThreadModel) {
//这里又加了一个判断条件,是用于防止刚被加载的实例又被其他线程卸载 this.countAllocated.incrementAndGet();
}
}
}
//如果还没有被初始化,则进行初始化,此处注意没有初始化与没有被加载时不同的,因为没有初始化说明已经有实例了
if (!this.instanceInitialized) {
this.initServlet(this.instance);
}
}
}
//返回已经初始化完成的Servlet实例
return this.instance;
}
}
}
下面是loadServletd的代码分析:(该方法也在StandardWrapper类中,一个StandWrapper类中只有一个Servlet)
//用于存储Wrapper对象中包装的Servlet名字
protected String servletClass = null;
public synchronized Servlet loadServlet() throws ServletException {
if (!this.singleThreadModel && this.instance != null) {
return this.instance;//在多线程模式下,如果实例已经存在了,则直接返回实例
} else {
Servlet servlet;
try {
InstanceManager instanceManager = ((StandardContext)this.getParent()).getInstanceManager();
try {
//instanceManager中有该Warpper对应的Context的ClassLoader对象,然后根据Servletd的名字进行加载实例
servlet = (Servlet)instanceManager.newInstance(this.servletClass);
}
this.initServlet(servlet);
}
return servlet;
}
}
为什么loadServlet()方法中有initServlet(),而allocate()方法中既调用了loadServlet(),又再一次调用了initServlet()呢?
不行了,今天只能分析到此,脑子已经理不过来了,不过具体的流程已经很明朗了,总结一下:
首先Engine从Connector中获取到Request对象和Response对象,然后从Engine转发到对应的Host,然后转发到对应的Context,然后对应的Wrpper负责加载对应的Servlet,其中又涉及到很多多线程下的安全问题,如何保证只加载一个Servlet实例并进行初始化。