设计模式之责任链讲解

责任链模式适用于需要将请求和处理解耦的场景,同时又需要动态地组织处理逻辑的场景。

通过使用责任链模式,可以实现请求的动态处理、灵活的扩展和简化的代码编写,提高系统的可维护性和可扩展性。

一、责任链入门

以下这是GPT生成的责任链代码:

1.定义抽象处理器接口

java 复制代码
// 定义抽象处理者接口
interface Handler {
    void handleRequest(int request);
    void setNextHandler(Handler handler);
}

2. 具体处理者类1

java 复制代码
class ConcreteHandler1 implements Handler {
	// 定义第二个处理器
    private Handler nextHandler;

    @Override
    public void handleRequest(int request) {
        if (request >= 0 && request < 10) {
            System.out.println("ConcreteHandler1 处理请求:" + request);
        } else if (nextHandler != null) {
            nextHandler.handleRequest(request);
        }
    }

    @Override
    public void setNextHandler(Handler handler) {
        this.nextHandler = handler;
    }
}

3. 具体处理者类2

java 复制代码
class ConcreteHandler2 implements Handler {
    private Handler nextHandler;

    @Override
    public void handleRequest(int request) {
        if (request >= 10 && request < 20) {
            System.out.println("ConcreteHandler2 处理请求:" + request);
        } else if (nextHandler != null) {
            nextHandler.handleRequest(request);
        }
    }

    @Override
    public void setNextHandler(Handler handler) {
        this.nextHandler = handler;
    }
}

4. 客户端类

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 创建具体处理者对象
        Handler handler1 = new ConcreteHandler1();
        Handler handler2 = new ConcreteHandler2();

        // 设置处理者之间的关系
        handler1.setNextHandler(handler2);

        // 创建请求并发送给责任链的第一个处理者
        handler1.handleRequest(5);
        handler1.handleRequest(15);
        handler1.handleRequest(25);
    }
}

在这个示例中,Handler 是抽象处理者接口,定义了处理请求的方法 handleRequest 和设置下一个处理者的方法 setNextHandler。ConcreteHandler1 和 ConcreteHandler2 是具体处理者类,分别处理不同范围的请求。Client 是客户端类,创建具体处理者对象并设置它们之间的关系,然后发送请求给责任链的第一个处理者。当请求发送到责任链后,责任链中的处理者按顺序尝试处理请求,直到有一个处理者处理了请求为止。
handler1.handleRequest(15); 表示将请求值为15的请求发送给责任链的第一个处理者 handler1。在当前设计的责任链模式中,一旦有一个处理者处理了请求,处理链就会终止,不再将请求传递给下一个处理者。因此,每次发送请求后,只有第一个能够处理该请求的处理者会对其进行处理。因为具体的实现类中是通过if...else进行处理器的整合的,当然也可以修改为只要满足特定的处理条件,处理器就会继续往下走。

责任链作为一种设计模式,目的是为了让代码更加优雅,复用性更高。

二、上下文责任链

1. 定义订单处理器模板接口

java 复制代码
public interface OrderProcessor {
	// 自定义
    void processOrder(Order order, OrderContext context);

	// 公共方法
    void preRequestHandler(Order order);
}

2. 定义订单处理器抽象类模板

实现公共方法

java 复制代码
public abstract class AbstractOrderProcessor implements OrderProcessor {

     @Override
     public void preRequestHandler(Order order) {
          System.out.println("采购员对订单统一处理");
     }
}

3. 订单处理器实现

3.1 开始处理器
java 复制代码
@Component
@HandlerOrder(order = 1)
public class PendingOrderProcessor extends AbstractOrderProcessor {

    @Override
    public void processOrder(Order order, OrderContext context) {
       // 处理待处理的订单逻辑
       System.out.println("开始订单处理: " + order.getId());
       // 可以通过上下文对象访问共享信息
       System.out.println("Context: " + context.getInfo());
    }
}
3.2 批准处理器
java 复制代码
@Component
@HandlerOrder(order = 2)
public class ApprovedOrderProcessor extends AbstractOrderProcessor{

     @Override
     public void processOrder(Order order, OrderContext context) {
          // 处理已批准的订单逻辑
          System.out.println("批准订单: " + order.getId());
          // 可以通过上下文对象访问共享信息
          System.out.println("Context: " + context.getInfo());
     }
}
3.3 发货处理器
java 复制代码
@Component
@HandlerOrder(order = 3)
public class ShippedOrderProcessor extends AbstractOrderProcessor{

    @Override
    public void processOrder(Order order, OrderContext context) {
         // 处理已发货的订单逻辑
         System.out.println("订单发货: " + order.getId());
         // 可以通过上下文对象访问共享信息
         System.out.println("Context: " + context.getInfo());
    }
}

4. 订单责任链

java 复制代码
@Component
public class OrderProcessorChain {

    private final ApplicationContext applicationContext;

    Collection<OrderProcessor> processors = new ArrayList<>();

    // 注入 ApplicationContext
    public OrderProcessorChain(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        // 获取所有订单处理器
        this.processors = applicationContext.getBeansOfType(OrderProcessor.class).values();
    }
	// 根据注解值进行排序
    protected List<OrderProcessor> getProcessors() {
        return this.processors.stream()
                .sorted((o1, o2) -> {
                    HandlerOrder order1 = o1.getClass().getAnnotation(HandlerOrder.class);
                    HandlerOrder order2 = o2.getClass().getAnnotation(HandlerOrder.class);
                    if (order1 == null && order2 == null) {
                        return 0; // 如果两者都没有注解,则认为它们相等
                    } else if (order1 == null) {
                        return 1; // 如果 o1 没有注解但 o2 有注解,则 o1 排在 o2 前面
                    } else if (order2 == null) {
                        return -1; // 如果 o1 有注解但 o2 没有注解,则 o1 排在 o2 后面
                    } else {
                        return Integer.compare(order1.order(), order2.order()); // 比较两个注解的 order 属性
                    }
                })
                .collect(Collectors.toList());
    }

    public void process(Order order, OrderContext context) {

        List<OrderProcessor> processors = getProcessors();
        // 依次调用订单处理器处理订单
        for (OrderProcessor processor : processors) {
            processor.preRequestHandler(order);
            processor.processOrder(order, context);
        }
    }
}

5. 实体类和注解

java 复制代码
@Data
public class Order { 
    private int id;
}

@Data
public class OrderContext {
    private String info; // 上下文信息
}

// 指定注解的保留策略
@Retention(RetentionPolicy.RUNTIME)
public @interface HandlerOrder {
    
    int order();
}

6. 测试

java 复制代码
@SpringBootTest
public class OrderProcessorChainTest {

    @Autowired
    private OrderProcessorChain processorChain;

    @Test
    public void testOrderProcessing() {
        // 创建订单
        Order order = new Order();
        order.setId(123);

        // 创建订单上下文对象
        OrderContext context = new OrderContext();
        context.setInfo("Some info for processing orders");

        // 处理订单
        processorChain.process(order, context);

    }
}

结果:

采购员对订单统一处理

开始订单处理: 123

Context: Some info for processing orders

采购员对订单统一处理

批准订单: 123

Context: Some info for processing orders

采购员对订单统一处理

订单发货: 123

Context: Some info for processing orders

三、Spring过滤器链

责任链在Spring中怎么运用呢?

在Spring框架中,责任链模式常常运用在拦截器(Interceptor)和过滤器(Filter)等场景中,用于处理HTTP请求、消息传递等。

拦截器(Interceptor):在Spring MVC中,拦截器用于在处理请求之前或之后执行一些操作,比如权限检查、日志记录等。你可以定义多个拦截器,它们按照顺序构成一个责任链。当一个请求到达时,会依次执行每个拦截器的预处理方法和后处理方法,类似于责任链模式的行为。

过滤器(Filter):在Spring框架中,你也可以使用Servlet过滤器来对HTTP请求进行预处理或后处理。过滤器链在Servlet容器中会按照配置顺序依次执行,也可以看作是一个责任链模式的应用。

事件监听器(Event Listener):Spring框架提供了事件监听器机制,你可以定义自己的事件和监听器,并通过Spring容器进行管理。当某个事件发生时,监听器会按照注册顺序依次被调用,类似于责任链模式。

下面是一个简单的Spring拦截器示例:

1. 定义拦截器

java 复制代码
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 在请求处理之前执行的操作,比如权限检查,未登录则直接拦截
        if (!checkAuth(request)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }
        // 放行
        return true;
    }

	// 可以不重写该方法
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 在请求处理之后执行的操作
    }

	// 可以不重写该方法
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 在请求完成之后执行的操作
    }

    private boolean checkAuth(HttpServletRequest request) {
        // 检查用户是否有权限
        // 这里假设简单地检查请求中是否包含合法的身份验证信息
        String authToken = request.getHeader("Authorization");
        return authToken != null && authToken.equals("valid_token");
    }
}

在这个示例中, LoginInterceptor 类实现了 HandlerInterceptor 接口,它是Spring MVC中的拦截器。preHandle 方法用于在请求处理之前执行操作,比如权限检查;postHandle 和 afterCompletion 方法分别用于在请求处理之后执行操作。多个拦截器可以组成一个责任链,按照它们的注册顺序依次执行。

代码中只是提供了拦截器的实现,但是并没有形成责任链,那么多个拦截器是如何形成链路并进行请求的处理的呢?

2. 过滤器链配置

java 复制代码
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private EmployeeService employeeService;


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 登录拦截器
        registry.addInterceptor(new LoginInterceptor(stringRedisTemplate))
                .excludePathPatterns(
                        "/user/login",
                        "/employee/login"
                ).order(2);

        // 管理员拦截器,用户可操作的功能都排除
        registry.addInterceptor(new AdminLoginInterceptor(employeeService))
                .excludePathPatterns(
                        "/user/login",
                        "/user/logout"
                ).order(3);

        // token刷新的拦截器,order值越小,越先执行,这个拦截器对所有请求都执行
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(1);
    }


}

在上面的代码中MvcConfig 实现了 WebMvcConfigurer 类,并且通过addInterceptors方法项过滤器链中增加了两个拦截器LoginInterceptorAdminLoginInterceptor。这两个拦截器通过order指定了拦截器的顺序。两个拦截器在请求过来时都会触发,拦截器会根据自身配置的拦截路径做过滤处理。

我们可以来看下 WebMvcConfigurer接口的定义:接口定义了很多方法,其中有一个addInterceptors方法就是将拦截器添加到链路中的。这个接口其实有点像我们在责任链入门中讲到的定义抽象处理器接口中的setNextHandler ,通过registry.addInterceptor()来将处理器进行组织。

java 复制代码
public interface WebMvcConfigurer {
  
    ... 
    default void addInterceptors(InterceptorRegistry registry) {
    }
}

InterceptorRegistry类中会通过addInterceptor方法将处理器添加到集合中去。

java 复制代码
public class InterceptorRegistry {
    private final List<InterceptorRegistration> registrations = new ArrayList();
    private static final Comparator<Object> INTERCEPTOR_ORDER_COMPARATOR;

    public InterceptorRegistry() {
    }

    public InterceptorRegistration addInterceptor(HandlerInterceptor interceptor) {
        InterceptorRegistration registration = new InterceptorRegistration(interceptor);
        this.registrations.add(registration);
        return registration;
    }
}

通过addInterceptors方法将拦截器添加到Spring MVC的拦截器链中。当Spring MVC接收到一个请求时,它会通过DispatcherServlet进行处理。DispatcherServlet会调用拦截器链中的每个拦截器来处理请求。

具体来说,以下是请求如何经过拦截器链被拦截器处理的过程:

当一个请求到达DispatcherServlet时,DispatcherServlet会创建一个用于处理该请求的HandlerExecutionChain。

在创建HandlerExecutionChain的过程中,DispatcherServlet会检查是否存在拦截器链。如果存在,DispatcherServlet会将该请求与拦截器链关联起来。

在处理请求之前,DispatcherServlet会依次调用拦截器链中每个拦截器的preHandle方法。拦截器的preHandle方法可以用来进行预处理,比如身份验证、日志记录等操作。

如果拦截器链中的所有拦截器的preHandle方法都返回true,表示请求可以继续处理。此时,DispatcherServlet会调用与请求匹配的处理器(Controller)来处理请求。

处理器处理完请求后,DispatcherServlet会依次调用拦截器链中每个拦截器的postHandle方法。拦截器的postHandle方法可以用来进行后处理,比如修改响应内容等操作。

最后,DispatcherServlet会依次调用拦截器链中每个拦截器的afterCompletion方法。拦截器的afterCompletion方法会在请求完成后被调用,无论请求是否成功处理。

通过这个过程,拦截器能够在请求处理的不同阶段进行干预,实现诸如权限验证、日志记录、异常处理等功能。拦截器链的顺序由拦截器的order方法决定,数值越小的拦截器优先级越高。

四、总结

关于责任链模式,必须要有处理器模板,要有链,链可以是集合,也可以是类似链表。请求就会沿着这条链依次被链上处理器处理。

相关推荐
思忖小下6 小时前
梳理你的思路(从OOP到架构设计)_简介设计模式
设计模式·架构·eit
liyinuo20179 小时前
嵌入式(单片机方向)面试题总结
嵌入式硬件·设计模式·面试·设计规范
aaasssdddd9611 小时前
C++的封装(十四):《设计模式》这本书
数据结构·c++·设计模式
T1an-111 小时前
设计模式之【观察者模式】
观察者模式·设计模式
思忖小下13 小时前
梳理你的思路(从OOP到架构设计)_设计模式Factory Method模式
设计模式·工厂方法模式·eit
霁月风14 小时前
设计模式——工厂方法模式
c++·设计模式·工厂方法模式
发飙的蜗牛'16 小时前
23种设计模式
android·java·设计模式
NorthCastle1 天前
设计模式-创建型模式-简单工厂模式详解
设计模式·简单工厂模式
越甲八千1 天前
重拾设计模式-外观模式和适配器模式的异同
设计模式·适配器模式·外观模式
越甲八千1 天前
重拾设计模式--适配器模式
设计模式·适配器模式