美文网首页
设计模式系列-责任链模式

设计模式系列-责任链模式

作者: 该叫什么昵称好 | 来源:发表于2020-10-05 15:00 被阅读0次

    你开发了一套博客系统,但用户老发一些涉黄、广告词汇。眼看网站就要被封了,你该怎么办?

    对的,过滤掉敏感词,还广大用户一篇清静。

    实现这个功能,你很自然地想到 Servlet 的过滤器,或者是 Spring 的拦截器。

    然而,你有想过吗?框架中的过滤器、拦截器是怎么实现的呢?

    虽然我们没机会开发一套拦截器机制,但了解开源框架的底层实现,会大大提高我们的敲代码水平。再不济,你也能在同事面前装下X。

    责任链模式的原理和实现

    拦截器的实现,离不开责任链模式。 无论是 Spring 的拦截器,还是 Tomcat 实现的 Servlet 过滤器,都用到了责任链模式。

    我们先来看它的官方定义:将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。

    看完之后,你一脸懵逼。简单来说,你定义了多个处理器,依次处理同一个请求。

    比如说,一个http请求,先经过A处理器处理,再传给B处理器,然后再传给C处理器,就这样一直传下去,形成一条链条。在这条链条上,每个处理器的都承担自己的责任,所以叫做:责任链模式。

    这就是责任链模式的原理了。那代码怎么写?

    思路是这样的。我们先定义两个处理器类(HandlerA、HandlerB),来处理具体的业务。再定义一个处理器链条,负责不断调用处理器,处理客户的请求。最后,创建一个处理器链条,这就是我们的客户端代码。

    你可以结合下面的代码,加深一下理解。

    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @Author: jiarupc
     * @Date: 2020/9/28
     * @Description:    责任链模式-演示demo
     */
    public class ChainOfResponsibility {
        /*********************** 处理器接口及实现 *************************/
        public interface IHandler {
            boolean handle(String request);
        }
    
        public static class HandlerA implements IHandler {
            @Override
            public boolean handle(String request) {
                System.out.println("已经过A处理器处理");
                // ...你想要的业务
    
                return request != null && !"".equals(request);
            }
        }
    
        public static class HandlerB implements IHandler {
            @Override
            public boolean handle(String request) {
                System.out.println("已经过B处理器处理");
                // ...你想要的业务
    
                return request != null && !"".equals(request);
            }
        }
    
        /*********************** 处理链条 *************************/
        public static class HandlerChain {
            private List<IHandler> handlers = new ArrayList<>();
    
            public void addHandler(IHandler handler) {
                this.handlers.add(handler);
            }
    
            public void handle(String request) {
                for (IHandler handler : handlers) {
                    boolean isHandle = handler.handle(request);
                    if (!isHandle) {
                        System.out.println("处理失败,跳过链条,直接退出");
                        break;
                    }
                }
                System.out.println("处理完成,共调用 " + handlers.size() + " 个处理器");
            }
        }
    
        /*********************** 客户端 *************************/
        public static void main(String[] args) {
            // 构建处理器链条,你可以用注解和反射,实现自动发现处理器
            HandlerChain chain = new HandlerChain();
            chain.addHandler(new HandlerA());
            chain.addHandler(new HandlerB());
    
            // 启动处理链,处理request
            String request = "请求模拟";
            chain.handle(request);
        }
    
    }
    

    为什么要用责任链模式?

    拿文章开头的业务做例子,我们现在不但要过滤涉黄词汇,还要过滤替换一些特殊符号。

    这时候,我们可以直接改拦截器的代码,直接在上面多写几个 if-else 判断。在小项目上,这样完全没问题。但要是碰上大项目,需求多了,你的代码很可能会越写越多,越写越乱。

    比方说,除了过滤涉黄、广告等词汇,还得过滤替换特殊符号。要做的东西越来越多,你能在里面无限写下去吗?这时候该怎么办?

    除此之外,在大项目、开源项目,是很多人、很多团队同时开发的,工作内容明确,泾渭分明。别人没法改你的代码,你必须给别人的开发留足空间。

    责任链模式能提高代码的拓展性。

    你不用再改别人的代码了,只要再新建一个 Handler 类,实现它的 handle() 方法,再调用 addHandler() 方法,添加进 HandlerChain,这就可以了。

    你想想,Spring 的拦截器不也是这样吗?先自定义一个 intercepter,再加上注解,就能拦截处理http请求了。

    这些各种高大上的框架,之所以用起来这么顺手,是因为给你留足了空间,让你能按自己的需求取开发。而这一切的开始,就是在上面的简单 demo 中,一步步迭代出来的。

    Shiro 怎么添加过滤器?

    责任链模式经常出现在框架中,让我们在不修改源码的情况下,添加新的功能。

    Shiro 是一个Java安全框架,提供了各种安全认证功能。这种安全框架一般会用各种过滤器,来实现权限拦截功能。

    我们就从 Shiro 的入手,来看看这些高大上的项目,是怎么运用责任链模式的。

    Shiro 在 Servlet Filter 的基础上,自己定制了一套的过滤器功能。就是下面这副图:

    Shiro-拦截器流程

    在开发的时候,我们该怎么使用 Shiro Filter 呢?首先,新建一个自己的 Filter 类,并且继承 UserFilter。然后,把刚才新建的 Filter 类,加到配置文件就行了。

    // 这里只留下了过滤器的代码,删掉了其它的代码,大家可以看得更直观
    
    /**
     * @Author: jiarupc
     * @Date: 2020/8/26
     * @Description:    登录token过滤器
     */
    public class TokenFilter extends UserFilter {
    
    
        @Override
        public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            System.out.println("token过滤器已处理");
            // ...你想要的业务
    
            chain.doFilter(request, response);
        }
    
    }
    
    /**
     * @Author: jiarupc
     * @Date: 2020/08/26
     * @Description: shiro 配置类:用于完成 shiro 配置
     */
    @Configuration
    public class ShiroConfiguration {
        /***************** shiro 基础配置 *****************/
        /**
         * 配置shiro过滤器
         * @param manager 已在 Spring 容器中注册的“安全管理器”
         * @return
         */
        @Bean("shiroFilter")
        public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager manager) {
            /** 基础配置 **/
            // 定义shiro过滤器工厂bean
            ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
    
            /** 自定义过滤器 **/
            Map<String, Filter> filterMap = Maps.newLinkedHashMap();
            // 登录token过滤器
            filterMap.put("token", new TokenFilter());
    
            bean.setFilters(filterMap);
    
            return bean;
        }
    
    }
    

    整个流程非常简单,我们只要继承 UserFilter,在上面写自己的业务,然后加一行配置就行了。拓展性这么好,Shiro 是怎么做到的呢?

    解析 Shiro 源码

    Shiro 的过滤器用到了责任链模式。追踪 Shiro 的源码,我们可以发现一个类 ProxiedFilterChain。我们看下里面的关键代码:

    /**
     * FilterChain 的代理类,负责执行 FilterChain 
     */
    public class ProxiedFilterChain implements FilterChain {
    
        // 初始化时,会从外部传入一个 FilterChain
        private FilterChain orig;
        // 需要执行的 Filter链
        private List<Filter> filters;
        // 当前执行到哪个 Filter
        private int index = 0;
    
        public ProxiedFilterChain(FilterChain orig, List<Filter> filters) {
            // ProxiedFilterChain 只是一个代理类,必须传入 FilterChain 才能执行
            if (orig == null) {
                throw new NullPointerException("original FilterChain cannot be null.");
            }
            this.orig = orig;
            this.filters = filters;
            this.index = 0;
        }
    
    
        public void doFilter(ServletRequest request, ServletResponse response) {
            // filter 链为空时,直接调用原来 FilterChain 的方法
            if (this.filters == null || this.filters.size() == this.index) {
                this.orig.doFilter(request, response);
            } else {
                this.filters.get(this.index++).doFilter(request, response, this);
            }
        }
    }
    

    我们可以看到,Shiro 拓展了责任链模式,增加了一个代理类。

    作为一个权限框架,Shiro 要执行的责任链不止一条。如果每条责任链都写自己执行的代码,那肯定造成代码冗余。所以,Shiro 就把重复代码抽出来,成为一个代理类,专门来执行责任链的业务。

    那么,Shiro 是怎么实现责任链模式的呢?

    Shiro 的代码非常巧妙,我们把代理类和过滤器合起来看,会发现:它们实现了一个递归调用。先来看代理类 ProxiedFilterChain.doFilter() 方法。我重新贴上关键代码:

    /**
     * FilterChain 的代理类,负责执行 FilterChain 
     */
    public class ProxiedFilterChain implements FilterChain {
        // 初始化时,会从外部传入一个 FilterChain
        private FilterChain orig;
        // 需要执行的 Filter链
        private List<Filter> filters;
        // 当前执行到哪个 Filter
        private int index = 0;
        
        public void doFilter(ServletRequest request, ServletResponse response) {
            // filter 链为空时,直接调用原来 FilterChain 的方法
            if (this.filters == null || this.filters.size() == this.index) {
                this.orig.doFilter(request, response);
            } 
            // 调用 filter.doFilter()
            else {
                this.filters.get(this.index++).doFilter(request, response, this);
            }
        }
    }
    

    在 ProxiedFilterChain 的第 19 行代码,它为了让 Filter 链顺利执行,做了两个操作。首先, this.filters.get(index++),这段代码的意思是:先获取计数器的下标,通过下标拿到对应的 Filter,再把计数器 + 1。最后,执行过滤器 Filter.doFilter() 方法的。

    再来看看 TokenFilter,在实现了业务代码后,这里的第 10 行代码,会重新调用 ProxiedFilterChain.doFilter 方法。

    public class TokenFilter extends UserFilter {
    
        // doFilterInternal 等于 doFilter,shiro 对 doFilter 做了特殊处理,不允许修改
        @Override
        public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            System.out.println("token过滤器已处理");
            // ...你想要的业务
    
            // 这行代码会调用 ProxiedFilterChain.doFilter(),找到实现递归调用
            chain.doFilter(request, response);
        }
    
    }
    

    这样一来,就实现了递归调用。如果你还是觉得很难理解,可以再看看下面这副流程图:

    Shiro-责任链流程

    写在最后

    责任链模式常用在框架开发上,来实现框架的过滤器、拦截器功能。使用者不需要修改框架源码,就能添加新的过滤拦截功能。在 Shiro 的过滤器中,就用到了责任链模式。

    当然,我们在日常工作中,很少能碰上 Shiro 那样的大项目,但你总得做好准备。说不定,在某一天,机会就来的呢?

    相关文章

      网友评论

          本文标题:设计模式系列-责任链模式

          本文链接:https://www.haomeiwen.com/subject/hpjauktx.html