主要是《深入剖析tomcat》的第五章和第十一章。个人觉得如下3点是关键:
1. pipeline相关概念及其执行valve的顺序;
2. standardwrapper的接受http请求时候的调用序列;
3. standardwrapper基础阀加载servlet的过程(涉及到STM);
顺便问一句,应该每一个servlet程序员都知道filter。但是你知道Filter在tomcat的哪一个地方出现的吗?答案是standardwrapper基础阀会创建Filter链,并调用doFilter()方法
servlet容器的作用
管理servlet资源,并为web客户端填充response对象。
不同级别的容器
Engine:表示整个Catalina的servlet引擎、
Host:表示一个拥有数个上下文的虚拟主机
Context:表示一个Web应用,一个context包含一个或多个wrapper
Wrapper:表示一个独立的servlet
容器都实现了Container接口,继承了ContainerBase抽象类。
管道任务
3个概念:pipeline、valve、valveContext
pipeline包含servlet容器将要调用的任务集合,定义如下:
public interface Pipeline {
public Valve getBasic();
public void setBasic(Valve valve);
public void addValve(Valve valve);
public Valve[] getValves();
public void invoke(Request request, Response response) throws IOException, ServletException;
public void removeValve(Valve valve);
}
valve表示一个具体的执行任务,定义如下:
public interface Valve {
public String getInfo();
public void invoke(Request request, Response response, ValveContext context) throws IOException, ServletException;
}
valveContext按照字面意思就是阀门的上下文,用于遍历valve
public interface ValveContex{
public String getInfo();
public void invokeNext(Request request, Response response) throws IOException, ServletException;
}
valveContext的invokeNext()方法的实现:
public final void invokeNext(Request request, Response response) throws IOException, ServletException {
int subscript = stage; stage = stage + 1;
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"));
}
}
一个阀门的invoke方法可以如下实现:
public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {
//Pass the request and response on to the next valve in our pipeline
valveContext.invokeNext(request, response);
// now perform what this valve is supposed to do ...
}
如果pipeline由valve1、valve2、valve3组成,调用valve1. Invoke()会发生什么?执行顺序是什么样的?假设valveN(N=1,2,3)的invoke()方法实现如下:
valveContext.invokeNext(request, response);
System.out.println(“valve1 invoke!”);
如果注意到了invokeNext()的实现,这层调用类似与下面的图:
类似与递归调用,输出为
“valve3 invoke!”
“valve2 invoke!”
“valve1 invoke!”
顺序恰好于添加的顺序相反。所以这个也可以说明,为什么基础阀看起来是放在最后的,但确实要做装载servlet这样的操作。其实,放在最后的阀门总是第一个被调用。书中的依次添加HeaderLoggerValve、ClientIPLoggerValve依次添加,那么调用顺序为ClientIPLoggerValve、HeaderLoggerValve。注意到书中示例程序的运行结果和添加阀门的顺序。不过话说回来,如果valve的invoke()方法实现为:
public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {
// now perform what this valve is supposed to do ...
valveContext.invokeNext(request, response); //Pass the request and response on to the next valve in our pipeline
}
那么阀门调用的顺序就会和阀门添加的顺序一致(个人感觉这样好理解)
Wrapper接口
Wrapper表示一个独立servlet定义的容器,wrapper继承了Container接口,并且添加了几个方法。包装器的实现类负责管理其下层servlet的生命周期。
包装器接口中重要方法有allocate和load方法。allocate方法负责定位该包装器表示的servlet的实例。Allocate方法必须考虑一个servlet,是否实现了avax.servlet.SingleThreadModel接口。
Wrapper调用序列:
(1) 连接器调用Wrapper的invoke()方法;
(2) Wrapper调用其pipeline的invoke()方法;
(3) Pipeline调用valveContext.invokeNext()方法;
基础阀的invoke实现示例如下:
public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {
SimpleWrapper wrapper = (SimpleWrapper) getContainer();
ServletRequest sreq = request.getRequest();
ServletResponse sres = response.getResponse();
Servlet servlet = null;
HttpServletRequest hreq = null;
if (sreq instanceof HttpServletRequest)
hreq = (HttpServletRequest) sreq;
HttpServletResponse hres = null;
if (sres instanceof HttpServletResponse) hres = (HttpServletResponse) sres;
// Allocate a servlet instance to process this request
try {
servlet = wrapper.allocate();
if (hres!=null && hreq!=null)
{
servlet.service(hreq, hres);
}
else
{
servlet.service(sreq, sres);
}
}
catch (ServletException e) {
}
}
Context应用程序
Context包含多个wrapper,至于用哪一个wrapper,是由映射器mapper决定的。Mapper定义如下:
public interface Mapper {
public Container getContainer();
public void setContainer(Container container);
public String getProtocol();
public void setProtocol(String protocol);
public Container map(Request request, boolean update);
}
map方法返回一个子容器(wrapper)负责来处理请求。map方法有两个参数,一个request对象和一个boolean值。实现将忽略第二个参数。这个方法是从请求对象中获取context路径和使用context的findServletMapping方法获取相关的路径名。如果一个路径名被找到,它使用context的findChild方法获取一个Wrapper的实例。一个mapper的实现:
public Container map(Request request, boolean update) {
// Identify the context-relative URI to be mapped
String contextPath = ((HttpServletRequest) request.getRequest()).getContextPath();
String requestURI = ((HttpRequest) request).getDecodedRequestURI();
String relativeURI = requestURI.substring(contextPath.length());
// Apply the standard request URI mapping rules from
// the specification
Wrapper wrapper = null;
String servletPath = relativeURI;
String pathInfo = null;
String name = context.findServletMapping(relativeURI);
if (name != null)
wrapper = (Wrapper) context.findChild(name);
return (wrapper);
}
容器包含一条管道,容器的invoke方法会调用pipeline的invoke方法。
1. pipeline的invoke方法会调用添加到容器中的阀门的invoke方法,然后调用基本阀门的invoke方法。
2.在一个wrapper中,基础阀负责加载相关的servlet类并对请求作出相应。
3. 在一个包含子容器的Context中,基础阀使用mapper来查找负责处理请求的子容器。如果一个子容器被找到,子容器的invoke方法会被调用,然后返回步骤1。
Context的一个基础阀示例如下:注意最后一句话:wrapper.invoke(request, response);
public void invoke(Request request, Response response,ValveContext valveContext)
throws IOException, ServletException {
// Validate the request and response object types
if (!(request.getRequest() instanceof HttpServletRequest) ||
!(response.getResponse() instanceof HttpServletResponse)) {
return;
}
// Disallow any direct access to resources under WEB-INF or META-INF
HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
String contextPath = hreq.getContextPath();
String requestURI = ((HttpRequest) request).getDecodedRequestURI();
String relativeURI = requestURI.substring(contextPath.length()).toUpperCase();
Context context = (Context) getContainer();
// Select the Wrapper to be used for this Request
Wrapper wrapper = null;
try {
wrapper = (Wrapper) context.map(request, true);
}catch (IllegalArgumentException e) {
badRequest(requestURI, (HttpServletResponse)
response.getResponse());
return;
}
if (wrapper == null) {
notFound(requestURI, (HttpServletResponse) response.getResponse());
return;
}
// Ask this Wrapper to process this Request
response.setContext(context);
wrapper.invoke(request, response);
}
我们主要关注的是一个servlet被调用的时候发生的细节。因此我们需要看StandardWrapper和StandarWrapperValve类
javax.servlet.SingleThreadModel
一个servlet可以实现javax.servlet.SingleThreadModel接口,实现此接口的一个servlet通俗称为SingleThreadModel(STM)的程序组件。
根据Servlet规范,实现此接口的目的是保证servlet一次只能有一个请求。
StandardWrapper实现
一个StandardWrapper对象的主要职责是:加载它表示的servlet,并实例化。该StandardWrapper不会调用servlet的service方法这个任务留给StandardWrapperValve对象,在StandardWrapper实例的基本阀门管道。StandardWrapperValve对象通过调用StandardWrapper的allocate方法获得Servlet实例。在获得Servlet实例之后的StandardWrapperValve调用servlet的service方法。
在servlet第一次被请求的时候,StandardWrapper加载servlet类。它是动态的加载servlet,所以需要知道servlet类的完全限定名称。通过StandardWrapper类的setServletClass方法将servlet的类名传递给StandardWrapper。必须考虑一个servlet是否实现了SingleThreadModel接口。 如果一个servlet没有实现SingleThreadModel接口,StandardWrapper加载该servlet一次,对于以后的请求返回相同的实例即可。
对于一个STM servlet,情况就有所不同了。StandardWrapper必须保证不能同时有两个线程提交STM servlet的service方法。
Servlet instance = ;
if ((servlet implementing SingleThreadModel>) {
synchronized (instance) {
instance.service(request, response);
}
}else{
instance.service(request, response);
}
Allocating the Servlet
StandardWrapperValve的invoke方法调用了包装器的allocate方法来获得一个请求servlet的实例,因此StandardWrapper类必须实现该接口
public javax.servlet.Servlet allocate() throws ServletException;
由于要支持STM servlet,这使得该方法更复杂了一点。实际上,该方法有两部分组成,一部分负责非STM servlet的工作,另一部分负责STM servlet。
第一部分的结构如下
if (!singleThreadModel) {
// returns a non-STM servlet instance
}
布尔变量singleThreadModel负责标志一个servlet是否是STM servlet。
它的初始值是false,loadServlet方法会检测加载的servlet是否是STM的,如果是则将它的值该为true
第二部分处理singleThreadModel为true的情况
synchronized (instancepool) {
// returns an instance of the servlet from the pool
}
对于非STM servlet,StandardWrapper定义一个java.servlet.Servlet类型的实例
private Servlet instance = null;
方法allocate检查该实例是否为null,如果是调用loadServlet方法来加载servlet。然后增加contAllocated整型并返回该实例。
if (!singleThreadModel) {
if (instance == null) {
synchronized (this) {
if (instance == null) {
try {
instance = loadServlet();
}catch (ServletException e) {
throw e;
}
}
}
}
if (!singleThreadModel) {
countAllocated++;
return (instance);
}