我们前面讲解过,Tomcat的Valve其实一共可以分成三类:
第一类:自定义的Valve(就是本文要讲的内容)
第二类:各个核心容器组件的BaseValve(例如之于StandardContext就是StandardContextValve)
第三类:Tomcat内置的一些功能级的Valve,例如AccesslogValve,RemoteAddrValve等
本文主要带你浏览一下ValveBase的主要功能,并为Tomcat自定义一个具有实用意义的Valve;
1.ValveBase

ValveBase是所有Valve的基类,如果你要自己进行自定义Valve的话,那么就需要继承这个类;
LifecycleMBeanBase代表该Valve是MBean监控的,Contained代表其实例化的类是容器对象,通过Tomcat的生命周期Lifecycle进行启停,而Valve实际上就是阀门的接口;
对于Valve接口来说,首先其是一个责任链,所以需要实现setNext,getNext接口

来完成一个责任链中的Valve的传递,因此这两个方法是需要实现的,
关于这两个方法,在ValveBase的基类中已经实现,持有一个next的属性:

另外,一个比较重要的方法invoke,这个就是自定义Valve必须实现的;
我们来看看Tomcat的核心Valve的这个invoke方法干了什么:

作为StandardEngine的BaseValve来说,其主要是通过request找到虚拟主机,然后执行虚拟主机Host中配置的第一个Valve,也就是下图中的箭头所指:

总结一下,ValveBase是自定义Valve需要实现的基类,而你只需要实现其invoke方法即可;
2.自定义Valve
我们自己可以定义一个Valve,我们来分析网络上比较热的一个用户经常自定义的RequestDumperValve
这个RequestDumperValve 主要的作用是打印request,response的dump信息,虽然在Tomcat配置了一个AccessLogValve,但还是不能去自定义化,按照自己的思路随心所欲的进行定制,这个时候,你就可以利用这个Valve的机制,单单实现ValveBase的基类,然后重写invoke方法即可:
public void invoke(Request request, Response response)
throws IOException, ServletException {
Log log = container.getLogger();
// Log pre-service information
log.info("REQUEST URI =" + request.getRequestURI());
......
log.info(" queryString=" + request.getQueryString());
......
log.info("-------------------------------------------------------");
// 调用下一个valve,前面是request,后面是response
getNext().invoke(request, response);
// Log post-service information
log.info("-------------------------------------------------------");
......
log.info(" contentType=" + response.getContentType());
Cookie rcookies[] = response.getCookies();
for (int i = 0; i < rcookies.length; i++) {
log.info(" cookie=" + rcookies[i].getName() + "=" +
rcookies[i].getValue() + "; domain=" +
rcookies[i].getDomain() + "; path=" + rcookies[i].getPath());
}
String rhnames[] = response.getHeaderNames();
for (int i = 0; i < rhnames.length; i++) {
String rhvalues[] = response.getHeaderValues(rhnames[i]);
for (int j = 0; j < rhvalues.length; j++)
log.info(" header=" + rhnames[i] + "=" + rhvalues[j]);
}
log.info(" message=" + response.getMessage());
log.info("========================================================");
}
在这个程序中,我们需要注意的是,红色部分的位置,因为我们的这个Valve一半的功能是要dump request,另外一半是要dump response,所以getNext().invoke(request, response); 这句话的位置是很考究的;
对于这个 RequestDumperValve 的配置,因为在Tomcat的容器核心组件都有Pipeline,所以我们可以配置在任意的环节上,例如Context,Engine上都行。
例如,如果需求是要dump一下某个虚拟主机下所有应用的Valve的话,那么我们就需要进行如下配置:
<Host name="yuantong" ....>
<Valve className="com.yuantong.RequestDumperValve"/>
<Context ...>
</Context>
<Context ....>
</Context>
</Host>
这样监视的内容就是虚拟主机yuantong下面的所有的Context应用。
Valve的配置,只需要你声明你的自定义的Valve的className属性,这个className属性是容器组件必备的,其余的属性可以根据你的Valve进行定制,例如Tomcat的Access_Log_Valve 中就有很多的属性,只需完成其get,set,属性就会自动进行装入;而对于上面定义的RequestDumperValve 是没有什么需要配置的属性罢了;
总结:
自定义Valve需要继承ValveBase,并实现invoke方法,配置在你所希望配置的容器组件下即可;
后续会回到Wrapper这个话题中,对于Valve的核心Valve,和Tomcat自定义的10几个Valve,后面会逐个进行分析,敬请期待!




