职责链模式

职责链模式

案例

张三家里有事需要请假,然后他就拿着假条找项目经理请假去了。而公司规定请假天数小于等于3天,项目经理可以审批该假条;如果员工请假天数大于3天,小于等于5天,部门经理可以审批;如果员工请假天数大于5天需要总经理可以审批,下面我们先使用简单的代码模拟这一过程:

1.首先定义了一个请假申请处理类:

/**
 * 处理请假申请类
 */
public class LeaveApplyHandler {
    private Random random = new Random(10);

    public void handlerApply(Integer day) {
        if (day <= 3) {
            pmHandler(day);
        } else if (day <= 5) {
            dmHandler(day);
        } else {
            gmHandler(day);
        }
    }

    // 总经理审批
    private void gmHandler(Integer day) {
        String result = (random.nextInt(10)) % 4 == 0 ? "通过" : "不通过";
        System.out.println("总经理审批,结果:" + result);
    }

    // 部门经理审批
    private void dmHandler(Integer day) {
        String result = (random.nextInt(5)) % 3 == 0 ? "通过" : "不通过";
        System.out.println("部门经理审批,结果:" + result);
    }

    // 项目经理审批
    private void pmHandler(Integer day) {
        String result = (random.nextInt(3)) % 2 == 0 ? "通过" : "不通过";
        System.out.println("项目经理审批,结果:" + result);
    }
}

2.张三去请假:

public class Main {
    public static void main(String[] args) {
        Integer day = 10;
        System.out.println("张三请假:" + day + "天");
        LeaveApplyHandler applyHandler = new LeaveApplyHandler();
        applyHandler.handlerApply(day);
    }
}

3.请假结果:这里设置的随机数,结果不通过。。。

张三请假:10天
总经理审批,结果:不通过

张三经过层层审批,最后他的申请没有通过。可以看到LeaveApplyHandler类中存在有大量的if...else...,缺乏灵活性。当增加一级审批,比如说当请假天数大于等于5天小于10天时让副总经理审批,就需要修改LeaveApplyHandler类中的逻辑,这样最终将会导致LeaveApplyHandler类过于复杂。其实这个审批的流程与设计模式中的职责链模式非常相似,下面就来介绍以下职责链模式

模式介绍

职责链模式(责任链模式)是一种行为型模式。在职责链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。

角色构成

  • Handler(抽象处理者):它定义了一个处理请求的接口,一般设计为抽象类,由于不同的具体处理者处理请求的方式不同,因此在其中定义了抽象请求处理方法。因为每一个处理者的下家还是一个处理者,因此在抽象处理者中定义了一个抽象处理者类型的对象(如结构图中的successor),作为其对下家的引用。通过该引用,处理者可以连成一条链。
  • ConcreteHandler(具体处理者):它是抽象处理者的子类,可以处理用户请求,在具体处理者类中实现了抽象处理者中定义的抽象请求处理方法,在处理请求之前需要进行判断,看是否有相应的处理权限,如果可以处理请求就处理它,否则将请求转发给后继者;在具体处理者中可以访问链中下一个对象,以便请求的转发。

UML类图

responsibility

从角色构成和类图可以看到职责链模式的核心就在于在抽象类中定义了下一个处理者的引用,每个处理者处理自己可以处理的逻辑,这样在这个处理过程中对于数据的处理就形成了一条链,这就是职责链模式命名的来源。

代码改造

根据上面的介绍,下面对案例代码进行改造:

1.首先定义抽象处理者:

/**
 * 抽象处理者角色
 */
public abstract class LeaveApplyHandler {
    protected Random random = new Random(10);
    // 定义下一个处理者的引用
    protected LeaveApplyHandler handler;

    // 这只下一个处理者
    public void setHandler(LeaveApplyHandler handler) {
        this.handler = handler;
    }

    // 处理逻辑方法
    public abstract void handlerApply(Integer day);
}

2.三个具体处理这类:

项目经理处理类:

// 项目经理处理类
public class PmLeaveApplyHandler extends LeaveApplyHandler {
    @Override
    public void handlerApply(Integer day) {
        if (day <= 3) {
            String result = (random.nextInt(3)) % 2 == 0 ? "通过" : "不通过";
            System.out.println("项目经理审批,结果:" + result);
        } else {
            handler.handlerApply(day);
        }
    }
}

部门经理处理类:

// 部门经理处理类
public class DmLeaveApplyHandler extends LeaveApplyHandler {
    @Override
    public void handlerApply(Integer day) {
        if (day <= 5) {
            String result = (random.nextInt(5)) % 3 == 0 ? "通过" : "不通过";
            System.out.println("部门经理审批,结果:" + result);
        } else {
            handler.handlerApply(day);
        }
    }
}

总经理处理类:

// 总经理处理类
public class GmLeaveApplyHandler extends LeaveApplyHandler {
    @Override
    public void handlerApply(Integer day) {
        String result = (random.nextInt(3)) % 2 == 0 ? "通过" : "不通过";
        System.out.println("项目经理审批,结果:" + result);
    }
}

3.测试请假流程:

public class Main {
    public static void main(String[] args) {
        PmLeaveApplyHandler pmLeaveApplyHandler = new PmLeaveApplyHandler();
        DmLeaveApplyHandler dmLeaveApplyHandler = new DmLeaveApplyHandler();
        GmLeaveApplyHandler gmLeaveApplyHandler = new GmLeaveApplyHandler();
        pmLeaveApplyHandler.setHandler(dmLeaveApplyHandler);
        dmLeaveApplyHandler.setHandler(gmLeaveApplyHandler);

        Integer day = 10;
        System.out.println("张三请假:" + day + "天");
        pmLeaveApplyHandler.handlerApply(day);
    }
}

4.测试结果:

张三请假:10天
项目经理审批,结果:通过

可以看到不仅代码改造成功了,同时测试结果时张三通过了请假申请。

模式应用

职责链模式在框架中有着广泛的应用,下面分析一下它在 SpringMVC 中的应用HandlerInterceptor 拦截器。

1.首先创建一个 web 工程,引入相关依赖-pom 文件:

<properties>
    <spring.version>5.1.15.RELEASE</spring.version>
</properties>

<dependencies>
    <!--spring 核心包 start-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-expression</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <!--spring web-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <!-- servlet api -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

2.配置 web.xml :

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <!--在DispatcherServlet的初始化过程中,框架会在web应用的 WEB-INF文件夹下寻找名为[servlet-name]-servlet.xml 的配置文件,生成文件中定义的bean。-->
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--指明了配置文件的文件名,不使用默认配置文件名,而使用dispatcher-servlet.xml配置文件。-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <!-- spring 配置文件路径 -->
            <param-value>classpath:/spring-servlet.xml</param-value>
        </init-param>
        <!-- 启动顺序:让这个 DispatcherServlet 随 Servlet 容器一起启动 -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <!-- 拦截所有请求 -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

3.创建拦截器:

MyHandlerInterceptorA:

public class MyHandlerInterceptorA implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyHandlerInterceptorA::preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyHandlerInterceptorA::postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyHandlerInterceptorA::afterCompletion");
    }
}

MyHandlerInterceptorB:

public class MyHandlerInterceptorB implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyHandlerInterceptorB::preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyHandlerInterceptorB::postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyHandlerInterceptorB::afterCompletion");
    }
}

4.配置 spring 文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 扫描controller -->
    <context:component-scan base-package="com.phoegel.responsibility.analysis.controller"/>
    <!-- 启用注解驱动 -->
    <mvc:annotation-driven/>

    <!-- 配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <!-- 配置拦截器 -->
    <mvc:interceptors>
        <mvc:interceptor>
            <!-- 匹配的是url路径 -->
            <mvc:mapping path="/**" />
            <!-- 拦截器类 -->
            <bean class="com.phoegel.responsibility.analysis.interceptor.MyHandlerInterceptorA"></bean>
        </mvc:interceptor>
        <mvc:interceptor>
            <!-- 匹配的是url路径 -->
            <mvc:mapping path="/**" />
            <!-- 拦截器类 -->
            <bean class="com.phoegel.responsibility.analysis.interceptor.MyHandlerInterceptorB"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

</beans>

5.编写请求类:

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        System.out.println("hello world");
        return "hello world";
    }
}

6.测试链接:http://localhost:8081/hello

7.后台打印:

MyHandlerInterceptorA::preHandle
MyHandlerInterceptorB::preHandle
hello world
MyHandlerInterceptorB::postHandle
MyHandlerInterceptorA::postHandle
MyHandlerInterceptorB::afterCompletion
MyHandlerInterceptorA::afterCompletion

可以看到在输出hello world之前和之后输出了两个自定义拦截器中的内容,这里的链式处理方式与职责链模式的设计思想非常相似。下面是其执行过程的源码:

1.DispatcherServlet.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 {
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);

         // 确定匹配当前请求的处理器
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }

         // 确定匹配当前请求的适配器
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
         
         // ...
		 // 调用配置的拦截器中的 preHandle() 方法
         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
         }

         // 调用处理请求的业务代码
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

         if (asyncManager.isConcurrentHandlingStarted()) {
            return;
         }

         applyDefaultViewName(processedRequest, mv);
         // 调用配置的拦截器中的 postHandle() 方法
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) {
         dispatchException = ex;
      }
      catch (Throwable err) {
         // As of 4.3, we're processing Errors thrown from handler methods as well,
         // making them available for @ExceptionHandler methods and other scenarios.
         dispatchException = new NestedServletException("Handler dispatch failed", err);
      }
      // 处理调用结果,在其内部会调用配置的拦截器中的 afterCompletion() 方法
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
   // ...
}

可以看到配置的拦截器是在HandlerExecutionChain类实例中的applyPreHandle()applyPostHandletriggerAfterCompletion三个方法中调用的。

2.下面是三个HandlerExecutionChain类中的方法的具体内容:

applyPreHandle():

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
   HandlerInterceptor[] interceptors = getInterceptors();
   if (!ObjectUtils.isEmpty(interceptors)) {
      for (int i = 0; i < interceptors.length; i++) {
         HandlerInterceptor interceptor = interceptors[i];
         // 顺序调用各拦截器中的 preHandle 方法
         if (!interceptor.preHandle(request, response, this.handler)) {
            triggerAfterCompletion(request, response, null);
            return false;
         }
         this.interceptorIndex = i;
      }
   }
   return true;
}

applyPostHandle():

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
      throws Exception {

   HandlerInterceptor[] interceptors = getInterceptors();
   if (!ObjectUtils.isEmpty(interceptors)) {
      for (int i = interceptors.length - 1; i >= 0; i--) {
         HandlerInterceptor interceptor = interceptors[i];
         // 倒序调用各拦截器中的 postHandle 方法
         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)) {
      for (int i = this.interceptorIndex; i >= 0; i--) {
         HandlerInterceptor interceptor = interceptors[i];
         try {
            // 倒序调用各拦截器中的 afterCompletion 方法
            interceptor.afterCompletion(request, response, this.handler, ex);
         }
         catch (Throwable ex2) {
            logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
         }
      }
   }
}

可以看到调用的拦截器是HandlerExecutionChain类内部的成员变量,而HandlerExecutionChain类实例是在DispatcherServlet中的doDispatch()方法中确定的,即mappedHandler = getHandler(processedRequest);

3.然后在一步步调用就会到AbstractHandlerMapping类中的getHandlerExecutionChain()方法:

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

   String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
   // 遍历 AbstractHandlerMapping 类中成员变量 adaptedInterceptors 添加到 HandlerExecutionChain 实例中
   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;
}

可以看到HandlerExecutionChain类中的拦截器是在AbstractHandlerMapping类中添加的。

4.而AbstractHandlerMapping类中的adaptedInterceptors变量是在程序启动时根据配置添加的,它通过继承ApplicationObjectSupport类,重写其中的initApplicationContext()方法,在方法中添加的,具体代码如下:

@Override
protected void initApplicationContext() throws BeansException {
   extendInterceptors(this.interceptors);
   // 发现配置中的拦截器
   detectMappedInterceptors(this.adaptedInterceptors);
   initInterceptors();
}

detectMappedInterceptors()方法:

protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
   // 添加实现了 MappedInterceptor 的类实例
   mappedInterceptors.addAll(
         BeanFactoryUtils.beansOfTypeIncludingAncestors(
               obtainApplicationContext(), MappedInterceptor.class, true, false).values());
}

最终就在程序启动时添加了实现了MappedInterceptor的类实例。整个拦截器执行顺序如下:

  1. 程序启动时添加配置的实现了MappedInterceptor接口的拦截器实例。本例中配置的是:MyHandlerInterceptorAMyHandlerInterceptorB
  2. 当请求调用目标方法前,调用拦截器链中的各拦截器的preHandle()方法,本例中为MyHandlerInterceptorAMyHandlerInterceptorB类中的preHandle()方法;
  3. 调用目标方法,本例中为HelloController.hello()方法;
  4. 目标方法执行完之后,调用拦截器链中的各拦截器的postHandle()方法,本例中为MyHandlerInterceptorAMyHandlerInterceptorB类中的postHandle()方法;
  5. 最后依次调用拦截器链中各拦截器的afterCompletion()方法,本例中为MyHandlerInterceptorAMyHandlerInterceptorB类中的afterCompletion()方法;

总结

1.主要优点

  • 职责链模式使得一个对象无须知道是其他哪一个对象处理其请求,对象仅需知道该请求会被处理即可,接收者和发送者都没有对方的明确信息,且链中的对象不需要知道链的结构,由客户端负责链的创建,降低了系统的耦合度。
  • 请求处理对象仅需维持一个指向其后继者的引用,而不需要维持它对所有的候选处理者的引用,可简化对象的相互连接。
  • 在给对象分派职责时,职责链可以给我们更多的灵活性,可以通过在运行时对该链进行动态的增加或修改来增加或改变处理一个请求的职责。
  • 在系统中增加一个新的具体请求处理者时无须修改原有系统的代码,只需要在客户端重新建链即可,从这一点来看是符合“开闭原则”的。

2.主要缺点

  • 由于一个请求没有明确的接收者,那么就不能保证它一定会被处理,该请求可能一直到链的末端都得不到处理;一个请求也可能因职责链没有被正确配置而得不到处理。
  • 对于比较长的职责链,请求的处理可能涉及到多个处理对象,系统性能将受到一定影响,而且在进行代码调试时不太方便。
  • 如果建链不当,可能会造成循环调用,将导致系统陷入死循环。

3.适用场景

  • 有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定,客户端只需将请求提交到链上,而无须关心请求的处理对象是谁以及它是如何处理的。
  • 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
  • 可动态指定一组对象处理请求,客户端可以动态创建职责链来处理请求,还可以改变链中处理者之间的先后次序。

参考资料

本篇文章github代码地址:https://github.com/Phoegel/design-pattern/tree/main/responsibility
转载请说明出处,本篇博客地址:https://www.cnblogs.com/phoegel/p/14117027.html

posted @ 2020-12-10 19:53  Phoegel  阅读(355)  评论(0编辑  收藏  举报