前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试官:兄弟,讲一下责任链模式

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

作者头像
java技术爱好者
发布2020-09-22 16:23:36
2380
发布2020-09-22 16:23:36
举报

前言

各位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等等。有兴趣的同学可以去看看源码,其实都大同小异,思想懂了之后,源码看起来就没那么费劲了。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-06-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 java技术爱好者 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 开始搞事情
  • 例子
  • 怎么优化呢?
  • 责任链模式的优点
  • SpringMVC的责任链模式
  • SpringMVC拦截器的使用
  • doDispatch()调用顺序
  • 关键在于HandlerExecutionChain接口
  • applyPreHandle()方法
  • applyPostHandle()方法
  • triggerAfterCompletion()方法
  • 拦截器集合interceptorList如何组装
  • SpringMVC拦截器执行流程图解
  • 最后说几句
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档