Servlet容器是用来处理Servlet资源的。共有四种容器:
- Engine(表示整个Catalina servlet引擎);
- Host(表示包含一个或多个Context容器的虚拟主机);
- Context(表示一个web应用,可包含多个wrapper);
- Wrapper(表示一个独立的servlet)。
四种容器虽然是层层包含的关系,但他们的结构是及其类似的,都继承自Container接口.
他们的另一个最大相同点就是通过引入容器中的管道(pipeline)和阀(valve)的集合实现的,来看看什么事管道任务
管道任务
管道包含了此Servlet容器将要调用的任务。一个阀代表一个具体的执行任务。**在Servlet容器中,必包含一个基础阀。**以及N(N>=0)个额外添加的阀,servlet阀的数量就是N(即不包括基础阀)。
graph LR
阀A-->阀B
阀B-->等等等
等等等-->阀C
之所以说是管道任务,因为阀会一个一个一次被调用执行,基础阀总是最后一个执行,且一定被执行。
一个servlet容器可以有一条管道,当调用容器的invoke方法后(在连接器中讲过,就是connector.getContainer().invoke(request, response);
), 容器就会将处理工作交由管道完成,伪代码如下:
// invoke each valve added to the pipeline
for(int i = 0; i < valves.length; i++) {
valve[i].invoke(...);
}
// then invoke the basic valve
basicValve.invoke(...);
在ContainerBase抽象类中,有如下方法,是几个容器共同使用的:
public void invoke(Request request, Response response)
throws IOException, ServletException {
pipeline.invoke(request, response);
}
pipeline对象就是所说的管道,来看看StandardPipeline(默认都是使用这个管道类)的invoke方法
public void invoke(Request request, Response response)
throws IOException, ServletException {
// Invoke the first Valve in this pipeline for this request
(new StandardPipelineValveContext()).invokeNext(request, response);
}
protected class StandardPipelineValveContext
implements ValveContext {
protected int stage = 0;
public void invokeNext(Request request, Response response)
throws IOException, ServletException {
int subscript = stage;
stage = stage + 1;
// Invoke the requested Valve for the current request thread
if (subscript < valves.length) {
valves[subscript].invoke(request, response, this);
} else if ((subscript == valves.length) && (basic != null)) {
basic.invoke(request, response, this);
} else {
throw new ServletException
(sm.getString("standardPipeline.noValve"));
}
}
}
它会新建一个ValveContext接口的实例并调用其invokeNext()方法,valves[subscript].invoke(request, response, this);
会将自身传给valve对象,valve在执行invoke后需要再次调用其invokeNext(),顺序把阀执行下去。
后续的Tomcat版会替换掉这个内部类,改用链表结构来实现阀的依次执行.
Wrapper
前面提到wrapper是servlet容器的最小单位,而且wrapper负责管理基础servelt类的servelt生命周期,即调用servelt.init()、servlet.destory()等方法。
代码示例
Context
一个Context可包含一个及多个Wrapper.
代码示例
Host
Engine
容器在启动时的加载过程
前面已经知道,Tomcat在启动时会依据server.xml来创建server,server中包含一个service,service中包含engine, engine中包含host,但一般到host就结束了,那我的应用程序(host里的context,context里的wrapper)是在什么时候完成加载的呢。
这就不得不提到HostConfig(类似的EngineConfig/ContextConfig/ServletConfig)了,上面的代码中可以看到有XXXconfig的类,这些就是完成容器配置的观察者,只有这些观察者正确完成了资源的加载,Tomcat才能够正常启动.
当Host容器调用启动方法时,会通知观察者(包括HostConfig)来执行对应的操作。
public class HostConfig
implements LifecycleListener, Runnable {
public void lifecycleEvent(LifecycleEvent event) {
// Identify the host we are associated with
//...
// Process the event that has occurred
if (event.getType().equals(Lifecycle.START_EVENT))
start();
else if (event.getType().equals(Lifecycle.STOP_EVENT))
stop();
}
protected void start() {
if (debug >= 1)
log(sm.getString("hostConfig.start"));
if (host.getAutoDeploy()) {
deployApps();
}
if (isLiveDeploy()) {
threadStart();
}
}
//加载host目录下的描述符、war包、文件夹,都会成为Context
protected void deployApps() {
if (!(host instanceof Deployer))
return;
if (debug >= 1)
log(sm.getString("hostConfig.deploying"));
File appBase = appBase();
if (!appBase.exists() || !appBase.isDirectory())
return;
String files[] = appBase.list();
// 下面这三个操作就是找到应用程序所在的目录,进行加载
// 一个目录就是一个Context
deployDescriptors(appBase, files);
deployWARs(appBase, files);
deployDirectories(appBase, files);
}
}
创建web aplication的代码(在StandardHostDeployer的install函数中)
// 先实例化Conext(一般为StandardContext,
// 然后设置目录,
// 再添加ContextConfig观察者),最后将它加入到Host的子容器集合中
// Install the new web application
Class clazz = Class.forName(host.getContextClass());
Context context = (Context) clazz.newInstance();
context.setPath(contextPath);
context.setDocBase(docBase);
if (context instanceof Lifecycle) {
clazz = Class.forName(host.getConfigClass());
LifecycleListener listener =
(LifecycleListener) clazz.newInstance();
((Lifecycle) context).addLifecycleListener(listener);
}
host.fireContainerEvent(PRE_INSTALL_EVENT, context);
host.addChild(context);
host.fireContainerEvent(INSTALL_EVENT, context);
总结
四种容器每个都有自己的管道,且都是在基本阀中依据某些规则,将对应的请求分配给对应的子容器,层层分配,最后到对应的servlet进行处理.