一、前言

在之前的技术探索中,我们深入剖析了工厂方法模式与策略模式,前者聚焦于对象创建过程的解耦,让对象的生成逻辑更加灵活可维护;后者则专注于算法的封装与切换,使得系统在面对不同业务规则时能够轻松应对 ,极大地提升了代码的可扩展性和可维护性。在理解这些设计模式的基础上,今天我们将一同踏入行为型设计模式的新领域 —— 责任链模式(Chain of Responsibility)。​

责任链模式作为行为型设计模式家族中的重要成员,其核心思想在于巧妙地解耦请求发送者与接收者之间的强关联关系。在责任链模式中,多个处理对象会被串联起来,形成一条像链条一样的结构。

当一个请求产生时,它会如同多米诺骨牌被推倒后产生的连锁反应一般,沿着这条责任链自动传递下去。每一个处理对象都有机会对请求进行处理,如果它无法处理或者处理完成后需要后续处理,就会将请求传递给链条中的下一个对象,直到请求被成功处理为止。这种模式不仅提升了系统的灵活性和可维护性,还使得代码结构更加清晰,易于扩展和修改。

二、责任链模式原型设计及说明 

在深入探讨责任链模式的代码实现和应用场景之前,我们先来通过 UML 类图直观地了解其结构。以下是使用 PlantUML 绘制的责任链模式 UML 类图:

在这个 UML 类图中:​

  • Handler是一个接口,它定义了两个重要的方法:setNext(handler: Handler)用于设置责任链中的下一个处理器,handle(request: String)用于处理请求。这个接口是责任链模式的基础,它规范了所有处理器必须具备的行为。​
  • AbstractHandler是一个抽象类,它实现了Handler接口。在AbstractHandler中,有一个私有成员变量nextHandler,用于存储责任链中的下一个处理器。AbstractHandler实现了setNext(handler: Handler)方法,用于设置下一个处理器。同时,它还提供了一个默认的handle(request: String)方法实现,在这个方法中,它会先判断自己是否能够处理请求,如果可以则进行处理,否则将请求传递给下一个处理器。具体的处理逻辑由子类实现,这种设计方式体现了模板方法模式的思想,将通用的处理流程放在抽象类中,而将具体的处理逻辑留给子类去实现,提高了代码的复用性和可扩展性。​
  • ConcreteHandlerA和ConcreteHandlerB是具体的处理器类,它们继承自AbstractHandler。在这两个类中,会实现handle(request: String)方法,根据自身的业务逻辑来判断是否能够处理请求。如果能够处理,则执行相应的处理逻辑;如果不能处理,则调用父类的handle方法,将请求传递给下一个处理器。​
  • Client代表客户端,它是责任链模式的使用者。在客户端中,会创建具体的处理器对象,并通过setNext方法将它们连接成一条责任链。然后,客户端调用责任链中第一个处理器的handle方法,发起请求,请求会沿着责任链依次传递,直到被某个处理器处理。

因此在责任链模式中,我们可以看到它的核心角色主要有3个:

✅Handler(抽象处理器):作为责任链模式的核心抽象,Handler定义了处理请求的统一接口规范。其中的setNext方法是构建责任链结构的关键,它使得各个处理器之间能够建立起链式关系,如同搭建起一条数据传递的通道。

而handle方法则是处理请求的抽象行为定义,为具体处理器提供了处理请求的契约。通过这种抽象定义,使得责任链模式具有高度的灵活性和可扩展性,不同的具体处理器可以根据自身的业务需求来实现handle方法,完成特定的处理逻辑 ,并且可以方便地在责任链中添加或移除处理器。​

✅ConcreteHandler(具体处理器):具体处理器是责任链模式中真正执行处理逻辑的角色。它们继承自AbstractHandler,实现了抽象的handle方法。在handle方法的实现中,每个具体处理器会根据自身的职责和业务规则来判断是否能够处理接收到的请求。

如果可以处理,就会执行具体的处理操作,例如进行数据验证、业务逻辑计算等;如果无法处理,就会按照责任链的传递规则,将请求转发给下一个处理器,即调用nextHandler的handle方法。这种机制实现了处理逻辑的分散和职责的分离,每个具体处理器只关注自己能够处理的部分,提高了代码的内聚性和可维护性。​

✅Client(客户端):客户端在责任链模式中扮演着发起请求和构建责任链的重要角色。

在客户端代码中,首先会创建一系列具体处理器的实例,这些实例将构成责任链的各个节点。然后,通过调用setNext方法,按照一定的顺序将这些处理器连接起来,形成一条完整的责任链,这个顺序的确定通常取决于业务需求和处理逻辑的先后顺序。最后,客户端通过调用责任链头部处理器的handle方法来触发请求的处理流程,请求会沿着责任链依次传递,由各个处理器进行处理,客户端不需要关心具体的处理过程和哪个处理器最终处理了请求,只需要关注请求的发起和最终的处理结果 ,这就实现了请求发送者与处理者之间的解耦。

三、责任链模式的适用场景与框架实践​

3.1 典型应用场景​

✅多级审批 / 权限校验场景:在许多企业级应用中,特别是 OA 系统中,审批流程是一个常见的业务场景。

以请假审批为例,员工提交请假申请后,请求会沿着由直属领导、部门经理、总经理等构成的责任链依次传递。直属领导首先判断请假天数是否在自己的审批权限内,如果是则进行审批,若超出权限则将请求传递给部门经理;部门经理按照同样的逻辑处理,若仍无法处理再传递给总经理,以此类推,直到请求被处理 ,这种方式不仅清晰地划分了不同层级的职责,还能确保审批流程的规范性和灵活性,同时也便于对审批流程进行管理和监控。​

✅可插拔处理流程:在 Web 开发中,过滤器链是责任链模式的典型应用。以 Java Web 开发中的 Servlet 过滤器为例。首先,定义一个日志记录过滤器LoggingFilter:

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter(filterName = "LoggingFilter")
public class LoggingFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化操作
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("记录请求信息: " + servletRequest);
        // 将请求传递给下一个过滤器
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        // 销毁操作
    }
}

然后,定义一个身份验证过滤器AuthenticationFilter:

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter(filterName = "AuthenticationFilter")
public class AuthenticationFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化操作
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        // 简单的身份验证逻辑示例,这里假设请求头中有一个名为"Authorization"的字段表示已认证
        if (request.getHeader("Authorization") != null) {
            // 身份验证通过,传递请求
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }
    }

    @Override
    public void destroy() {
        // 销毁操作
    }
}

 最后,定义一个字符编码过滤器CharacterEncodingFilter:

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter(filterName = "CharacterEncodingFilter")
public class CharacterEncodingFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化操作
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        servletRequest.setCharacterEncoding("UTF-8");
        // 将请求传递给下一个过滤器或目标Servlet
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        // 销毁操作
    }
}

当一个 HTTP 请求到达服务器时,它会首先进入日志记录过滤器LoggingFilter,该过滤器记录请求的相关信息后,将请求传递给身份验证过滤器AuthenticationFilter;身份验证过滤器验证用户身份,如果验证通过则将请求传递给字符编码过滤器CharacterEncodingFilter,设置请求的字符编码;最后请求到达目标 Servlet 进行业务处理。​

通过这种方式,可以方便地添加或移除过滤器,实现处理流程的动态组合和扩展,提高系统的可维护性和可扩展性 ,同时也使得每个过滤器的职责单一,便于开发和调试。例如,如果需要新增一个权限校验过滤器,只需要定义一个新的过滤器类并配置到过滤器链中即可,对原有过滤器和业务逻辑几乎没有影响。

✅异常处理的多层捕获机制:在 Java 编程中,异常处理的多层捕获机制也体现了责任链模式的思想。在 Java 编程中,异常处理的多层捕获机制也体现了责任链模式的思想。当一个异常发生时,首先由最内层的 try - catch 块尝试捕获处理。以文件读取操作为例,假设我们要读取一个文件中的内容并进行处理,代码如下:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class ExceptionHandlingExample {
    public static void main(String[] args) {
        String filePath = "example.txt";
        try {
            // 最外层try块,主要处理文件读取相关的异常
            BufferedReader reader = new BufferedReader(new FileReader(filePath));
            try {
                // 内层try块,主要处理读取行时可能出现的空指针异常(假设文件为空等情况)
                String line;
                while ((line = reader.readLine()) != null) {
                    try {
                        // 更内层try块,处理对读取内容进行数字转换时可能出现的异常
                        int num = Integer.parseInt(line);
                        System.out.println("转换后的数字: " + num);
                    } catch (NumberFormatException e) {
                        // 处理数字转换异常
                        System.out.println("无法将内容转换为数字: " + line);
                    }
                }
            } catch (NullPointerException e) {
                // 处理读取行时的空指针异常
                System.out.println("读取文件内容时出现空指针异常");
            } finally {
                try {
                    // 关闭文件流
                    reader.close();
                } catch (IOException e) {
                    // 处理关闭文件流时的异常
                    System.out.println("关闭文件流时出现异常");
                }
            }
        } catch (IOException e) {
            // 处理文件读取异常
            System.out.println("无法读取文件: " + filePath);
        }
    }
}

 这样当一个异常发生时,首先由最内层的 try 块对应的 catch 块尝试捕获处理。例如,最内层 try 块中发生了NumberFormatException异常(当文件内容无法转换为数字时),它会首先被对应的 catch 块捕获并处理。

如果内层 try 块中发生了NullPointerException异常(如文件为空,读取到空行),内层的 catch 块无法处理,异常会被抛出到外层的 try - catch 块,由外层对应的 catch 块处理。如果外层的 catch 块也无法处理(如文件不存在,发生IOException),异常会继续被抛出到更外层的 try - catch 块,直到异常被成功处理或者被抛出到最外层,如果最外层也无法处理,则可能导致程序终止。

这种机制使得异常处理的逻辑更加清晰,每个 catch 块只需要关注自己能够处理的异常类型,提高了代码的健壮性和可读性 ,同时也符合责任链模式中请求在处理链上传递直到被处理的特点。

 3.2 责任链模式在开源框架的应用

(1)Spring Security 的 FilterChainProxy 过滤器链

在 Spring Security 框架中,FilterChainProxy 是核心组件之一,它负责管理和执行安全过滤器链。以一个简单的 Spring Boot 项目结合 Spring Security 为例,假设我们有一个 Web 应用,需要对某些接口进行身份验证和权限控制。 

首先,引入 Spring Security 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

然后,创建一个配置类来配置 FilterChainProxy 相关的过滤器链。比如:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
           .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
                .and()
           .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
           .logout()
                .permitAll();
    }

    @Bean
    @Override
    public InMemoryUserDetailsManager userDetailsService() {
        UserDetails user =
            User.withDefaultPasswordEncoder()
               .username("user")
               .password("password")
               .roles("USER")
               .build();

        return new InMemoryUserDetailsManager(user);
    }
}

这里我们用http.authorizeRequests() 部分定义了过滤器链的规则。antMatchers("/public/**").permitAll() 表示所有以 /public/ 开头的请求都允许匿名访问,anyRequest().authenticated() 表示其他所有请求都需要进行身份验证。​

当一个 HTTP 请求进入 Spring Security 保护的应用程序时,FilterChainProxy 会根据这些配置的安全规则,将请求依次传递给多个过滤器进行处理。例如,这里默认会使用 UsernamePasswordAuthenticationFilter 用于处理表单登录的身份验证。当用户访问需要认证的接口时,会被重定向到 /login 页面(由 formLogin().loginPage("/login") 配置),用户提交表单后,UsernamePasswordAuthenticationFilter 会拦截请求,进行用户名和密码的验证。​

此时如果我们需要添加自定义的权限检查过滤器,可以创建一个类实现 Filter 接口,然后在配置类中添加到过滤器链中:

import org.springframework.stereotype.Component;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Component
@WebFilter(filterName = "customPermissionFilter", urlPatterns = "/protected/*")
public class CustomPermissionFilter implements javax.servlet.Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        // 这里添加自定义的权限检查逻辑,例如检查用户角色是否有权限访问
        if (/* 用户有访问权限 */) {
            chain.doFilter(request, response);
        } else {
            // 返回权限不足的响应
            response.getWriter().write("Access Denied");
        }
    }
}

然后在配置类中添加这个过滤器到过滤器链:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.Filter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public Filter customPermissionFilter() {
        return new CustomPermissionFilter();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
           .addFilterBefore(customPermissionFilter(), FilterSecurityInterceptor.class)
           .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .antMatchers("/protected/**").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
           .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
           .logout()
                .permitAll();
    }

    @Bean
    @Override
    public InMemoryUserDetailsManager userDetailsService() {
        UserDetails user =
            User.withDefaultPasswordEncoder()
               .username("user")
               .password("password")
               .roles("USER")
               .build();

        UserDetails admin =
            User.withDefaultPasswordEncoder()
               .username("admin")
               .password("admin")
               .roles("ADMIN")
               .build();

        return new InMemoryUserDetailsManager(user, admin);
    }
}

在这个例子中,addFilterBefore(customPermissionFilter(), FilterSecurityInterceptor.class) 将自定义的 CustomPermissionFilter 添加到 FilterSecurityInterceptor 之前。CustomPermissionFilter 会在请求到达 FilterSecurityInterceptor 之前对请求进行处理,检查用户是否有访问权限。​

这种通过 FilterChainProxy 管理过滤器链的设计使得 Spring Security 的安全控制逻辑清晰,易于扩展和定制,用户可以根据实际需求添加自定义的过滤器到过滤器链中,以满足特定的安全需求,同时也提高了代码的复用性和可维护性。

(2)MyBatis 的 Interceptor 责任链插件机制

MyBatis 的插件机制利用责任链模式,通过 Interceptor 接口实现对 SQL 执行过程的拦截和处理。下面结合实际代码进行详细说明。我们首先定义一个简单的 Interceptor 示例:

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.Properties;

// 标记该类是一个插件,指定要拦截的接口、方法及参数
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class CustomInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("进入自定义拦截器,在SQL执行前干点事");
        // 执行原有的SQL查询逻辑
        Object result = invocation.proceed();
        System.out.println("SQL执行完毕,在结果返回前干点事");
        return result;
    }

    @Override
    public Object plugin(Object target) {
        // 使用Plugin.wrap方法包装目标对象,生成代理对象
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 设置插件属性
        System.out.println("设置插件属性: " + properties);
    }
}

这里我们通过CustomInterceptor 实现了 Interceptor 接口,并使用 @Intercepts 注解指定了要拦截的 Executor 接口的 query 方法。intercept 方法中,在调用 invocation.proceed() 前后添加了自定义逻辑,分别在 SQL 执行前和结果返回前打印日志。plugin 方法负责生成代理对象,setProperties 方法用于设置插件属性。

然后在 MyBatis 的配置文件(通常是mybatis-config.xml)中添加如下配置:

<configuration>
    <plugins>
        <plugin interceptor="com.example.CustomInterceptor">
            <property name="property1" value="value1"/>
            <property name="property2" value="value2"/>
        </plugin>
    </plugins>
</configuration>

这样就将自定义的 CustomInterceptor 插件配置到了 MyBatis 中,并可以通过<property>标签设置插件属性。​

在 MyBatis 执行 SQL 语句的过程中,会经过多个内部组件,如参数处理器(ParameterHandler)、语句处理器(StatementHandler)、结果集处理器(ResultSetHandler)等。

我们可以实现 Interceptor 接口,编写自定义的拦截逻辑,并将其添加到 InterceptorChain 中。当 MyBatis 执行 SQL 时,会按照 InterceptorChain 中拦截器的顺序,依次调用每个拦截器的 intercept 方法,对 SQL 执行过程进行拦截和处理。

例如,分页插件可以在 SQL 执行前添加分页相关的逻辑,数据权限插件可以在 SQL 执行时动态添加数据权限过滤条件等。这种机制使得 MyBatis 的功能得到了极大的扩展,用户可以根据实际需求定制 SQL 的执行逻辑,提高了 MyBatis 的灵活性和适应性 ,同时也体现了责任链模式在框架中的应用优势。

(3) Netty 的 ChannelHandler 处理管道

在 Netty 网络编程框架中,ChannelHandler 处理管道是责任链模式的重要应用。

ChannelHandler 是 Netty 中处理 I/O 事件的核心组件,它可以处理入站事件(如读取数据)和出站事件(如发送数据)。当一个 Channel(通道)建立后,会创建一个 ChannelPipeline(管道),ChannelPipeline 中包含了多个 ChannelHandler,这些 ChannelHandler 按照添加的顺序形成一条责任链。

当有 I/O 事件发生时,事件会首先被 ChannelPipeline 头部的 ChannelHandler 接收,然后按照责任链的顺序依次传递给后续的 ChannelHandler 进行处理。每个 ChannelHandler 可以根据自身的业务逻辑对事件进行处理,例如进行数据编解码、心跳检测、业务逻辑处理等。

这种设计使得 Netty 的 I/O 处理逻辑非常灵活和可扩展,用户可以方便地添加自定义的 ChannelHandler 到管道中,实现各种复杂的网络通信功能 ,同时也提高了 Netty 的性能和可靠性。 

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class NettyServer {
    public static void main(String[] args) throws Exception {
        // 定义两个线程组,bossGroup用于接收客户端连接,workerGroup用于处理I/O事件
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .childHandler(new ChannelInitializer<SocketChannel>() {
                  @Override
                  protected void initChannel(SocketChannel ch) throws Exception {
                      ChannelPipeline pipeline = ch.pipeline();
                      // 添加StringDecoder,用于将字节数据解码为字符串
                      pipeline.addLast(new StringDecoder());
                      // 添加StringEncoder,用于将字符串编码为字节数据
                      pipeline.addLast(new StringEncoder());
                      // 添加自定义的业务处理器MyServerHandler
                      pipeline.addLast(new MyServerHandler());
                  }
              });

            // 绑定端口并启动服务器
            ChannelFuture f = b.bind(8888).sync();
            System.out.println("Server started on port 8888");

            // 等待服务器关闭
            f.channel().closeFuture().sync();
        } finally {
            // 优雅关闭线程组
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class MyServerHandler extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println("Received message: " + msg);
        // 处理业务逻辑,这里简单打印接收到的消息
        ctx.writeAndFlush("Message received successfully!");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

这里我们首先创建了一个ServerBootstrap来配置和启动 Netty 服务器。在childHandler中,通过ChannelInitializer初始化SocketChannel的ChannelPipeline。这里添加了StringDecoder和StringEncoder用于数据的编解码,这是常见的入站和出站处理器。

然后添加了自定义的MyServerHandler,它继承自SimpleChannelInboundHandler,用于处理业务逻辑。当有数据从客户端发送过来时,会先经过StringDecoder解码,然后传递到MyServerHandler的channelRead0方法进行处理,处理完后再通过StringEncoder编码并发送响应给客户端。

这种通过在ChannelPipeline中添加多个ChannelHandler的方式,充分体现了 Netty 中 ChannelHandler 处理管道的责任链模式,使得代码逻辑清晰,易于扩展和维护。

3.3责任链模式实际应用中的开发建议 

✅控制链长度避免性能损耗:在实际的开发中,责任链的长度会对系统性能产生显著影响。当责任链过长时,请求在处理过程中需要经过多个处理器的传递和处理,这会增加系统的开销,导致请求处理时间变长,响应速度变慢。

为了避免这种情况,建议将责任链的节点数量控制在 10 个以内。在设计责任链时,需要充分考虑业务需求和处理逻辑,合理划分处理器的职责,避免不必要的处理器添加到责任链中。

同时,在系统运行过程中,可以通过性能监控工具实时监测责任链的处理时间和性能指标,及时发现并解决由于链过长导致的性能问题 ,确保系统的高效运行。​

✅明确终止条件防止死循环:在责任链模式中,每个处理器都需要判断自己是否能够处理请求,并决定是否将请求传递给下一个处理器。为了防止出现死循环,必须明确每个处理器的终止条件。

例如,在审批流程中,当请求到达具有最高审批权限的处理器时,无论是否能够处理请求,都应该终止请求的传递;在过滤器链中,当某个过滤器处理完请求后,如果不需要后续处理,也应该终止传递。在代码实现中,可以通过在处理器的处理方法中添加明确的条件判断来实现终止条件。

同时,在测试阶段,需要对责任链的各种情况进行全面测试,确保终止条件的正确性和有效性,避免出现死循环导致系统崩溃或资源耗尽的情况 ,保证系统的稳定性和可靠性。​

✅结合 Spring 容器管理处理器生命周期:在基于 Spring 框架的企业开发中,可以充分利用 Spring 容器的强大功能来管理责任链中处理器的生命周期。

Spring 容器提供了依赖注入(DI)和 Bean 管理的功能,可以方便地创建、配置和管理处理器对象。通过将处理器定义为 Spring 的 Bean,并利用依赖注入将它们连接成责任链,可以简化责任链的构建过程,提高代码的可维护性和可测试性。

  1. 同时,Spring 容器还负责管理 Bean 的生命周期,包括创建、初始化、销毁等过程,这使得处理器的生命周期管理更加规范和可靠。例如,可以在处理器的初始化方法中进行一些资源的初始化操作,在销毁方法中进行资源的释放操作。

此外,利用 Spring 的 AOP(面向切面编程)功能,还可以对处理器的执行过程进行切面增强,如添加日志记录、事务管理等功能 ,进一步提升系统的性能和可维护性。

三、责任链模式的简易应用实现:订单处理链 

在实际业务开发中,责任链模式常用于处理具有多个处理步骤且步骤执行顺序相对固定、每个步骤处理逻辑相对独立的业务场景。假设我们有一个电商订单处理系统,业务需求如下:​

  1. 订单校验:检查订单的基本信息,如商品信息是否完整、用户信息是否有效、订单金额是否合理等。只有通过校验的订单才能继续后续处理。​
  2. 折扣计算:根据用户等级、促销活动等规则计算订单可享受的折扣,更新订单金额。​
  3. 库存检查与扣减:检查商品库存是否充足,如果充足则扣减库存;若库存不足,订单处理中断并提示用户。​
  4. 记录日志:在订单处理完成后,记录订单处理的详细信息,包括订单号、处理步骤、处理结果等,用于后续的查询和分析。​

下面结合这些业务需求,我们来看看责任链模式怎么应用在实际开发中:

// 抽象处理器
public abstract class OrderHandler {

    @Autowired
    private OrderHandler next;

    public void handle(OrderContext context) {
        if (canHandle(context)) {
            process(context);
        }
        if (next != null) {
            next.handle(context);
        }
    }

    protected abstract boolean canHandle(OrderContext context);

    protected abstract void process(OrderContext context);
}

这里OrderHandler是抽象处理器类,它定义了责任链模式中处理器的通用结构和行为。其中,@Autowired注解用于自动装配下一个处理器next,通过这种方式实现了处理器之间的链式调用。

handle方法是处理请求的核心方法,它首先调用canHandle方法判断当前处理器是否能够处理请求,如果可以,则调用process方法执行具体的处理逻辑;如果当前处理器无法处理请求或者处理完成后需要继续传递,会检查next是否为空,如果不为空,则将请求传递给下一个处理器,调用next.handle(context) ,这种设计模式使得请求能够在责任链中依次传递,直到被某个处理器处理。

// 具体处理器实现(订单校验处理器)
@Service
public class ValidationHandler extends OrderHandler {

    protected boolean canHandle(OrderContext context) {
        return!context.isValidated();
    }

    protected void process(OrderContext context) {
        // 执行校验逻辑,这里简单示例校验商品信息是否为空
        if (context.getOrder().getGoodsList() != null &&!context.getOrder().getGoodsList().isEmpty()) {
            context.setValidated(true);
        } else {
            throw new RuntimeException("订单校验失败,商品信息为空");
        }
    }
}

ValidationHandler是具体处理器类,它继承自OrderHandler,用于实现订单的基础校验功能。canHandle方法通过判断OrderContext中的isValidated属性来确定当前处理器是否能够处理请求,如果isValidated为false,表示订单尚未经过校验,当前处理器可以处理;

process方法中包含了具体的校验逻辑,在实际实现中,会对订单的商品信息、用户信息、金额等进行详细校验。如果校验通过,会将OrderContext中的isValidated属性设置为true,表示订单已通过校验;如果校验失败,则抛出运行时异常,中断订单处理流程,提示用户订单校验出现问题 ,通过这种方式,确保只有通过基础校验的订单才能进入后续的处理环节。

// 具体处理器实现(折扣计算处理器)
@Service
public class DiscountHandler extends OrderHandler {

    protected boolean canHandle(OrderContext context) {
        return context.isValidated() &&!context.isDiscountCalculated();
    }

    protected void process(OrderContext context) {
        // 简单示例根据用户等级计算折扣
        if ("vip".equals(context.getOrder().getUser().getLevel())) {
            double discount = context.getOrder().getTotalAmount() * 0.1;
            context.getOrder().setTotalAmount(context.getOrder().getTotalAmount() - discount);
            context.setDiscountCalculated(true);
        } else {
            context.setDiscountCalculated(true);
        }
    }
}

DiscountHandler负责计算订单折扣。canHandle方法判断订单是否已通过校验且尚未计算折扣。process方法根据用户等级计算折扣并更新订单总金额。

// 具体处理器实现(库存检查与扣减处理器)
@Service
public class InventoryHandler extends OrderHandler {

    protected boolean canHandle(OrderContext context) {
        return context.isValidated() && context.isDiscountCalculated() &&!context.isInventoryChecked();
    }

    protected void process(OrderContext context) {
        // 简单示例检查库存是否充足,假设库存系统返回库存数量
        for (Goods goods : context.getOrder().getGoodsList()) {
            int stock = getStockFromInventorySystem(goods.getId());
            if (stock < goods.getQuantity()) {
                throw new RuntimeException("库存不足,商品:" + goods.getName());
            }
        }
        // 扣减库存,实际实现中需要与库存系统交互
        for (Goods goods : context.getOrder().getGoodsList()) {
            deductStockFromInventorySystem(goods.getId(), goods.getQuantity());
        }
        context.setInventoryChecked(true);
    }

    private int getStockFromInventorySystem(String goodsId) {
        // 模拟从库存系统获取库存数量
        return 100;
    }

    private void deductStockFromInventorySystem(String goodsId, int quantity) {
        // 模拟扣减库存操作
        System.out.println("扣减商品 " + goodsId + " 库存 " + quantity);
    }
}

InventoryHandler用于检查库存并扣减库存。canHandle方法确保订单已通过前面的处理步骤且尚未进行库存检查。process方法检查库存是否充足,若充足则扣减库存,否则抛出异常中断订单处理。

// 具体处理器实现(日志记录处理器)
@Service
public class LogHandler extends OrderHandler {

    protected boolean canHandle(OrderContext context) {
        return context.isValidated() && context.isDiscountCalculated() && context.isInventoryChecked() &&!context.isLogged();
    }

    protected void process(OrderContext context) {
        // 简单示例记录订单处理日志
        System.out.println("订单处理日志:订单号 " + context.getOrder().getOrderId() + " 处理完成");
        context.setLogged(true);
    }
}

LogHandler负责记录订单处理日志。canHandle方法判断订单是否已完成前面所有处理步骤且尚未记录日志。process方法记录订单处理日志。

// 链式装配(Spring配置类)
@Configuration
public class OrderChainConfig {

    @Bean
    public OrderHandler orderHandlerChain(
            ValidationHandler validation,
            DiscountHandler discount,
            InventoryHandler inventory,
            LogHandler log) {
        validation.setNext(discount);
        discount.setNext(inventory);
        inventory.setNext(log);
        return validation;
    }
}

最后在 Spring 配置类OrderChainConfig中,通过@Configuration注解标识这是一个配置类,@Bean注解定义了一个名为orderHandlerChain的 Bean,用于创建和装配订单处理责任链。

在方法内部,按照订单处理的业务顺序,依次调用各个处理器的setNext方法,将ValidationHandler、DiscountHandler、InventoryHandler和LogHandler连接成一条责任链,其中ValidationHandler作为责任链的起始节点,LogHandler作为末尾节点。

最后返回ValidationHandler,这样在其他地方获取到这个 Bean 时,就可以通过调用其handle方法来触发整个订单处理流程,实现了责任链的初始化和装配 ,使得订单处理的各个环节能够按照预定的顺序协同工作。​

通过上述代码实现,我们可以清晰地看到责任链模式如何将复杂的订单处理业务拆分成多个独立的处理步骤,每个步骤由一个具体处理器负责,通过责任链的方式依次处理,提高了代码的可维护性和扩展性。当有新的处理步骤或业务逻辑变更时,只需新增或修改相应的具体处理器类,而不会影响其他处理器的功能。

四、总结

4.1 模式核心价值​

✅解耦请求发起者与处理者的强依赖:在传统的请求处理方式中,请求发起者通常需要明确知道具体的处理者是谁,并直接与处理者进行交互,这就导致了两者之间存在紧密的耦合关系。

而责任链模式通过将请求沿着责任链进行传递,请求发起者只需要将请求发送到责任链上,无需关心具体是哪个处理者最终处理了请求 ,实现了请求发起者与处理者之间的解耦,降低了代码的耦合度,使得系统的各个部分能够更加独立地进行开发、维护和扩展。​

✅支持处理流程的动态组合与扩展:责任链模式的一大优势在于其能够支持处理流程的动态组合与扩展。

在实际应用中,业务需求往往是多变的,可能需要根据不同的场景和条件来调整处理流程。通过责任链模式,我们可以在运行时动态地添加、移除或调整处理器在责任链中的顺序,从而灵活地满足不同的业务需求。

例如,在电商订单处理的例子中,如果后续业务规则发生变化,需要在订单处理流程中新增一个赠品发放的环节,只需要创建一个新的处理器,并将其添加到责任链中合适的位置即可,无需对现有代码进行大规模的修改,极大地提高了系统的灵活性和可扩展性 。​

✅符合开闭原则,新增处理器不影响现有逻辑:开闭原则是面向对象设计中的重要原则之一,它要求软件实体(类、模块、函数等)对扩展开放,对修改关闭。

责任链模式很好地符合了这一原则,当需要新增处理器时,我们只需要创建一个新的具体处理器类,实现抽象处理器接口中定义的处理方法,并将其添加到责任链中,而不需要修改现有处理器的代码。

这种设计方式使得系统在面对新的业务需求时,能够通过扩展来满足,而不会对已有的稳定代码造成影响,提高了代码的可维护性和稳定性 ,同时也降低了系统维护的成本和风险。

4.2 延伸思考

在实际的软件开发过程中,我们常常会遇到一些复杂的业务场景,其中不仅需要按照一定的顺序处理请求,还需要根据不同的条件动态地调整处理顺序。

例如,在一个复杂的工作流系统中,不同类型的任务可能需要不同的处理流程,而且在某些情况下,还需要根据任务的优先级、资源的可用性等因素来动态地改变处理顺序。此时,单纯的责任链模式可能无法满足这些复杂的需求。​

为了解决这些问题,我们可以考虑将责任链模式与策略模式相结合。策略模式主要用于封装一系列可以相互替换的算法,使得在运行时可以根据不同的条件选择不同的算法。

在责任链模式中结合策略模式,可以为每个处理器提供多种处理策略,并且在运行时根据具体的业务条件动态地选择合适的策略来处理请求。通过这种方式,我们可以实现更加灵活和智能的责任链结构配置,使得系统能够更好地应对复杂多变的业务需求 。

当然了,策略模式我们暂时还没说到,后续在慢慢补充~

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐