• 责任链模式与spring容器的搭配应用


    背景

    有个需求,原先只涉及到一种A情况设备的筛选,每次筛选会经过多个流程,比如先a功能,a功能通过再筛选b功能,然后再筛选c功能,以此类推。现在新增了另外一种B情况的筛选,B情况同样需要A情况的筛选流程,并且需要在A情况的基础上,新增另外的功能筛选,这里假设A需要a、b、c功能的筛选,而B需要a、b、c、d功能的筛选,并且这些功能的筛选的顺序可能发生变动,比如新增了某个筛选,这个筛选涉及到的计算量少那肯定可以把这个置在前面先处理,不满足条件就return,咋一看,这个需求很符合责任链模式的应用场景,下面介绍编码。这里的代码参考了 马丁玩编程 在其12306项目里面的责任链模式,并做出一些相应改动,以适配当前的场景。

    代码

    责任链模式顶层接口

    这里继承了Ordered类,是为了方便后续对处理器进行排序。

    1. public interface AbstractChainHandler extends Ordered {
    2. default boolean handler(REQUEST requestParam){
    3. return true;
    4. };
    5. }

    A情况的接口和B情况的接口。

    1. public interface DeviceTypeAChainFilter extends AbstractChainHandler<DeviceFilterBO> {
    2. }

    1. public interface DeviceTypeBChainFilter extends AbstractChainHandler<DeviceFilterBO> {
    2. }

    定义成接口,后续往里面添加处理器的时候,方便查看当前A规则和B规则都有哪些处理器:

    image-20240206171639069

    具体的处理器

    处理器1:

    1. @Component
    2. public class DeviceFunctionChainHandler implements DeviceTypeAChainFilter, DeviceTypeBChainFilter {
    3. @Override
    4. public boolean handler(DeviceFilterBO deviceFilterBO) {
    5. if (deviceFilterBO.getDeviceBO().getCondition() % 2 == 0) {
    6. System.out.println("处理器A:筛选功能不通过");
    7. return false;
    8. }
    9. // 筛选功能
    10. System.out.println("处理器A:筛选功能通过");
    11. return true;
    12. }
    13. @Override
    14. public int getOrder() {
    15. return 0;
    16. }
    17. }

    处理器2:

    1. @Component
    2. public class DeviceResolutionChainHandler implements DeviceTypeAChainFilter, DeviceTypeBChainFilter {
    3. @Override
    4. public boolean handler(DeviceFilterBO deviceFilterBO) {
    5. // 分辨率支持
    6. System.out.println("处理器B:分辨率支持");
    7. return true;
    8. }
    9. @Override
    10. public int getOrder() {
    11. return 10;
    12. }
    13. }

    处理器3:

    1. @Component
    2. public class DeviceCaculateOutputChainHandler implements DeviceTypeBChainFilter {
    3. @Override
    4. public boolean handler(DeviceFilterBO deviceFilterBO) {
    5. // 接口支持
    6. System.out.println("处理器C:输出接口支持");
    7. // 计算设备数量满足要求
    8. System.out.println("处理器C:根据输出接口计算的设备数量满足要求");
    9. return true;
    10. }
    11. @Override
    12. public int getOrder() {
    13. return 30;
    14. }
    15. }

    处理器4:

    1. @Component
    2. public class DeviceCaculateInputChainHandler implements DeviceTypeAChainFilter, DeviceTypeBChainFilter {
    3. @Override
    4. public boolean handler(DeviceFilterBO deviceFilterBO) {
    5. if (deviceFilterBO.getDeviceBO().getCondition() % deviceFilterBO.getCondition() == 0) {
    6. System.out.println("处理器D:输入接口不支持");
    7. return false;
    8. }
    9. ArrayList<DeviceBO> deviceRes = (ArrayList<DeviceBO>) AbstractChainContext.threadLocal.get();
    10. deviceRes.add(deviceFilterBO.getDeviceBO());
    11. // 接口支持
    12. System.out.println("处理器D:输入接口支持");
    13. // 计算设备数量满足要求
    14. System.out.println("处理器D:根据输入接口计算的设备数量满足要求");
    15. return true;
    16. }
    17. @Override
    18. public int getOrder() {
    19. return 40;
    20. }
    21. }

    可以看到,处理器都用@Component进行标识,后续通过ioc容器获取这些处理器进行分类和执行。并且,可以看到A..filter接口有三个实现者,这说明A有三种处理器,同理B有四种处理器,并且由于顶层接口继承了Order类,所有具体的处理器都会标识当前的order,如上面的10,20,30...这里把Order的数字间隔放大一些,比如10,20,30,如果以后要往这些间隔插入新的处理逻辑也方便。

    获取具体处理器和执行hanlder的上下文类

    先将不同的处理规则的接口都放在某个特定包下

    image-20240206172405349

    先去扫描这个包下的所有接口,然后再去Spring Ioc容器里面拿出这些接口的实现类,把不同的接口实现类按接口名字作为标识,按Order对这些实现类进行排序,然后放到一个List里面,以接口名字作为key,实现类List作为value,后续调用链式调用的时候,传入具体的接口名字(处理规则名字),实现链式顺序调用,具体实现如下

    AbstractChainContext上下文类:

    1. public final class AbstractChainContext<REQUEST, RESPONSE> implements CommandLineRunner {
    2. private final static Map<String, List<AbstractChainHandler>> abstractChainHandlerContainer = new HashMap<>();
    3. public final static ThreadLocal threadLocal = new ThreadLocal<>();
    4. public void handler(String mark, REQUEST requestParam) {
    5. List<AbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(mark);
    6. if (CollectionUtils.isEmpty(abstractChainHandlers)) {
    7. throw new RuntimeException(String.format("[%s] Chain of Responsibility ID is undefined.", mark));
    8. }
    9. for (AbstractChainHandler abstractChainHandler : abstractChainHandlers) {
    10. if(!abstractChainHandler.handler(requestParam)){
    11. break;
    12. }
    13. }
    14. }
    15. @Override
    16. public void run(String... args) {
    17. List<Class<?>> interfaces = getInterfacesInPackage("com.zh.demo.designpattern.chain.type");
    18. for (Class<?> interfaceType : interfaces) {
    19. Map<String, AbstractChainHandler> beansOfType = (Map<String, AbstractChainHandler>) ApplicationContextHolder.getBeansOfType(interfaceType);
    20. // 转成list
    21. List<AbstractChainHandler> sortedList = beansOfType.values().stream()
    22. .sorted(Comparator.comparing(Ordered::getOrder))
    23. .collect(Collectors.toList());
    24. int index = interfaceType.getName().lastIndexOf(".") + 1;
    25. abstractChainHandlerContainer.put(interfaceType.getName().substring(index), sortedList);
    26. }
    27. }
    28. public static List<Class<?>> getInterfacesInPackage(String packageName) {
    29. List<Class<?>> result = new ArrayList<>();
    30. try {
    31. ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    32. String path = packageName.replace('.', '/');
    33. Enumeration<URL> resources = classLoader.getResources(path);
    34. while (resources.hasMoreElements()) {
    35. URL resource = resources.nextElement();
    36. File directory = new File(resource.getFile());
    37. File[] files = directory.listFiles();
    38. if (files != null) {
    39. for (File file : files) {
    40. if (file.getName().endsWith(".class")) {
    41. String className = packageName + '.' + file.getName().replace(".class", "");
    42. Class<?> clazz = Class.forName(className);
    43. if (clazz.isInterface()) {
    44. result.add(clazz);
    45. }
    46. }
    47. }
    48. }
    49. }
    50. } catch (IOException | ClassNotFoundException e) {
    51. e.printStackTrace();
    52. }
    53. return result;
    54. }
    55. }

    在上面变量中,用了个 public final static ThreadLocal threadLocal = new ThreadLocal<>(); 这个是用来保存设备的筛选列表。

    定义好不同筛选规则的枚举类:

    1. public enum DeviceChainMarkEnum {
    2. /**
    3. * A设备过滤器
    4. */
    5. DEVICE_TYPEA_FILTER("DeviceTypeAChainFilter"),
    6. /**
    7. * B设备过滤器
    8. */
    9. DEVICE_TYPEB_FILTER("DeviceTypeBChainFilter");
    10. String name;
    11. public String getName() {
    12. return name;
    13. }
    14. DeviceChainMarkEnum(String name) {
    15. this.name = name;
    16. }
    17. }

    Service的编写

    1. @Service
    2. @RequiredArgsConstructor
    3. @Slf4j
    4. public class DemoServiceImpl implements DemoService {
    5. private final AbstractChainContext<DeviceFilterBO, Object> devcieTypeChainContext;
    6. @Override
    7. public List<DeviceBO> filterDeviceTypeA(ParmDTO parmDTO) {
    8. ArrayList<DeviceBO> deviceList = new ArrayList<>();
    9. // 简化条件
    10. parmDTO.setCondition(2);
    11. // 实际情况应该是从数据库读取设备的信息
    12. for (int i = 0; i < 5; i++) {
    13. DeviceBO deviceDTO = DeviceBO.builder().condition(new Random().nextInt(100)).build();
    14. deviceList.add(deviceDTO);
    15. }
    16. ArrayList<DeviceBO> deviceRes = new ArrayList<>();
    17. // 把需要的结果放到threadLocal中,在具体的处理器中对结果List进行处理
    18. AbstractChainContext.threadLocal.set(deviceRes);
    19. // 筛选多个设备 对符合的设备加入到deviceRes
    20. for (DeviceBO deviceBo : deviceList) {
    21. DeviceFilterBO deviceFilterBO = DeviceFilterBO.builder().condition(parmDTO.getCondition()).deviceBO(deviceBo).build();
    22. // 以A规则进行处理
    23. devcieTypeChainContext.handler(DeviceChainMarkEnum.DEVICE_TYPEA_FILTER.getName(), deviceFilterBO);
    24. }
    25. AbstractChainContext.threadLocal.remove();
    26. System.out.println("筛选结果数量:" + deviceRes.size());
    27. return deviceRes;
    28. }
    29. @Override
    30. public List<DeviceBO> filterDeviceTypeB(ParmDTO parmDTO) {
    31. ArrayList<DeviceBO> deviceList = new ArrayList<>();
    32. // 简化条件
    33. parmDTO.setCondition(2);
    34. // 实际情况应该是从数据库读取设备的信息
    35. for (int i = 0; i < 5; i++) {
    36. DeviceBO deviceDTO = DeviceBO.builder().condition(new Random().nextInt(100)).build();
    37. deviceList.add(deviceDTO);
    38. }
    39. ArrayList<DeviceBO> deviceRes = new ArrayList<>();
    40. // 把需要的结果放到threadLocal中,在具体的处理器中对结果List进行处理
    41. AbstractChainContext.threadLocal.set(deviceRes);
    42. // 筛选多个设备 对符合的设备加入到deviceRes
    43. for (DeviceBO deviceBo : deviceList) {
    44. DeviceFilterBO deviceFilterBO = DeviceFilterBO.builder().condition(parmDTO.getCondition()).deviceBO(deviceBo).build();
    45. // 以B规则进行处理
    46. devcieTypeChainContext.handler(DeviceChainMarkEnum.DEVICE_TYPEB_FILTER.getName(), deviceFilterBO);
    47. }
    48. AbstractChainContext.threadLocal.remove();
    49. System.out.println("筛选结果数量:" + deviceRes.size());
    50. return deviceRes;
    51. }
    52. }

    这里假设有五种设备,每个设备通过DeviceBO里面的condition设置条件,演示一遍筛选过程

    DeviceBO类:

    1. @Builder
    2. @Data
    3. public class DeviceBO {
    4. private int condition;
    5. }

    演示筛选规则A,一共五个设备数据,只有一个筛选通过了,这里涉及到A,B,D三种处理器

    image-20240222103239661

    演示筛选规则B,一共五个设备数据,2个筛选通过了,这里涉及到A,B,C,D三种处理器

    image-20240222103338406

    文章转载自:John-zh

    原文链接:https://www.cnblogs.com/Johnyzh/p/18026938

    体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

  • 相关阅读:
    Oxygen XML Editor 26版新功能
    【Kubernetes系列】工作负载资源之DaemonSet
    猿创征文|vue3+ts封装table组件并注册发布
    鸿蒙组件学习_Tabs组件
    C++ list容器模拟实现
    matlab层次分析法模型及相关语言基础
    【SLAM】4李群&李代数
    微信小程序之图片上传并保存在服务器
    两届 TOKEN 2049 之间,孙宇晨和波场的布局与野心
    vue2的vue.config文件
  • 原文地址:https://blog.csdn.net/kfashfasf/article/details/136257605