1. 基本介绍
责任链是一种非常常见的设计模式, 具体我就不介绍了, 本文是讲解如何在SpringBoot中优雅的使用责任链模式
1.1. 代码执行流程
基本步骤如下 :
- SpringBoot启动时, 需要获取 handler 对应Bean, 不同业务对应着不同的多个处理器, 比如 购票业务, 可能需要检查参数是否为空, 检测参数是否合法, 检测是否重复购票等等, 所以需要一个 mark 用于标记当前业务, 这样才能把相同的handler放到一起
- 然后就是通过 mark 将不同的handler 放到一起, 具体查看 3.7 核心加载类
- 然后实现一个方法, 通过传入 mark 和 参数去批量执行 对应部分的代码
2. 项目创建
2.1. 项目结构
2.2. maven
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.5.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>cn.knightzz</groupId><artifactId>chain-responsibility-pattern-example</artifactId><version>0.0.1-SNAPSHOT</version><name>chain-responsibility-pattern-example</name><description>责任链模式demo</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
3. 代码编写
3.1. 实体类
这个类是用于存储实体类的
package cn.knightzz.pattern.dto.req;/*** @author 王天赐* @title: PurchaseTicketReqDTO* @description:* @create: 2023-08-29 18:09*/
public class PurchaseTicketReqDTO {}
3.2. 枚举类
package cn.knightzz.pattern.common.enums;/*** @author 王天赐* @title: TicketChainMarkEnum* @description: 存储标记责任链的注解* @create: 2023-08-29 18:10*/
public enum TicketChainMarkEnum {/*** 用于标记购票的责任链过滤器*/TRAIN_PURCHASE_TICKET_FILTER("train_purchase_ticket_filter");private String name;TicketChainMarkEnum(String name) {this.name = name;}
}
枚举类主要是用于标记某一类业务的责任链
3.3. 通用类
package cn.knightzz.pattern.context;import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;import java.lang.annotation.Annotation;
import java.util.Map;/*** @author 王天赐* @title: ApplicationContextHolder* @description:* @create: 2023-08-29 18:31*/
@Component
public class ApplicationContextHolder implements ApplicationContextAware {private static ApplicationContext CONTEXT;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {ApplicationContextHolder.CONTEXT = applicationContext;}/*** Get ioc container bean by type.** @param clazz* @param <T>* @return*/public static <T> T getBean(Class<T> clazz) {return CONTEXT.getBean(clazz);}/*** Get ioc container bean by name and type.** @param name* @param clazz* @param <T>* @return*/public static <T> T getBean(String name, Class<T> clazz) {return CONTEXT.getBean(name, clazz);}/*** Get a set of ioc container beans by type.** @param clazz* @param <T>* @return*/public static <T> Map<String, T> getBeansOfType(Class<T> clazz) {return CONTEXT.getBeansOfType(clazz);}/*** Find whether the bean has annotations.** @param beanName* @param annotationType* @param <A>* @return*/public static <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType) {return CONTEXT.findAnnotationOnBean(beanName, annotationType);}/*** Get ApplicationContext.** @return*/public static ApplicationContext getInstance() {return CONTEXT;}
}
ApplicationContextHolder 的作用是, 当Bean被创建时, 将Spring中存储Bean的容器注入到CONTEXT中, 这样我们就可以在其他类中使用 Bean
3.4. 通用责任链接口
package cn.knightzz.pattern.chain;import cn.knightzz.pattern.common.enums.TicketChainMarkEnum;
import org.springframework.core.Ordered;/*** @author 王天赐* @title: AbstractChainHandler* @description:* @create: 2023-08-29 18:15*/
public interface AbstractChainHandler<T> extends Ordered {/*** 执行责任链逻辑** @param requestParam 责任链执行入参*/void handler(T requestParam);/*** @return 责任链组件标识*/String mark();
}
3.5. 购票责任链接口
package cn.knightzz.pattern.filter;import cn.knightzz.pattern.chain.AbstractChainHandler;
import cn.knightzz.pattern.common.enums.TicketChainMarkEnum;
import cn.knightzz.pattern.dto.req.PurchaseTicketReqDTO;/*** @author 王天赐* @title: TrainPurchaseTicketChainFilter* @description:* @create: 2023-08-29 18:10*/
public interface TrainPurchaseTicketChainFilter<T extends PurchaseTicketReqDTO> extends AbstractChainHandler<PurchaseTicketReqDTO> {@Overridedefault String mark() {return TicketChainMarkEnum.TRAIN_PURCHASE_TICKET_FILTER.name();}
}
通过实现通过责任链接口, 编写默认 mark 方法, 用于标记当前责任链处理器集合
3.6. 购票责任链处理器
package cn.knightzz.pattern.filter.handler;import cn.knightzz.pattern.dto.req.PurchaseTicketReqDTO;
import cn.knightzz.pattern.filter.TrainPurchaseTicketChainFilter;
import org.springframework.stereotype.Component;/*** @author 王天赐* @title: TrainPurchaseTicketParamNotNullChainHandler* @description:* @create: 2023-08-29 18:18*/
@Component
public class TrainPurchaseTicketParamNotNullChainHandler implements TrainPurchaseTicketChainFilter<PurchaseTicketReqDTO> {@Overridepublic void handler(PurchaseTicketReqDTO requestParam) {System.out.println("参数不能为空 , 过滤器执行成功");}@Overridepublic int getOrder() {return 10;}
}
package cn.knightzz.pattern.filter.handler;import cn.knightzz.pattern.dto.req.PurchaseTicketReqDTO;
import cn.knightzz.pattern.filter.TrainPurchaseTicketChainFilter;
import org.springframework.stereotype.Component;/*** @author 王天赐* @title: TrainPurchaseTicketParamVerifyChainHandler* @description: 购票流程过滤器之验证参数是否有效* @create: 2023-08-29 18:23*/
@Component
public class TrainPurchaseTicketParamVerifyChainHandler implements TrainPurchaseTicketChainFilter<PurchaseTicketReqDTO> {@Overridepublic void handler(PurchaseTicketReqDTO requestParam) {System.out.println("参数合法 , 过滤器执行成功");}@Overridepublic int getOrder() {return 20;}
}
package cn.knightzz.pattern.filter.handler;import cn.knightzz.pattern.dto.req.PurchaseTicketReqDTO;
import cn.knightzz.pattern.filter.TrainPurchaseTicketChainFilter;
import org.springframework.stereotype.Component;/*** @author 王天赐* @title: TrainPurchaseTicketRepeatChainHandler* @description: 购票流程过滤器之验证乘客是否重复购买* @create: 2023-08-29 18:24*/
@Component
public class TrainPurchaseTicketRepeatChainHandler implements TrainPurchaseTicketChainFilter<PurchaseTicketReqDTO> {@Overridepublic void handler(PurchaseTicketReqDTO requestParam) {System.out.println("未重复购票 , 过滤器执行成功");}@Overridepublic int getOrder() {return 30;}
}
3.7. 核心加载类
package cn.knightzz.pattern.context;import cn.knightzz.pattern.chain.AbstractChainHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;import java.util.*;
import java.util.stream.Collectors;/*** @author 王天赐* @title: AbstractChainContext* @description: CommandLineRunner:SpringBoot 启动完成后执行的回调函数* @create: 2023-08-29 18:27*/
@Component
@Slf4j
public final class AbstractChainContext<T> implements CommandLineRunner {// CommandLineRunner:SpringBoot 启动完成后执行的回调函数// 存储责任链组件实现和责任链业务标识的容器// 比如:Key:购票验证过滤器 Val:HanlderA、HanlderB、HanlderC、......private final Map<String, List<AbstractChainHandler>> abstractChainHandlerContainer = new HashMap<>();public void handler(String mark, T requestParam) {List<AbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(mark);if (CollectionUtils.isEmpty(abstractChainHandlers)) {throw new RuntimeException(String.format("[%s] Chain of Responsibility ID is undefined.", mark));}abstractChainHandlers.forEach(each -> each.handler(requestParam));}@Overridepublic void run(String... args) throws Exception {// 通过ApplicationContextHolder获取所有的BeanMap<String, AbstractChainHandler> chainFilterMap =ApplicationContextHolder.getBeansOfType(AbstractChainHandler.class);chainFilterMap.forEach((beanName, bean) -> {// 获取指定类型的责任链集合, 如果没有就创建// 需要将同一个mark的放到一起List<AbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(bean.mark());if (CollectionUtils.isEmpty(abstractChainHandlers)) {abstractChainHandlers = new ArrayList<>();}// 添加到处理器集合中abstractChainHandlers.add(bean);// 对处理器集合顺序进行排序List<AbstractChainHandler> actualAbstractChainHandlers = abstractChainHandlers.stream().sorted(Comparator.comparing(Ordered::getOrder)).collect(Collectors.toList());log.info("mark {} , bean : {} add container", bean.mark(), bean);//将排好序的Bean存入到容器等待运行时被调用abstractChainHandlerContainer.put(bean.mark(), actualAbstractChainHandlers);});}
}
这个类主要是需要实现 CommandLineRunner
接口, 这个接口提供一个run方法, 会在SpringBoot启动后执行
handler 方法
3.8. 基本使用
package cn.knightzz.pattern.service;import cn.knightzz.pattern.common.enums.TicketChainMarkEnum;
import cn.knightzz.pattern.context.AbstractChainContext;
import cn.knightzz.pattern.dto.req.PurchaseTicketReqDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;/*** @author 王天赐* @title: TicketService* @description:* @create: 2023-08-29 19:04*/
@Service
@RequiredArgsConstructor
public class TicketService {private final AbstractChainContext<PurchaseTicketReqDTO> purchaseTicketAbstractChainContext;public void purchase(PurchaseTicketReqDTO requestParam) {purchaseTicketAbstractChainContext.handler(TicketChainMarkEnum.TRAIN_PURCHASE_TICKET_FILTER.name(), requestParam);}
}
如上面代码所示 , 使用时 直接调用 AbstractChainContext 提供的handler方法既可