架构师内功心法,必须完全掌握吃透的踢皮球方式的责任链模式详解

前提是你 提交于 2020-03-16 17:50:55

某厂面试归来,发现自己落伍了!>>>

在日常生活中责任链模式还是挺常见的,我们平时工作处理一些事务,往往都是各个部门协同合作完成某一个项目任务。而每个部门都有自己的职责,所以很多时候事情完成了一部分,便会交给下一个部门,直到所有的部门全部完成所有的工作之后,那么这个项目任务才算最终完成。还有平时说的过五关斩六将其实也是一种责任链的模式。

一、责任链模式的应用场景

责任链模式(Chain of Responsibility Pattern)是将链中的每一个节点看作是一个对象,每个节点处理的请求均不同,且内部自动维护下一个节点对象。当一个请求从链式的首端发出时,会沿着链的路径依次传递给每一个节点对象,直到有对象处理这个请求为止。责任模式主要是解耦请求与处理,客户只要将请求发送到对应的链上即可,无需关心请求的具体内容和处理细节,请求会自动进行传递直至有节点的对象进行处理。

责任链模式适用于以下几个场景:

  • 多个对象可以处理同一请求,但是具体由哪个对象处理则在运行时动态决定的;
  • 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求;
  • 可动态指定一组对象处理请求。

责任链模式主要包含两种角色:

  • 抽象处理者(Handler):定义一个请求处理的方法,并维护下一个处理节点Handler对象的引用;
  • 具体处理者(ConcreteHandler):对请求进行处理,如果不感兴趣,则进行转发。

责任链模式的本质是解耦请求与处理,让请求在处理链中能进行传递与被处理;理解责任链模式应当理解的是其模式(道)而不是其具体实现(术),它的独到之处是其将节点处理者组成了链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动起来。

1.1 数据校验拦截使用责任链模式

首先创建一个实体类 User 对象:

public class User {

    private String username;
    private String password;
    private String roleName;

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", roleName='" + roleName + '\'' +
                '}';
    }
}

写一个简单的用户登录权限 UserService 类:

public class UserService {

    private final String ROLE_NAME = "administrator";

    public void login(String username, String password) {
        if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            System.out.println("用户和密码校验成功了,可以继续往下执行了!");
            return;
        }
        System.out.println("用户和密码不能为空,可以继续往下执行了!");

        User user = check(username, password);
        if(null == user) {
            System.out.println("用户不存在!");
            return;
        }

        System.out.println("恭喜,登录成功了!");

        if(!ROLE_NAME.equals(user.getRoleName())) {
            System.out.println("不是管理员,所以没有操作权限!");
        }

        System.out.println("允许操作!");
    }

    public User check(String username, String password) {
        User user = new User(username, password);
        user.setRoleName(ROLE_NAME);
        return user;
    }

    public static void main(String[] args) {
        UserService userService =  new UserService();
        userService.login("kevin", "123456");
    }
}

上面的代码主要功能做了登录前的数据验证,判断逻辑是有先后顺序的。首先判断非空,然后判断用户名和密码,最后根据用户名密码获得用户角色。如果有角色的话就可以获得用户得操作权限。这样的代码在业务中实现显得非常的臃肿,我们来进行改造,可以使用责任链模式,将这些检查步骤串联起来,这样可以使得我们在编码的时候更加关注某个具体业务逻辑的实现处理。

首先创建一个抽象的 Handler 类:

public abstract class Handler {

    protected Handler chain;

    public void next(Handler handler) {
        this.chain = handler;
    }

    public abstract void doHandle(User user);

}

然后分别创建校验ValidateHandler类,登录验证LoginHandler类,还有权限验证AuthHandler类:

ValidateHandler类:

public class ValidateHandler extends Handler {

    @Override
    public void doHandle(User user) {
        if(StringUtils.isEmpty(user.getUsername()) ||
                StringUtils.isEmpty(user.getPassword())) {
            System.out.println("用户和密码校验成功了,可以继续往下执行了!");
            return;
        }
        System.out.println("用户和密码不能为空,可以继续往下执行了!");
        chain.doHandle(user);
    }
}

LoginHandler类:

public class LoginHandler extends Handler {
    @Override
    public void doHandle(User user) {
        System.out.println("恭喜,登录成功了!");
        user.setRoleName(ROLE_NAME);
        chain.doHandle(user);
    }
}

AuthHandler类:

public class AuthHandler extends Handler {
    @Override
    public void doHandle(User user) {
        if(!ROLE_NAME.equals(user.getRoleName())) {
            System.out.println("不是管理员,所以没有操作权限!");
        }

        System.out.println("允许操作!");
    }
}

接下来改造 UserService 类,使得前面定义的几个Handler串联起来:

public class UserService {

    public void login(String username, String password) {
        Handler validateHandler = new ValidateHandler();
        Handler loginHandler = new LoginHandler();
        Handler authHandler = new AuthHandler();

        validateHandler.next(loginHandler);
        loginHandler.next(authHandler);
        validateHandler.doHandle(new User(username, password));
    }


    public static void main(String[] args) {
        UserService userService =  new UserService();
        userService.login("kevin", "123456");
    }
}

其实我们平时使用的很多验证框架的运用这样的一个原理,将各个维度的权限处理后解耦之后再进行串联,各自只负责各自相关的职责即可。如果职责与自己不相关则抛给链上的下一个Handler,俗称踢皮球

1.2 责任链模式和建造者模式结合使用

我们看到前面的代码在UserService类中,当链式结构比较长的话,那么其代码也是很臃肿的,如果在后面修改业务逻辑的话,都需要在UserService类中去进行修改,不符合开闭原则。产生这些问题的原因就是链式结构的组装过于复杂,而且对于结构的创建我们很容易就想到建造者模式,使用建造者模式,完成可以对UserService指定的处理节点对象进行自动链式组装,只需要指定处理节点对象,其它任何事情无需关心,并且可以指定处理对象节点的顺序不同,构造出来的链式结构也随之不同。来对Handler的代码进行改造:

public abstract class Handler<T> {

    protected final String ROLE_NAME = "administrator";

    protected Handler chain;

    public void next(Handler handler) {
        this.chain = handler;
    }

    public abstract void doHandle(User user);

    public static class Builer<T> {
        private Handler<T> head;
        private Handler<T> tail;

        public Builer<T> addHandler(Handler<T> handler) {
            if(this.head == null) {
                this.head = this.tail = handler;
                return this;
            }
            this.tail.next(handler);
            this.tail = handler;
            return this;
        }

        public Handler<T> build() {
            return this.head;
        }

    }

}

然后修改 UserService类:

public class UserService {

    public void login(String username, String password) {
        Handler validateHandler = new ValidateHandler();
        Handler loginHandler = new LoginHandler();
        Handler authHandler = new AuthHandler();

        Handler.Builer builer = new Handler.Builer();
        builer.addHandler(validateHandler)
                .addHandler(loginHandler)
                .addHandler(authHandler);

        builer.build().doHandle(new User(username, password));
    }


    public static void main(String[] args) {
        UserService userService =  new UserService();
        userService.login("kevin", "123456");
    }
}

二、责任链模式在源码中的体现

2.1 Servlet中的Filter类

package javax.servlet;

import java.io.IOException;

public interface Filter {
    default void init(FilterConfig filterConfig) throws ServletException {
    }

    void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;

    default void destroy() {
    }
}

这个Filter接口定义很简单,相当于责任链模式中Handler抽象角色,那么它是如何形成一条责任链的呢?在doFilter()方法的最后一个参数我们已经看到了FilterChain类,来看下这个类的源码:

public interface FilterChain {
    void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException;
}

FilterChain类中也是只定义了一个doFilter()方法,具体的逻辑是由使用者自己去进行实现的。我们来看一个Spring中实现的MockFilterChain类:

public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
    Assert.notNull(request, "Request must not be null");
    Assert.notNull(response, "Response must not be null");
    Assert.state(this.request == null, "This FilterChain has already been called!");
    if (this.iterator == null) {
        this.iterator = this.filters.iterator();
    }

    if (this.iterator.hasNext()) {
        Filter nextFilter = (Filter)this.iterator.next();
        nextFilter.doFilter(request, response, this);
    }

    this.request = request;
    this.response = response;
}

它把链条中所有的Filter放到List中,然后在调用doFilter()方法时循环迭代List,也就是List中的Filter会顺序执行。

2.2 Netty中的Pipeline

Netty中的串行化处理Pipeline采用了责任链模式设计。底层采用双向列表的数据结构,将链式的处理器串联起来。客户端每一个请求过来,Netty认为Pipeline中所有的处理器都有机会处理它。对于入栈的请求全部从头节点开始往后传播,一直传播到尾部节点才会把消息释放掉。 负责处理器的接口ChannelHandler源码:

public interface ChannelHandler {

    /**
     * Gets called after the {@link ChannelHandler} was added to the actual context and it's ready to handle events.
     */
    void handlerAdded(ChannelHandlerContext ctx) throws Exception;

    /**
     * Gets called after the {@link ChannelHandler} was removed from the actual context and it doesn't handle events
     * anymore.
     */
    void handlerRemoved(ChannelHandlerContext ctx) throws Exception;

    /**
     * Gets called if a {@link Throwable} was thrown.
     *
     * @deprecated is part of {@link ChannelInboundHandler}
     */
    @Deprecated
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;

    /**
     * Indicates that the same instance of the annotated {@link ChannelHandler}
     * can be added to one or more {@link ChannelPipeline}s multiple times
     * without a race condition.
     * <p>
     * If this annotation is not specified, you have to create a new handler
     * instance every time you add it to a pipeline because it has unshared
     * state such as member variables.
     * <p>
     * This annotation is provided for documentation purpose, just like
     * <a href="http://www.javaconcurrencyinpractice.com/annotations/doc/">the JCIP annotations</a>.
     */
    @Inherited
    @Documented
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Sharable {
        // no value
    }
}

Netty对负责处理接口做了更细粒度的划分,处理器被分为两种,一种是入栈处理器 ChannelInboundHandler,另外一种是出栈处理器ChannelOutboundHandler,这两个接口都继承ChannelHandler接口。所有的处理都添加在Pipeline上。所以,添加删除责任处理器的接口行为都在ChannelPipeline中进行了规定

在默认的实现类中将所有的Handler都串成了一个链表:

public class DefaultChannelPipeline implements ChannelPipeline {

    static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultChannelPipeline.class);

    private static final String HEAD_NAME = generateName0(HeadContext.class);
    private static final String TAIL_NAME = generateName0(TailContext.class);

    private static final FastThreadLocal<Map<Class<?>, String>> nameCaches =
            new FastThreadLocal<Map<Class<?>, String>>() {
        @Override
        protected Map<Class<?>, String> initialValue() throws Exception {
            return new WeakHashMap<Class<?>, String>();
        }
    };

    private static final AtomicReferenceFieldUpdater<DefaultChannelPipeline, MessageSizeEstimator.Handle> ESTIMATOR =
            AtomicReferenceFieldUpdater.newUpdater(
                    DefaultChannelPipeline.class, MessageSizeEstimator.Handle.class, "estimatorHandle");
    final AbstractChannelHandlerContext head;
    final AbstractChannelHandlerContext tail;
    ......

在Pipeline中的任意一个节点,只要我们不手动往下传播下去,这个事件就会终止传播在当前节点。对于入栈的数据,默认会传递到尾部节点进行回收。如果我们不进行下一步传播,事件就会终止在当前节点。对于出栈的数据把数据写回客户端也意味着事件的终止。

三、责任链模式的优缺点

优点:

  • 将请求与处理解耦;
  • 请求处理者(节点对象)只需要关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,直接转到下一级节点对象;
  • 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果;
  • 链路结构灵活,可以通过改变链路结构动态的新增或者删除责任;
  • 易于扩展新的请求处理类,符合开闭原则。

缺点:

  • 责任链太长或者处理时间太长,会影响整体性能;
  • 如果节点对象存在循环引用时,会造成死循环,导致系统崩溃。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!