本系列第四节重点讲讲tomcat中的流水线和阀门。
如果你了解servlet的过滤器那么理解流水线会容易得多,因为他们两个的原理是一样的。一个流水线就像是一个过滤链,上面的阀门就和过滤器一样,在tomcat中阀门可以操作传递给他们的Request和Resopnse对象。实际上这两种工作模式都是基于责任链模式实现的,如果不清楚责任链模式可以去网上查一查。
tomcat中的ValveBase就是表示的流水线上的阀门类,其中最重要的一个方法为:
//核心方法invoke,里面主要是处理request和response的逻辑
//当然还有重要的一步,调用链中的下一个处理节点,典型的责任链
//是由sucessor保存下一个节点来完成的,tomcat中抽象出ValveContext
//存储所有的valve,只需要调用context.invokeNext方法即可
public abstract void invoke(Request request, Response response,
ValveContext context)
throws IOException, ServletException;
ValveContext类似下面:
/**
*valvecontext的核心方法,调用下一个value,tomcat中StandardPipelineValveContext
实现了ValveContext接口
*/
public void invokeNext(Request request, Response response)
throws IOException, ServletException;
StandardPipelineValveContext类为StandardPipeline的内部类,pipeline中的Valve valves[]字段存储了所有的valve。这样StandardPipelineValveContext便可以使用pipeline中的valves字段了。StandardPipelineValveContext类如下:
protected class StandardPipelineValveContext
implements ValveContext {
// ------------------------------------------------- Instance Variables
//表示下一步要调用的valve
protected int stage = 0;
// --------------------------------------------------------- Properties
/**
* Return descriptive information about this ValveContext
* implementation.
*/
public String getInfo() {
return info;
}
// ----------------------------------------------------- Public Methods
/**
* Cause the <code>invoke()</code> method of the next Valve that is
* part of the Pipeline currently being processed (if any) to be
* executed, passing on the specified request and response objects
* plus this <code>ValveContext</code> instance. Exceptions thrown by
* a subsequently executed Valve (or a Filter or Servlet at the
* application level) will be passed on to our caller.
*
* If there are no more Valves to be executed, an appropriate
* ServletException will be thrown by this ValveContext.
*
*/
public void invokeNext(Request request, Response response)
throws IOException, ServletException {
//subscript表示当前要调用的valve
int subscript = stage;
stage = stage + 1;
// Invoke the requested Valve for the current request thread
//从valves集合中取出一个valve调用它的invoke方法
if (subscript < valves.length) {
valves[subscript].invoke(request, response, this);
} else if ((subscript == valves.length) && (basic != null)) {
//最后调用basic的invoke方法
basic.invoke(request, response, this);
} else {
throw new ServletException
(sm.getString("standardPipeline.noValve"));
}
}
}
来看一个非常熟悉的tomcat错误处理页面,错误页面处理都是由tomcat中的ErrorReportValve类负责输出,主要的逻辑如下:
/**
* Invoke the next Valve in the sequence. When the invoke returns, check
* the response state, and output an error report is necessary.
*
* @param request The servlet request to be processed
* @param response The servlet response to be created
* @param context The valve context used to invoke the next valve
* in the current processing pipeline
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet error occurs
*/
public void invoke(Request request, Response response,
ValveContext context)
throws IOException, ServletException {
// Perform the request
context.invokeNext(request, response);
ServletRequest sreq = (ServletRequest) request;
Throwable throwable =
(Throwable) sreq.getAttribute(Globals.EXCEPTION_ATTR);
ServletResponse sresp = (ServletResponse) response;
if (sresp.isCommitted()) {
return;
}
//检查处理过程是否抛出了异常
if (throwable != null) {
// The response is an error
//如果有异常设置错误输出
response.setError();
// Reset the response (if possible)
try {
//重置响应
sresp.reset();
} catch (IllegalStateException e) {
;
}
ServletResponse sresponse = (ServletResponse) response;
if (sresponse instanceof HttpServletResponse)
((HttpServletResponse) sresponse).sendError
(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
response.setSuspended(false);
//报告错误
try {
report(request, response, throwable);
} catch (Throwable tt) {
tt.printStackTrace();
}
}
实际上tomcat中的请求和响应就是被流水线上的一个个valve层层处理的,最后提一点,可以通过service.xml文件配置我们需要的valve类似下面:
<!-- Define the default virtual host
Note: XML Schema validation will not work with Xerces 2.2.
-->
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true"
xmlValidation="false" xmlNamespaceAware="false">
<!-- SingleSignOn valve, share authentication between web applications
Documentation at: /docs/config/valve.html -->
<!--
<Valve className="org.apache.catalina.authenticator.SingleSignOn" />
-->
<!-- Access log processes all example.
Documentation at: /docs/config/valve.html -->
<!--
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log." suffix=".txt" pattern="common" resolveHosts="false"/>
-->
通过配置文件配置valve会被自动的组织到valves[]中,可以想象下是在解析xml文件的过程中完成的addValve动作。