好吧,标题很三俗。为了对得起踩关键字进来的同学,贴图一张!

另有传送门一扇。
Filter在MVC开发中十分普遍。想要做登录验证?想要做统一的编码转换?想要做日志审计?统统可以,只需依葫芦画瓢,实现Filter接口。
public interface Filter {
public void init(FilterConfig filterConfig) throws ServletException;
public void doFilter ( ServletRequest request, ServletResponse response, FilterChain chain ) ;
public void destroy();
}
init和destroy一般是摆设,doFilter里面才是我们关心的勾当。看各路code snippets总是这样的写法。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
// handle request or response, or something else
...
// invoke filterChain at last
filterChain.doFilter(request, response);
}
对于这种编程模型,总是有些疑问:
问题一:web.xml中定义的Filter,它是按照什么样的顺序来执行?
问题二:为什么每个Filter,总是在最后写上filterChain.doFilter,这样就神奇地交给了下一个Filter?
[先看问题一]
不看tomcat或者jetty等容器源码的情况下,准备通过实验获得答案。
Java EE项目的创建、打包、部署是个烦人的活,这里借助maven来省时省力。
实验的环境是maven-2.2.1和tomcat-6.0.29。
1.创建webapp项目
mvn archetype:create -DartifactId=design-pattern -DgroupId=info.iloveolive -DarchetypeArtifactId=maven-archetype-webapp
这会得到一个最简单的webapp工程结构,但还缺少servlet规范相关的jar包。
在总pom.xml中加上
<![CDATA[
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-servlet_2.5_spec</artifactId>
<version>1.2</version>
<scope>provided</scope>
</dependency>
]]>
Geronimo是对servlet规范的apache实现。
作为一个完美主义者,加上了<scope>provided</scope>,表示不将Geronimo打包进最后的war包。因为运行war包的容器tomcat肯定含有servlet jar包。
运行mvn eclipse:eclipse,导入eclipse。
2.实现两个简单的Filter,主要是在filterChain.doFilter()前后打上调用日志。
package info.iloveolive.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
*
* @author atom
* @version $Id: FirstFilter.java, v 0.1 2011-11-23 下午7:49:24 atom Exp $
*/
public class FilterOne implements Filter {
/**
* @see javax.servlet.Filter#destroy()
*/
@Override
public void destroy() {
}
/**
* @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
*/
@Override
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
throws IOException,
ServletException {
System.out.println("before invoke FilterOne's doFilter()");
arg2.doFilter(arg0, arg1);
System.out.println("after invoke FilterOne's doFilter()");
}
/**
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
FilterTwo类似,略。
3.写一个简单的jsp,仅仅为了接收请求,流通Filter。
<![CDATA[
<html>
<head><title>Filter Trace</title></head>
<body>
<h2>Filters are working ...</h2>
<p>now cat $CATALINA_HOME/logs/catalina.out to find how filters invoked</p>
</body>
</html>
]]>
4.web.xml配置。
<![CDATA[
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<filter>
<filter-name>filterOne</filter-name>
<filter-class>info.iloveolive.filter.FilterOne</filter-class>
</filter>
<filter>
<filter-name>filterTwo</filter-name>
<filter-class>info.iloveolive.filter.FilterTwo</filter-class>
</filter>
<filter-mapping>
<filter-name>filterTwo</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>filterOne</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>filterTraceServlet</servlet-name>
<jsp-file>/trace.jsp</jsp-file>
</servlet>
<servlet-mapping>
<servlet-name>filterTraceServlet</servlet-name>
<url-pattern>/trace</url-pattern>
</servlet-mapping>
</web-app>
]]>
在filter-mapping中,故意先写上filterTwo,再写filterOne。
5.打成war包,部署到tomcat。
这里展示当代3种青年的做法。
普通青年:
运行mvn war:war,在simple-web/target目录下得到simple-web.war,将它复制到tomcat/webapps目录下人肉部署。
文艺青年:
优雅地在pom.xml中添加上tomcat-maven-plugin插件
<![CDATA[
<properties>
<finalName>simple-web</finalName>
</properties>
<build>
<finalName>${finalName}</finalName>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>tomcat-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
]]>
运行mvn tomcat:run,完成打包、部署、启动tomcat。
二逼青年:
同样在pom.xml中添加上tomcat-maven-plugin插件
<![CDATA[
<properties>
<finalName>simple-web</finalName>
</properties>
<build>
<finalName>${finalName}</finalName>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>tomcat-maven-plugin</artifactId>
<configuration>
<url>http://localhost:8080/manager</url>
<username>atom</username>
<password>power</password>
<path>/${finalName}</path>
</configuration>
</plugin>
</plugins>
</build>
]]>
额外多出来的配置部分configuration,让maven能够远程操纵tomcat。atom是具有manager角色的一个用户实例。
在tomcat/conf/tomcat-users.xml中,要有如下配置。
<![CDATA[
<tomcat-users>
<role rolename="manager"/>
<user username="atom" password="power" roles="manager"/>
</tomcat-users>
]]>
启动tomcat后(一定要先启动),二逼青年通过运行mvn tomcat:redeploy完成部署。
6.浏览器输入http://localhost:8080/simple-web/trace,看日志结果

可见,<filter-mapping>的顺序,决定filter调用顺序。
再看问题二
Filter实际上是职责链设计模式(Chain of Responsibility)的修改版。
原始的职责链中,包含两个重要的元素,请求(Request)和处理器(Handler)。
当客户端提交一个请求时,请求沿着链传递。处理器面对一个请求,有两种选择:
- 如果是它能够处理的类型,则进行处理,处理完毕后终止该链路传递,该请求的生命周期结束;
- 如果不是它能够处理的类型,处理器“推卸责任”,把它丢给下一个处理器,请求会继续在链上传递。
同样创建一个标准的maven结构工程,来进行实验。
1.首先定义“请求”的接口,只简单定义了一个方法。
package info.iloveolive.cor.request;
/**
* 职责链的请求
* @author atom
* @version $Id: Request.java, v 0.1 Nov 27, 2011 9:18:09 PM atom Exp $
*/
public interface Request {
/**
* 返回请求类型
* @return
*/
String getRequestType();
}
实现一个具体的请求类型:“格式化”请求。
package info.iloveolive.cor.request;
/**
* "格式化"请求
* @author atom
* @version $Id: FormatRequest.java, v 0.1 Nov 27, 2011 9:27:25 PM atom Exp $
*/
public class FormatRequest implements Request {
/**
* @return
* @see info.iloveolive.cor.request.Request#getRequestType()
*/
@Override
public String getRequestType() {
return "request of format";
}
}
同理,实现另外两个请求,PrintRequest和SaveRequest。
2.定义处理器接口。
package info.iloveolive.cor.handler;
import info.iloveolive.cor.request.Request;
/**
* 职责链的处理器
* @author atom
* @version $Id: Handler.java, v 0.1 Nov 27, 2011 9:28:34 PM atom Exp $
*/
public interface Handler {
/**
* 处理一个请求,简单起见,不设置返回值
* @param request
*/
void handle(Request request);
}
实现“格式化”处理器
package info.iloveolive.cor.handler;
import info.iloveolive.cor.request.FormatRequest;
import info.iloveolive.cor.request.Request;
/**
*
* @author atom
* @version $Id: FormatHandler.java, v 0.1 Nov 27, 2011 9:30:34 PM atom Exp $
*/
public class FormatHandler implements Handler {
Handler succeesor;
public FormatHandler(Handler succeesor) {
super();
this.succeesor = succeesor;
}
/**
* @param request
* @see info.iloveolive.cor.handler.Handler#handle(info.iloveolive.cor.request.Request)
*/
@Override
public void handle(Request request) {
if(request instanceof FormatRequest) { // 是否是格式化请求
System.out.println(this.getClass().getSimpleName() + " handle " + request.getRequestType());
} else {
if(succeesor != null) { // 转交给后续者处理
System.out.println("can not handle, drop it to succeesor");
succeesor.handle(request);
}
}
}
}
FormatHandler中有个succeesor字段,指向下一个处理器。
指定的过程,出于简单起见,放在构造函数里面,比较2b。但这不影响职责链设计模式的理解。
同理实现另外两个处理器,PrintHandler和SaveHandler。
3.前期准备完成后,客户端终于登场。
装配顺序是任意的。
package info.iloveolive.cor.client;
import info.iloveolive.cor.handler.FormatHandler;
import info.iloveolive.cor.handler.Handler;
import info.iloveolive.cor.handler.PrintHandler;
import info.iloveolive.cor.handler.SaveHandler;
import info.iloveolive.cor.request.FormatRequest;
import info.iloveolive.cor.request.PrintRequest;
import info.iloveolive.cor.request.SaveRequest;
/**
*
* @author atom
* @version $Id: ClientMain.java, v 0.1 Nov 27, 2011 9:41:59 PM atom Exp $
*/
public class ClientMain {
public static void main(String[] args) {
/**
* 初始化三个处理器
* 通过初始化函数,装配了一个处理链路
* 格式化 -> 保存 -> 打印
*/
Handler handler3 = new PrintHandler(null);
Handler handler2 = new SaveHandler(handler3);
Handler handler1 = new FormatHandler(handler2);
// 职责链处理第1个请求
System.out.println("[ 1st request start ]");
handler1.handle(new SaveRequest());
System.out.println("[ 1st request end ]");
// 职责链处理第2个请求
System.out.println("[ 2nd request start ]");
handler1.handle(new FormatRequest());
System.out.println("[ 2nd request end ]");
// 职责链处理第3个请求
System.out.println("[ 3rd request start ]");
handler1.handle(new PrintRequest());
System.out.println("[ 3rd request end ]");
}
}
来看看最终运行的结果。
[ 1st request start ]
can not handle, drop it to succeesor
SaveHandler handle request of save
[ 1st request end ]
[ 2nd request start ]
FormatHandler handle request of format
[ 2nd request end ]
[ 3rd request start ]
can not handle, drop it to succeesor
can not handle, drop it to succeesor
PrintHandler handle request of print
[ 3rd request end ]
可见,职责链设计模式就是链式处理,推卸责任。它体现了单一职责原则,每个处理器只负责关心的请求处理。同时也体现了开放-封闭原则,想要增加对新请求的处理,只需要增加新的处理器即可。
客户端和处理链都没有对方的明确信息,链中的对象也不知道链的结构。职责链简化了对象的相互衔接,它们之间的耦合只在于保持一个指向后继者的引用。通过设置后继者,处理器能处理就处理,不行就往后推卸责任。
至此,你心生疑惑,纯粹的职责链模式,和web下的Filter开发有些不同。
是的,纯粹的职责链模式中,链是抽象存在的,并没有一个专门的对象结构来表示链。
以tomcat为例,改进的职责链中,它切断了前后处理器间的引用耦合,引入处理器持有者,来统一管理处理器。这个持有者,就是链。
一个web请求,发送到tomcat,和Filter处理有关的邂逅,是这样开始的:
// Get the FilterChain Here
ApplicationFilterFactory factory = ApplicationFilterFactory.getInstance();
ApplicationFilterChain filterChain = factory.createFilterChain(request,wrapper,servlet);
// 开始职责链生命周期的处理
filterChain.doFilter(request, response);
ApplicationFilterChain中和职责链紧密相关的3个字段。
final class ApplicationFilterChain implements FilterChain, CometFilterChain {
/**
* Filters.
*/
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
/**
* The int which is used to maintain the current position
* in the filter chain.
*/
private int pos = 0;
、
/**
* The int which gives the current number of filters in the chain.
*/
private int n = 0;
}
ApplicationFilterConfig数组含有FilterConfig,通过FilterConfig很容易得到Filter,所以它是处理器持有者。
n表示数组中长度,即含有多少个处理器。
pos表示当前处于“激活”状态的处理器序号。
每次执行filterChain.doFilter(request, response)方法时,ApplicationFilterChain会这样做。
// Call the next filter if there is one
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = null;
try {
filter = filterConfig.getFilter();
...
filter.doFilter(request, response, this);
} catch (IOException e) {
}
}
它记录下当前链中的处理器位置,依次取出作为激活处理器。处理器执行filter.doFilter方法时,巧妙地把ApplicationFilterChain的指针传了进去(this部分)。这样,在Filter实现代码中,一旦自身有关的处理部分完工后,就可以通过filterChain.doFilter,把调度权再转交回ApplicationFilterChain,它根据pos标记,轻松选出下一个处理器,继续处理请求。
这种改进模式比较适合框架开发,链路的装配交给框架,客户端无需操心,只管定义处理器,而且每个处理器都有机会沾手请求。
体现在编程界面上,框架释出jar包,客户端配置好xml让框架读取。
在侧面上也可以看出,它的执行效率比较低,毕竟伴随大量的出栈入栈操作,所以才说,不要随意添加Filter。
参考链接
1.CoR 模式 (一种)
http://www.iteye.com/topic/411182
2.Design Pattern: Chain of Responsibility 模式
http://caterpillar.onlyfun.net/Gossip/DesignPattern/ChainofResponsibility.htm
3.filter的执行顺序
http://hi.baidu.com/good_pb/blog/item/e958e20f89b0b5236059f30c.html
4.代码下载