由浅入深讲解责任链模式,理解Tomcat的Filter过滤器
程序员文章站
2022-11-27 16:36:18
本文将从简单的场景引入, 逐步优化, 最后给出具体的责任链设计模式实现. 场景引入 首先我们考虑这样一个场景: 论坛上用户要发帖子, 但是用户的想法是丰富多变的, 他们可能正常地发帖, 可能会在网页中浅入html代码, 可能会使用错误的表情格式, 也可能发送一些敏感信息. 作为论坛的管理员必须对用户 ......
本文将从简单的场景引入, 逐步优化, 最后给出具体的责任链设计模式实现.
场景引入
- 首先我们考虑这样一个场景: 论坛上用户要发帖子, 但是用户的想法是丰富多变的, 他们可能正常地发帖, 可能会在网页中浅入html代码, 可能会使用错误的表情格式, 也可能发送一些敏感信息.
- 作为论坛的管理员必须对用户的帖子进行过滤才能显示出来, 否则论坛就经营不下去了. 现在我们考虑一种最简单处理方式.
public class demo1 { public static void main(string[] args) { string msg = "大家好 :), <script>haha</script> 我要说超级敏感的话";//假设有一条这样的贴子 msgprocessor mp = new msgprocessor(); mp.setmsg(msg);//处理帖子 system.out.println(mp.process()); } } //帖子处理器 class msgprocessor{ private string msg; public string process(){ //对html标签<>进行处理 string str = msg.replace("<", "[").replace(">", "]"); //对敏感字符尽心处理 str = str.replace("敏感", "正常"); //对错误的表情格式进行处理 str = str.replace(":)", "^_^"); return str; } //get() / set() 方法... } //输出结果 大家好 ^_^, [script]haha[/script] 我要说超级正常的话
责任链模型初体现
- 通过上面的代码可以看到帖子处理器会对帖子进行不同的过滤, 我们可以把一种过滤方法对应为一个过滤器, 并且向上抽取出过滤器接口.
public class demo2 { public static void main(string[] args) { string msg = "大家好 :), <script>haha</script> 我要说超级敏感的话"; msgprocessor mp = new msgprocessor(); mp.setmsg(msg); system.out.println(mp.process()); } } class msgprocessor{ private string msg; private filter[] filters = {new htmlfilter(), new sensitivefilter(), new expressionfilter()}; public string process(){ for(filter f : filters){ msg = f.dofilter(msg); } return msg; } public string getmsg() { return msg; } public void setmsg(string msg) { this.msg = msg; } } //过滤器接口 interface filter{ public string dofilter(string s); } //处理html标签 class htmlfilter implements filter{ @override public string dofilter(string s) { return s.replace("<", "[").replace(">", "]"); } } //处理敏感词句 class sensitivefilter implements filter{ @override public string dofilter(string s) { return s.replace("敏感", "正常"); } } //处理表情 class expressionfilter implements filter{ @override public string dofilter(string s) { return s.replace(":)", "^_^"); } }
- 上面的代码已经具备了责任链的模型. 在帖子发送到服务器的过程中, 它将依次经过3个过滤器, 这三个过滤器就构成一条过滤器链.
- 下面我们考虑, 如果我们要在帖子处理过程中加入新的过滤器链条, 加在原链条的末尾或中间, 该怎么办呢?
- 消息经过过滤器链条的过程会得到处理, 我们可以把过滤器链条看成一个过滤器, 让他也实现
filter
接口, 那么就可以在一条过滤链中任意加入其他过滤器和过滤链了. - 下面的代码实现了过滤链
filterchain
, 用过滤链替代原来的msgprocessor
.
public class demo3 { public static void main(string[] args) { string msg = "大家好 :), <script>haha</script> 我要说超级敏感的话";//待处理的帖子 filterchain fc1 = new filterchain();//创建一条过滤器链1 fc1.add(new htmlfilter()) .add(new sensitivefilter());//往过滤器链1中添加过滤器 filterchain fc2 = new filterchain();//创建一条过滤器链2 fc2.add(new expressionfilter());//往过滤器链2中添加过滤器 fc1.add(fc2);//把过滤器链2当作过滤器添加到过滤器链1中,(过滤器链实现了filter接口) msg = fc1.dofilter(msg);//使用过滤器链1对帖子进行过滤 system.out.println(msg); } } class filterchain implements filter{ private list<filter> list = new arraylist<>(); public filterchain add(filter filter){ this.list.add(filter); return this; } @override public string dofilter(string s) { for(filter f : list){ s = f.dofilter(s); } return s; } } class htmlfilter implements filter{ @override public string dofilter(string s) { return s.replace("<", "[").replace(">", "]"); } } class sensitivefilter implements filter{ @override public string dofilter(string s) { return s.replace("敏感", "正常"); } } class expressionfilter implements filter{ @override public string dofilter(string s) { return s.replace(":)", "^_^"); } } interface filter{ public string dofilter(string s); }
更精巧设计, 展现责任链模式
- 在继续优化之前, 我们考虑更现实的需求, 一个请求(发出一个帖子)作为数据报发送给服务器, 服务器除了需要对请求进行过滤外, 还需要给出响应, 并且可能要对响应也进行处理. 如下图所示
- 当一个消息(包含请求体和响应体)发往服务器时, 它将依次经过过滤器1, 2, 3. 而当处理完成后, 封装好响应发出服务器时, 它也将依次经过过滤器3, 2, 1.
- 大家可能会觉得有点像栈结构, 但是像归像, 这一逻辑应该如何实现呢?
- 首先我们可以让过滤器持有过滤器链的引用, 通过调用过滤器链依次执行每个过滤器. 为了能让过滤器依次执行每个过滤器, 过滤器会持有一个
index
序号, 通过序号控制执行顺序. 至于后面对response
的倒序请求, 则通过方法返回实现. 这部分设计纯用文字难以讲清, 请务必看下面的代码和代码后的分析, 配图. - 这个部分是责任链的精髓了, 懂了这部分代码, 看web开发中的过滤器源码就没压力了.
public class demo4 { public static void main(string[] args) { string msg = "大家好 :), <script>haha</script> 我要说超级敏感的话";//以下三行模拟一个请求 request request = new request(); request.setrequeststr(msg); response response = new response();//响应 filterchain fc = new filterchain();//过滤器链 htmlfilter f1 = new htmlfilter();//创建过滤器 sensitivefilter f2 = new sensitivefilter(); expressionfilter f3 = new expressionfilter(); fc.add(f1);//把过滤器添加到过滤器链中 fc.add(f2); fc.add(f3); fc.dofilter(request, response, fc);//直接调用过滤器链的dofilter()方法进行处理 system.out.println(request.getrequeststr()); } } interface filter{ public void dofilter(request request, response response, filterchain fc); } class filterchain implements filter{ private list<filter> list = new arraylist<>(); private int index = 0; public filterchain add(filter filter){ this.list.add(filter); return this; } @override public void dofilter(request request, response response, filterchain fc) { if(index == list.size()){ return;//这里是逆序处理响应的关键, 当index为容器大小时, 证明对request的处理已经完成, 下面进入对response的处理. } filter f = list.get(index);//过滤器链按index的顺序拿到filter index++; f.dofilter(request, response, fc); } } class htmlfilter implements filter{ @override public void dofilter(request request, response response, filterchain fc) { request.setrequeststr(request.getrequeststr().replace("<", "[").replace(">","]")); system.out.println("在htmlfilter中处理request");//先处理request fc.dofilter(request, response, fc);//调用过滤器链的dofilter方法, 让它去执行下一个filter的dofilter方法, 处理response的代码将被挂起 system.out.println("在htmlfilter中处理response"); } } class sensitivefilter implements filter{ @override public void dofilter(request request, response response, filterchain fc) { request.setrequeststr(request.getrequeststr().replace("敏感", "正常")); system.out.println("在sensitivefilter中处理request"); fc.dofilter(request, response, fc); system.out.println("在sensitivefilter中处理response"); } } class expressionfilter implements filter{ @override public void dofilter(request request, response response, filterchain fc) { request.setrequeststr(request.getrequeststr().replace(":)", "^_^")); system.out.println("在expressionfilter中处理request"); fc.dofilter(request, response, fc); system.out.println("在expressionfilter中处理response"); } } class request{ private string requeststr;//真正的request对象中是包含很多信息的, 这里仅用一个字符串作模拟 public string getrequeststr() { return requeststr; } public void setrequeststr(string requeststr) { this.requeststr = requeststr; } } class response{ private string responsestr; public string getresponsestr() { return responsestr; } public void setresponsestr(string responsestr) { this.responsestr = responsestr; } }
- 下面我描述一次整个过程, 你可以根据文字找到相应的代码进行理解.
- 首先我们分别创建一个
request
和response
对象.request
在传入进后端时需要依次被过滤器1, 2, 3进行处理,response
对象在输出时要依次被过滤器3, 2, 1处理. - 创建好请求和响应对象后我们创建过滤器链, 并依次加入过滤器1, 2, 3. 整个处理流程将交给过滤器链决定.
- 接着我们调用过滤器链的
dofilter()
方法对request对象进行处理 - 这时过滤器链中的
index
值为0, 通过index
我们找到第一个过滤器并调用它的dofilter()
方法, 我们观察这段代码
class htmlfilter implements filter{ @override public void dofilter(request request, response response, filterchain fc) { request.setrequeststr(request.getrequeststr().replace("<", "[").replace(">","]")); system.out.println("在htmlfilter中处理request");//先处理request fc.dofilter(request, response, fc);//调用过滤器链的dofilter方法, 让它去执行下一个filter的dofilter方法, 处理response的代码将被挂起 //在返回的过程中执行response system.out.println("在htmlfilter中处理response"); } }
- 进入
dofilter()
方法后, 首先会对request
请求进行处理, 然后又调用了过滤器链的dofilter()
方法. 这就是整个责任链模式的精妙之处, 它解释了为什么要给dofilter()
加上一个过滤器链参数, 就是为了让每个过滤器可以调用过滤器链本身执行下一个过滤器. - 为什么要调用过滤器链本身? 因为当调用过滤器本身后, 程序将跳转回到过滤器链的
dofilter
方法执行, 这时index
为1, 也就是拿到第二个过滤器, 然后继续处理. - 正是由于这个跳转, 使得过滤器中对
response
的处理暂时无法执行, 它必须等待上面的对过滤器链的方法返回才能被执行. - 所以最后我们将看到
response
响应被过滤器3, 2, 1(和请求倒序)执行.
- 放大招了, 如果看了上面的图还是不懂, 欢迎给我留言.
- 整个责任链模式已经从无到有展现出来了
阅读tomcat中的filter过滤器源码, 加深理解.
- 相信通过上面的讲解, 你已经对整个责任链模式有了进一步的理解.
- 下面我们通过阅读tomcat里filter的源码感受一下.
public interface filter { void init(filterconfig var1) throws servletexception; //熟悉的dofilter(), 熟悉的3个参数request, reponse, filterchain. void dofilter(servletrequest var1, servletresponse var2, filterchain var3) throws ioexception, servletexception; void destroy(); }
- 我们可以看到
filter
接口的定义和我们的讲解的差不多, 只是多了初始化和销毁的方法.
public interface filterchain { void dofilter(servletrequest var1, servletresponse var2) throws ioexception, servletexception; }
- 同时我们也看到它把
filterchain
也向上抽取成接口, 不过这里的filterchain
没有实现filter
接口, 也就是说我们不能把两条filterchain
拼接在一起, 换个角度想tomcat中的过滤器的可扩展性还没有我们例子中的好呢^_^
上一篇: php中session_unset与session_destroy的区别分析
下一篇: 害我一直等