zoukankan      html  css  js  c++  java
  • 由浅入深讲解责任链模式,理解Tomcat的Filter过滤器

    本文将从简单的场景引入, 逐步优化, 最后给出具体的责任链设计模式实现.

    场景引入

    • 首先我们考虑这样一个场景: 论坛上用户要发帖子, 但是用户的想法是丰富多变的, 他们可能正常地发帖, 可能会在网页中浅入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;
        }
    }
    

     

    • 下面我描述一次整个过程, 你可以根据文字找到相应的代码进行理解.
    • 首先我们分别创建一个RequestResponse对象. 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中的过滤器的可扩展性还没有我们例子中的好呢^_^

     

  • 相关阅读:
    Semaphore
    财报分析
    关于C#中的new的用法
    Linux(CentOS)下Postgresql数据库的安装配置
    CentOS下实现SCP免输密码传送文件
    HiveQL逻辑执行顺序
    CentOS上以源码的方式安装Redis笔记
    Python学习心得(七) 深入理解threading多线程模块
    SQL Server返回两个Date日期相差共多少天零多少小时零多少分钟零多少秒
    Python学习心得(六) 反射机制、装饰器
  • 原文地址:https://www.cnblogs.com/tanshaoshenghao/p/10741160.html
Copyright © 2011-2022 走看看