专栏首页java技术爱好者面试官:兄弟,讲一下责任链模式

面试官:兄弟,讲一下责任链模式

前言

各位java技术爱好者,我们又见面了! 之前我在面试的时候被问到责任链模式的问题,当时答不上来。这件事就一直在我心里耿耿于怀。相信很多人面试完都有这种体验,哈哈~ 不过今日不同往日了,现在我已经搞懂了,其实并不是很难,给大家分享一下。

开始搞事情

要学习一种技术,当然要搞清楚能解决什么问题,这是最关键的,否则就像买了一个开瓶器,还是用嘴咬开啤酒瓶盖一样(比喻很巧妙)。

例子

首先我们用Request对象,表示一个请求。

public class Request {
    //请求数据
    private String data;

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
}

假设data中有很多数据,我们要过滤掉一些关键字,比如水果蔬菜。 如果不用设计模式,直接用if解决,就是这样写:

public static void main(String[] args) throws Exception {
        //创建请求体
        String data = "肉蛋葱鸡水果蔬菜千层饼雅俗共赏第五层的马老师";
        Request request = new Request();
        request.setData(data);
        String requestData = request.getData();
        //如果包含水果关键字
        if (requestData.contains("水果")) {
            //过滤水果关键字
            requestData = filterByWord(requestData, "水果");
        }
        //如果包含蔬菜关键字
        if (requestData.contains("蔬菜")) {
            //过滤蔬菜关键字
            requestData = filterByWord(requestData, "蔬菜");
        }
        request.setData(requestData);
        System.out.println(requestData);//肉蛋葱鸡千层饼雅俗共赏第五层的马老师
    }
    //过滤关键字的方法
    private static String filterByWord(String data, String word) {
        StringBuilder sb = new StringBuilder(data);
        while (true) {
            int index = sb.indexOf(word);
            //如果不等于-1,搜索到关键字
            if (index != -1) {
                for (int i = 0; i < word.length(); i++) {
                    //关键字有多长就删多少次,比如水果,在index位置上删两次
                    sb.deleteCharAt(index);
                }
            } else {
                //等于-1,没有关键字,跳出循环
                break;
            }
        }
        return sb.toString();
    }
}    

这样的代码在项目中不要太多,遇事不决来个if,不行就再来一个。一般这种程序员就只看到了第一层。 问题: 1.如果这时候要增加多一些关键字呢,怎么处理?在原来的代码里继续加if的话,那这个方法就越写越长。而且破坏了开闭原则。 2.如果要调换顺序呢。难道要剪切复制代码? 所以一个好的程序员,实现功能只是基本要求,重要是代码要有好的维护性和扩展性。

怎么优化呢?

我们可以这样想,把每个过滤关键字的方法抽成一个类,然后定义一个过滤关键字的方法,因为有很多个类似职责的类,所以定义一个接口公共的方法可以定义在接口。 第一步:定义过滤器接口

public interface Filter {
    //接口方法
    String doFilter(String data, FilterChain filterChain);

    //过滤关键字的方法
    default String filterByWord(String data, String word) {
        StringBuilder sb = new StringBuilder(data);
        while (true) {
            int index = sb.indexOf(word);
            if (index != -1) {
                for (int i = 0; i < word.length(); i++) {
                    //关键字有几个字,就删几次。比如水果就在index删两次即可
                    sb.deleteCharAt(index);
                }
            } else {
                //如果找不到关键字,就跳出循环
                break;
            }
        }
        return sb.toString();
    }
}

第二步:定义水果关键字过滤器

public class FruitsFilter implements Filter {
    @Override
    public String doFilter(String data, FilterChain filterChain) {
        //过滤水果关键字
        data = filterByWord(data, "水果");
        //拿到控制器,继续调用下一个过滤器
        return filterChain.preHandleRequest(data);
    }
}

第三部:定义蔬菜关键字过滤器

public class VegetablesFilter implements Filter {
    @Override
    public String doFilter(String data, FilterChain filterChain) {
        //过滤蔬菜关键字
        data = filterByWord(data, "蔬菜");
        //拿到控制器,继续调用下一个过滤器
        return filterChain.preHandleRequest(data);
    }
}

第四步:定义一个过滤器的控制器

public class FilterChain {
    //使用List封装过滤器,List是有序的
    private List<Filter> filters = new ArrayList<>();
    //过滤器的下标,从0开始,每次调用preHandleRequest方法就+1
    private int index = 0;

    //添加过滤器
    public void addFilter(Filter filter) {
        filters.add(filter);
    }

    //调用过滤器的doFilter()方法,并把指针+1指向下一个过滤器
    public String preHandleRequest(String data) {
        //如果指针大于集合的size,则return,不再往下调用,相当于递归的终结条件
        if (index == filters.size()) {
            return data;
        }
        Filter filter = filters.get(index);
        //下标指针+1
        index++;
        //精髓在这个this,把自己再当做参数传进去,实现了递归
        return filter.doFilter(data, this);
    }
}

这样就大功告成了,最后我们创建一个Main方法试试吧~

public class Main {
    public static void main(String[] args) throws Exception {
        //创建请求体
        String data = "肉蛋葱鸡水果蔬菜千层饼雅俗共赏第五层的马老师";
        Request request = new Request();
        request.setData(data);
        //创建FilterChain执行链
        FilterChain filterChain = new FilterChain();
        //添加过滤器
        filterChain.addFilter(new FruitsFilter());
        filterChain.addFilter(new VegetablesFilter());
        //调用调用链的preHandleRequest方法
        String s = filterChain.preHandleRequest(request.getData());
        request.setData(s);
        //打印结果,验证
        System.out.println(request.getData());//下面是打印结果,结果正确
        //肉蛋葱鸡千层饼雅俗共赏第五层的马老师
    }
}

这就是责任链模式!下面用一张图来看看调用链的执行顺序。其实没有想得那么难吧。你学会了吗?

责任链模式的优点

我们回过头去看,为什么这么绕呢,直接if不香吗? 还真不香,在用if的时候,提到的两个问题,破坏开闭原则,还有调用顺序的问题,用责任链模式都得到解决了。 1.如果要加一些过滤的关键字,只需要加一个过滤的类,然后再添加到过滤器的集合中,不需要对原来的代码进行侵入式的开发。符合开闭原则。2.执行顺序要变更的话,也不需要侵入式的改代码,只需要改一下添加过滤器的顺序。在Servlet中,过滤器的顺序就是按xml文件定义的顺序。在SpringMVC则是按注册的顺序。这就非常易于扩展。 要多为下一个程序员着想,我为人人,人人为我,世界才会变得更美好!哈哈~~

SpringMVC的责任链模式

以上就是一个简单的demo,重要的是学习这种思想。那么我们看实战中是怎么应用的,所谓知其然知其所以然,学习忌讳浅尝辄止,趁热打铁,我们看看框架中怎么应用责任链模式。

SpringMVC拦截器的使用

很简单,实现HandlerInterceptor接口,接口有三个方法需要重写。

  • preHandle():在业务处理器处理请求之前被调用。预处理。
  • postHandle():在业务处理器处理请求执行完成后,生成视图之前执行。后处理。
  • afterCompletion():在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面);

我们定义一个关键字过滤器WordInterceptor

//定义一个关键字拦截器
public class WordInterceptor implements HandlerInterceptor {
    //在执行controller定义的请求方法前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("执行WordInterceptor的preHandle()");
        return true;
    }
    //controller定义的请求方法执行后,但还没渲染页面前,执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("执行WordInterceptor的postHandle()");
    }
    //在渲染页面完毕后执行,或者preHandle()返回fasle时执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("执行WordInterceptor的afterCompletion()");
    }
}

如法炮制,再定义一个登录拦截器

public class LoginInterceptor implements HandlerInterceptor {
    //在执行controller定义的请求方法前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("执行LoginInterceptor的preHandle()");
        return true;
    }
    //controller定义的请求方法执行后,但还没渲染页面前,执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("执行LoginInterceptor的postHandle()");
    }
    //在渲染页面完毕后执行,或者preHandle()返回fasle时执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("执行LoginInterceptor的afterCompletion()");
    }
}

然后再注册到拦截器的集合中。

@Component
public class WebInterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //先注册关键字拦截器,拦截所有请求
        registry.addInterceptor(new WordInterceptor()).addPathPatterns("/**");
        //再注册登录拦截器,拦截所有请求
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**");
    }
}

然后启动项目,发起一个请求,我们就可以在控制台看到调用的顺序了。注意观察:

//按注册顺序执行preHandle()方法
执行WordInterceptor的preHandle()
执行LoginInterceptor的preHandle()
//按注册顺序,逆序执行postHandle()方法
执行LoginInterceptor的postHandle()
执行WordInterceptor的postHandle()
//从最后一个preHandle()执行的类,逆序执行afterCompletion()
执行LoginInterceptor的afterCompletion()
执行WordInterceptor的afterCompletion()

执行的流程图就是这样:

原理是什么呢?我们不妨走进源码去分析。 还是要看DispatcherServletdoDispatch()方法

doDispatch()调用顺序

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //请求对象
        HttpServletRequest processedRequest = request;
        //定义一个调用链
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;
            try {
                //省略
                // 获取调用链
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }
                //获取对应的适配器
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                //调用拦截器的preHandle()前处理方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    //如果上面返回fasle,取反就是true,进来这里就return,结束了
                    return;
                }
                //调用Controller的RequestMapping对应的方法
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                //调用拦截器的postHandle()后处理方法
                mappedHandler.applyPostHandle(processedRequest, response, mv);
                //省略
            }catch (Exception ex) {
            //如果出现异常还是会执行triggerAfterCompletion()方法
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Throwable err) {
            //如果出现异常还是会执行triggerAfterCompletion()方法
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                    new NestedServletException("Handler processing failed", err));
        }
        finally {
            //省略
        }
    }

从上面源码中明显可以知道,拦截器接口的执行顺序: applyPreHandle()handle()applyPostHandle()triggerAfterCompletion() 或者 applyPreHandle()triggerAfterCompletion()

关键在于HandlerExecutionChain接口

其实上面这些方法除了handle()定义在适配器中,其他都是这个接口的。handle()方法我已经在上一篇《适配器模式与SpringMV》讲过了。下面我们看HandlerExecutionChain接口。

public class HandlerExecutionChain {
    //省略
    @Nullable
    private HandlerInterceptor[] interceptors;

    @Nullable//拦截器集合
    private List<HandlerInterceptor> interceptorList;
    //指针,用来记录applyPreHandle()方法执行到哪一个拦截器
    private int interceptorIndex = -1;
}

这不就跟我们的demo类似吗?定义了一个集合封装拦截器,定义一个指针遍历集合。

applyPreHandle()方法

那么前置方法applyPreHandle()是怎么样执行的呢?

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //获取拦截器
        HandlerInterceptor[] interceptors = getInterceptors();
        //判断不为空
        if (!ObjectUtils.isEmpty(interceptors)) {
            //按顺序遍历,所以拦截器接口定义的preHandle()是按顺序执行的
            for (int i = 0; i < interceptors.length; i++) {
                //按顺序获取注册的拦截器
                HandlerInterceptor interceptor = interceptors[i];
                //执行拦截器的preHandle()方法
                if (!interceptor.preHandle(request, response, this.handler)) {
                    //如果preHandle()返回false,那就调用triggerAfterCompletion()方法
                    triggerAfterCompletion(request, response, null);
                    //返回false,结束调用
                    return false;
                }
                //如果preHandle()返回true,继续执行
                //把下标索引记录到成员变量的指针中,用于后面执行triggerAfterCompletion()方法
                this.interceptorIndex = i;
            }
        }
        return true;
    }

applyPostHandle()方法

然后下一步执行的applyPostHandle()方法,又是怎么执行的呢?

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
            throws Exception {
        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            //倒序遍历,所以postHandle()方法是从最后一个拦截器开始执行的
            for (int i = interceptors.length - 1; i >= 0; i--) {
                HandlerInterceptor interceptor = interceptors[i];
                //postHandle()能获取到ModelAndView对象,拦截器可以对mv对象进行后处理
                interceptor.postHandle(request, response, this.handler, mv);
            }
        }
    }

triggerAfterCompletion()方法

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
            throws Exception {
        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            //从成员变量interceptorIndex记录的指针值,开始倒序遍历
            for (int i = this.interceptorIndex; i >= 0; i--) {
                HandlerInterceptor interceptor = interceptors[i];
                try {
                    interceptor.afterCompletion(request, response, this.handler, ex);
                }
                catch (Throwable ex2) {
                    logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
                }
            }
        }
    }

拦截器集合interceptorList如何组装

我们都看到上面三个方法都有一个公用的方法getInterceptors()

    @Nullable
    public HandlerInterceptor[] getInterceptors() {
        if (this.interceptors == null && this.interceptorList != null) {
            this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[0]);
        }
        return this.interceptors;
    }

关键是这个interceptorList是在哪里把拦截器add()进去的呢? 其实很简单,经过一路顺藤摸瓜,我们看到AbstractHandlerMappinggetHandlerExecutionChain()方法:

    protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
        HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
                (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

        String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
        for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
            //判断是否继承拦截器父类
            if (interceptor instanceof MappedInterceptor) {
                MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
                if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                    //添加拦截器
                    chain.addInterceptor(mappedInterceptor.getInterceptor());
                }
            }
            else {
                //添加拦截器
                chain.addInterceptor(interceptor);
            }
        }
        //返回调用链
        return chain;
    }

那么上面这个方法又在什么时候被调用呢?我们一直往上找调用方。 首先是AbstractHandlerMappinggetHandler()方法

    @Override
    @Nullable
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        //获取调用链
        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
        //省略
        return executionChain;
    }

然后到了DispatcherServletgetHandler()方法

    @Nullable
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
            for (HandlerMapping hm : this.handlerMappings) {
                //省略
                //获取调用链
                HandlerExecutionChain handler = hm.getHandler(request);
                if (handler != null) {
                    //不为null则返回
                    return handler;
                }
            }
        }
        return null;
    }

最后回到了DispatcherServletdoDispatch()方法

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;
            try {
                // 获取调用链方法!!!
                mappedHandler = getHandler(processedRequest);
                //省略
                }
            }
        }
    }

哈哈~~真相大白了!是在doDispatch()方法里,获取调用链getHandler()方法中组装好interceptorList拦截器集合的!

SpringMVC拦截器执行流程图解

用张图总结一下,就是这样,我用不同的颜色分步骤从浅到深标记了(求点赞):

最后说几句

除了SpringMVC外,其实还有很多框架都使用了责任链模式,比如ServletFilter,还有Struts2Interceptor等等。有兴趣的同学可以去看看源码,其实都大同小异,思想懂了之后,源码看起来就没那么费劲了。

本文分享自微信公众号 - java技术爱好者(yehongzhi_java),作者:牛九木

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-06-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 5千字的SpringMVC总结,我觉得你会需要

    SpringMVC再熟悉不过的框架了,因为现在最火的SpringBoot的内置MVC框架就是SpringMVC。我写这篇文章的动机是想通过回顾总结一下,重新认识...

    java技术爱好者
  • 教你用构建者(生成器)模式优雅地创建对象

    很多博客文章上来就先抛出一个定义,我们不妨反过来问一句为什么要用构建者模式。 首先我们创建一个User类,然后采用有参构造器的方式创建对象。

    java技术爱好者
  • 详细讲解!从秒杀聊到ZooKeeper分布式锁

    实际上ZooKeeper的应用是非常广泛的,实现分布式锁只是其中一种。接下来我们就ZooKeeper实现分布式锁解决秒杀超卖问题进行展开。

    java技术爱好者
  • JavaWeb10-request&response你不得不学(1)

    ? request&response 一.request和response的介绍 1. request和response的作用执行流程 Web服务器收到客户端...

    Java帮帮
  • Android基于Retrofit2.0 封装的超好用的RetrofitClient工具类

    本篇是去年出的一篇关于retrofit和rxJava的文章,收到很多讨论和好评,今天再次编辑一下发出来,以便对RxJava和Retrofit的整理系统的学习入门...

    开发者技术前线
  • Java程序员的日常—— 垃圾回收中引用类型的作用

    在Java里面,是不需要太过于关乎垃圾回收,但是这并不意味着开发者可以不了解垃圾回收的机制,况且在java中内存泄露也是家常便饭的事情。因此了解垃圾回收的相关...

    用户1154259
  • 【设计模式-建造者模式】

    将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。

    Liusy
  • Python读取二进制文件代码方法解析

    有二进制文件中保存了 20 亿个 2 Bytes 的数,需将其读出,每 20000 个数作图,拟合后输出结果。

    砸漏
  • Jenkins+SVN+Maven自动化部署环境搭建

    往期精选 环境准备 操作系统:Windows10 Java环境:下载 jdk-1.8.0-131-X64.zip,配置Java环境变量(参考:http://ji...

    企鹅号小编
  • java并发编程实践学习(2)--对象的组合

    先验条件(Precondition):某些方法包含基于状态的先验条件。例如,不能从空队列中移除一个元素,在删除元素前队列必须处于非空状态。基于状态的先验条件的操...

    Ryan-Miao

扫码关注云+社区

领取腾讯云代金券