责任链模式设计说明文档一、责任链模式简介责任链模式是一种行为型设计模式它的核心思想是将多个处理逻辑按照一定顺序串联起来请求进入后依次经过这些处理器每个处理器只负责自己的校验或处理逻辑。在业务开发中责任链模式非常适合处理以下场景参数校验业务规则校验权限校验风控校验订单状态流转校验多步骤审批流程多条件过滤处理相比把所有判断逻辑都写在一个方法里责任链模式可以让每个处理器只关注自己的职责从而提升代码的扩展性和可维护性。二、本案例实现目标本案例通过责任链模式实现对PersonDto参数的校验。接口请求如下PostMapping(/test) public String test(RequestBody PersonDto personDto){ marchant.handler(666, personDto); return success; }请求参数PersonDto包含两个字段Data public class PersonDto { /** * 测试名称 */ private String name; /** * 测试年龄 */ private Integer age; }当前责任链主要包含两个校验节点处理器作用执行顺序AgeHandler校验年龄是否合法0NameHandler校验名字是否合法10当请求进入/test接口后会根据责任链标识666找到对应的一组处理器然后按照getOrder()的顺序依次执行。三、核心类说明1. PersonController请求入口RestController public class PersonController { Resource PersonHandlerContext marchant; PostMapping(/test) public String test(RequestBody PersonDto personDto){ marchant.handler(666, personDto); return success; } }作用说明PersonController是接口入口负责接收前端传入的请求参数。在test()方法中调用了marchant.handler(666, personDto);这里的666是责任链分组标识。也就是说系统会执行所有mark()返回666的责任链处理器。2. PersonDto请求参数对象Data public class PersonDto { private String name; private Integer age; }作用说明PersonDto是接口接收的业务参数对象。示例请求参数{ name: ZHANGSAN, age: 18 }该对象会被传入责任链由不同的处理器分别进行校验。3. AgeEnum年龄枚举RequiredArgsConstructor public enum AgeEnum { EIGHTTEEN(18), NINETEEN(19); Getter private final int age; }作用说明AgeEnum用来维护系统允许的年龄值。当前合法年龄包括18 19如果请求中的age不属于枚举范围则会被AgeHandler拦截。4. NameEnum名称枚举public enum NameEnum { ZHANGSAN, LISI; Override public String toString() { return this.name(); } }作用说明NameEnum用来维护系统允许的名称值。当前合法名称包括ZHANGSAN LISI如果请求中的name不属于枚举范围则会被NameHandler拦截。四、责任链接口设计Person 接口public interface PersonT extends Ordered { /** * 责任校验方法 */ void handle(T s); /** * 责任链名称 */ String mark(); }这是整个责任链模式的核心接口。所有责任链处理器都需要实现这个接口。接口方法说明1. handle(T s)void handle(T s);该方法表示当前责任节点的具体处理逻辑。比如AgeHandler负责校验年龄NameHandler负责校验名称每一个处理器只处理自己负责的业务逻辑。2. mark()String mark();该方法用于标识当前处理器属于哪一条责任链。例如Override public String mark() { return 666; }多个处理器返回同一个mark说明它们属于同一条责任链。3. getOrder()由于PersonT继承了 Spring 的Ordered接口所以每个责任链处理器都需要实现int getOrder();该方法用于控制处理器执行顺序。规则是数值越小优先级越高越先执行。例如AgeHandler.getOrder() 0 NameHandler.getOrder() 10所以执行顺序是AgeHandler - NameHandler五、具体责任链处理器1. AgeHandler年龄校验处理器Component public class AgeHandler implements PersonPersonDto { Override public void handle(PersonDto s) { boolean ageAnyMatch Arrays.stream(AgeEnum.values()) .anyMatch(enumConstant - enumConstant.getAge() s.getAge()); if (!ageAnyMatch) { throw new RuntimeException(年龄异常); } } Override public String mark() { return 666; } Override public int getOrder() { return 0; } }作用说明AgeHandler负责校验请求中的年龄是否合法。核心逻辑Arrays.stream(AgeEnum.values()) .anyMatch(enumConstant - enumConstant.getAge() s.getAge());这段代码会遍历AgeEnum中的所有枚举值判断请求中的age是否存在于枚举中。如果不存在则抛出异常throw new RuntimeException(年龄异常);年龄校验示例合法请求{ name: ZHANGSAN, age: 18 }非法请求{ name: ZHANGSAN, age: 20 }如果传入age 20因为AgeEnum中只定义了18和19所以会抛出年龄异常2. NameHandler名称校验处理器Component public class NameHandler implements PersonPersonDto { Override public void handle(PersonDto s) { boolean nameAnyMatch Arrays.stream(NameEnum.values()) .anyMatch(enumConstant - Objects.equals(enumConstant.toString(), s.getName())); if (!nameAnyMatch) { throw new RuntimeException(名字校验错误); } } Override public String mark() { return 666; } Override public int getOrder() { return 10; } }作用说明NameHandler负责校验请求中的名称是否合法。核心逻辑Arrays.stream(NameEnum.values()) .anyMatch(enumConstant - Objects.equals(enumConstant.toString(), s.getName()));该逻辑会遍历NameEnum中的所有枚举值判断传入的name是否在允许范围内。如果不存在则抛出异常throw new RuntimeException(名字校验错误);名称校验示例合法请求{ name: ZHANGSAN, age: 18 }非法请求{ name: WANGWU, age: 18 }如果传入name WANGWU因为NameEnum中只定义了ZHANGSAN和LISI所以会抛出名字校验错误六、责任链上下文容器PersonHandlerContextComponent public class PersonHandlerContextT implements ApplicationContextAware, CommandLineRunner { /** * 应用上下文 * */ private ApplicationContext applicationContext; /** * 校验集合 * */ private final MapString, ListPerson verifynew HashMap(); public void handler(String mark,T requestParam){ // 根据 mark 标识从责任链容器中获取一组责任链实现 Bean 集合 ListPerson abstractChainHandlers verify.get(mark); if (CollectionUtils.isEmpty(abstractChainHandlers)) { throw new RuntimeException(String.format([%s] 责任链未定义, mark)); } abstractChainHandlers.forEach(each - each.handle(requestParam)); } /** * 项目启动前注入责任链bean * */ Override public void run(String... args) throws Exception { MapString, Person beansOfType applicationContext.getBeansOfType(Person.class); beansOfType.forEach((beanName, bean) - { // 判断 Mark 是否已经存在抽象责任链容器中如果已经存在直接向集合新增如果不存在创建 Mark 和对应的集合 ListPerson abstractChainHandlers verify.getOrDefault(bean.mark(), new ArrayList()); abstractChainHandlers.add(bean); verify.put(bean.mark(), abstractChainHandlers); }); verify.forEach((mark, unsortedChainHandlers) - { // 对每个 Mark 对应的责任链实现类集合进行排序优先级小的在前 unsortedChainHandlers.sort(Comparator.comparing(Ordered::getOrder)); }); } /** * 注入上下文 * */ Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContextapplicationContext; } }这是整套责任链的核心调度类。它的主要作用是从 Spring 容器中获取所有Person类型的 Bean。按照mark()对处理器进行分组。按照getOrder()对同组处理器排序。请求进来时根据mark找到对应责任链并依次执行。七、责任链初始化流程项目启动时PersonHandlerContext会执行run()方法。因为它实现了CommandLineRunner所以 Spring Boot 启动完成后会自动调用run(String... args)初始化第一步获取所有责任链 BeanMapString, Person beansOfType applicationContext.getBeansOfType(Person.class);这行代码会从 Spring 容器中获取所有实现了Person接口的 Bean。当前项目中会获取到AgeHandler NameHandler初始化第二步按照 mark 分组beansOfType.forEach((beanName, bean) - { ListPerson abstractChainHandlers verify.getOrDefault(bean.mark(), new ArrayList()); abstractChainHandlers.add(bean); verify.put(bean.mark(), abstractChainHandlers); });每个处理器都会通过mark()方法返回自己的分组标识。因为AgeHandler和NameHandler的mark()都返回666所以最终会形成如下结构{ 666: [ AgeHandler, NameHandler ] }这个Map就是责任链容器。初始化第三步按照 getOrder 排序verify.forEach((mark, unsortedChainHandlers) - { unsortedChainHandlers.sort(Comparator.comparing(Ordered::getOrder)); });由于每个处理器都实现了Ordered接口所以可以根据getOrder()排序。当前顺序如下处理器getOrder()AgeHandler0NameHandler10因此最终执行顺序为AgeHandler - NameHandler八、请求执行流程当调用接口POST /test请求体{ name: ZHANGSAN, age: 18 }执行流程如下1. 前端请求 /test 接口 2. Spring MVC 将 JSON 参数封装成 PersonDto 3. Controller 调用 marchant.handler(666, personDto) 4. PersonHandlerContext 根据 mark 666 获取责任链列表 5. 执行 AgeHandler校验年龄 6. 执行 NameHandler校验名称 7. 所有处理器执行成功后接口返回 success流程图请求进入 | v PersonController.test() | v PersonHandlerContext.handler(666, personDto) | v 根据 mark 获取责任链集合 | v AgeHandler.handle() | v NameHandler.handle() | v 返回 success九、异常执行流程如果请求参数为{ name: ZHANGSAN, age: 20 }执行流程如下1. 请求进入 Controller 2. 调用 handler(666, personDto) 3. 找到 mark 666 的责任链 4. 执行 AgeHandler 5. AgeHandler 校验失败 6. 抛出 RuntimeException(年龄异常) 7. 后续 NameHandler 不再执行也就是说责任链中某个节点抛出异常后后续节点不会继续执行。这符合参数校验场景的常见设计。十、当前代码体现的设计思想1. 单一职责原则每个处理器只负责一个校验逻辑。例如AgeHandler 只负责年龄校验 NameHandler 只负责名称校验这样做的好处是代码更清晰修改某个校验逻辑不会影响其他处理器新增校验逻辑时只需要新增类不需要大规模修改原代码2. 开闭原则如果后续需要新增一个性别校验只需要新增一个处理器Component public class GenderHandler implements PersonPersonDto { Override public void handle(PersonDto s) { // 性别校验逻辑 } Override public String mark() { return 666; } Override public int getOrder() { return 20; } }不需要修改PersonHandlerContext的代码也不需要修改原来的AgeHandler和NameHandler。这就是对扩展开放对修改关闭。3. 解耦业务逻辑如果没有责任链模式代码可能会写成这样PostMapping(/test) public String test(RequestBody PersonDto personDto) { if (personDto.getAge() ! 18 personDto.getAge() ! 19) { throw new RuntimeException(年龄异常); } if (!ZHANGSAN.equals(personDto.getName()) !LISI.equals(personDto.getName())) { throw new RuntimeException(名字校验错误); } return success; }这样写的问题是Controller 逻辑臃肿所有校验逻辑耦合在一起后期新增规则会不断修改 Controller不利于维护和扩展使用责任链之后Controller 只负责调用marchant.handler(666, personDto);具体校验逻辑交给各个 Handler 处理。十一、责任链模式的优点1. 扩展性强新增一个处理节点只需要新增一个实现类。例如新增手机号校验Component public class PhoneHandler implements PersonPersonDto { Override public void handle(PersonDto s) { // 手机号校验 } Override public String mark() { return 666; } Override public int getOrder() { return 30; } }Spring 启动时会自动扫描该 Bean并加入责任链。2. 代码结构清晰每个 Handler 只负责自己的逻辑。AgeHandler - 年龄校验 NameHandler - 名称校验 PhoneHandler - 手机号校验相比大量if else责任链模式更加清晰。3. 支持动态分组通过mark()可以定义多条责任链。例如Override public String mark() { return user_register; }Override public String mark() { return order_create; }这样可以支持不同业务场景使用不同的责任链。示例handler(user_register, userDto); handler(order_create, orderDto);4. 支持顺序控制通过getOrder()可以灵活控制执行顺序。例如参数非空校验 - 枚举合法性校验 - 业务规则校验 - 数据库唯一性校验可以设置成处理器顺序NotNullHandler0EnumCheckHandler10BusinessRuleHandler20DatabaseCheckHandler30这样可以保证基础校验先执行复杂校验后执行。十二、适合在项目中的应用场景这套责任链写法非常适合用于以下业务场景。1. 用户注册校验手机号格式校验 验证码校验 密码强度校验 用户是否已存在校验 邀请人是否合法校验2. 订单创建校验商品是否存在 库存是否充足 用户状态是否正常 优惠券是否可用 订单金额是否正确3. 支付前校验订单是否存在 订单状态是否待支付 支付金额是否正确 支付渠道是否可用 是否存在重复支付4. 审批流程校验申请人权限校验 部门预算校验 审批金额校验 审批模板校验 附件完整性校验十五、总结本代码通过 Spring Boot 和责任链模式实现了一套灵活的参数校验机制。整体设计思路如下Controller 接收请求 | v PersonHandlerContext 根据 mark 获取责任链 | v 按照 getOrder 排序执行 Handler | v 每个 Handler 负责一个独立校验逻辑 | v 全部校验通过后返回成功这套设计的优点是降低 Controller 代码复杂度避免大量if else每个校验逻辑独立维护新增校验规则只需要新增 Handler支持多条责任链分组支持执行顺序控制符合开闭原则和单一职责原则因此该代码可以作为一个通用的责任链模板在用户校验、订单校验、支付校验、审批校验等场景中复用。