责任链是一种非常常见的设计模式, 具体我就不介绍了, 本文是讲解如何在SpringBoot中优雅的使用责任链模式
基本步骤如下 :
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.5.RELEASEversion>
<relativePath/>
parent>
<groupId>cn.knightzzgroupId>
<artifactId>chain-responsibility-pattern-exampleartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>chain-responsibility-pattern-examplename>
<description>责任链模式demodescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
这个类是用于存储实体类的
package cn.knightzz.pattern.dto.req;
/**
* @author 王天赐
* @title: PurchaseTicketReqDTO
* @description:
* @create: 2023-08-29 18:09
*/
public class PurchaseTicketReqDTO {
}
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;
}
}
枚举类主要是用于标记某一类业务的责任链
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;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextHolder.CONTEXT = applicationContext;
}
/**
* Get ioc container bean by type.
*
* @param clazz
* @param
* @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
* @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
* @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
* @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
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();
}
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> {
@Override
default String mark() {
return TicketChainMarkEnum.TRAIN_PURCHASE_TICKET_FILTER.name();
}
}
通过实现通过责任链接口, 编写默认 mark 方法, 用于标记当前责任链处理器集合
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> {
@Override
public void handler(PurchaseTicketReqDTO requestParam) {
System.out.println("参数不能为空 , 过滤器执行成功");
}
@Override
public 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> {
@Override
public void handler(PurchaseTicketReqDTO requestParam) {
System.out.println("参数合法 , 过滤器执行成功");
}
@Override
public 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> {
@Override
public void handler(PurchaseTicketReqDTO requestParam) {
System.out.println("未重复购票 , 过滤器执行成功");
}
@Override
public int getOrder() {
return 30;
}
}
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));
}
@Override
public void run(String... args) throws Exception {
// 通过ApplicationContextHolder获取所有的Bean
Map<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 方法
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方法既可