首页 > 其他分享 >责任链模式与spring容器的搭配应用

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

时间:2024-02-22 11:35:10浏览次数:26  
标签:容器 return 搭配 spring deviceRes 接口 处理器 筛选 public

背景

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

代码

责任链模式顶层接口

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

public interface AbstractChainHandler<REQUEST> extends Ordered {

    default boolean handler(REQUEST requestParam){

        return true;
    };
    
}

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

public interface DeviceTypeAChainFilter extends AbstractChainHandler<DeviceFilterBO> {

}
public interface DeviceTypeBChainFilter extends AbstractChainHandler<DeviceFilterBO> {

}

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

image-20240206171639069

具体的处理器

处理器1:

@Component
public class DeviceFunctionChainHandler implements DeviceTypeAChainFilter, DeviceTypeBChainFilter {

    @Override
    public boolean handler(DeviceFilterBO deviceFilterBO) {
        if (deviceFilterBO.getDeviceBO().getCondition() % 2 == 0) {
            System.out.println("处理器A:筛选功能不通过");
            return false;
        }
        // 筛选功能
        System.out.println("处理器A:筛选功能通过");
        return true;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

处理器2:

@Component
public class DeviceResolutionChainHandler implements DeviceTypeAChainFilter, DeviceTypeBChainFilter {

    @Override
    public boolean handler(DeviceFilterBO deviceFilterBO) {
        // 分辨率支持
        System.out.println("处理器B:分辨率支持");
        return true;
    }

    @Override
    public int getOrder() {
        return 10;
    }

}

处理器3:

@Component
public class DeviceCaculateOutputChainHandler implements DeviceTypeBChainFilter {

    @Override
    public boolean handler(DeviceFilterBO deviceFilterBO) {
        // 接口支持
        System.out.println("处理器C:输出接口支持");
        // 计算设备数量满足要求
        System.out.println("处理器C:根据输出接口计算的设备数量满足要求");
        return true;
    }

    @Override
    public int getOrder() {
        return 30;
    }
}

处理器4:

@Component
public class DeviceCaculateInputChainHandler implements DeviceTypeAChainFilter, DeviceTypeBChainFilter {

    @Override
    public boolean handler(DeviceFilterBO deviceFilterBO) {
        if (deviceFilterBO.getDeviceBO().getCondition() % deviceFilterBO.getCondition() == 0) {
            System.out.println("处理器D:输入接口不支持");
            return false;
        }
        ArrayList<DeviceBO> deviceRes = (ArrayList<DeviceBO>) AbstractChainContext.threadLocal.get();
        deviceRes.add(deviceFilterBO.getDeviceBO());
        // 接口支持
        System.out.println("处理器D:输入接口支持");
        // 计算设备数量满足要求
        System.out.println("处理器D:根据输入接口计算的设备数量满足要求");
        return true;
    }

    @Override
    public int getOrder() {
        return 40;
    }
}

可以看到,处理器都用@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上下文类:

public final class AbstractChainContext<REQUEST, RESPONSE> implements CommandLineRunner {

    private final static Map<String, List<AbstractChainHandler>> abstractChainHandlerContainer = new HashMap<>();

    public final static ThreadLocal threadLocal = new ThreadLocal<>();

    public void handler(String mark, REQUEST requestParam) {
        List<AbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(mark);
        if (CollectionUtils.isEmpty(abstractChainHandlers)) {
            throw new RuntimeException(String.format("[%s] Chain of Responsibility ID is undefined.", mark));
        }
        for (AbstractChainHandler abstractChainHandler : abstractChainHandlers) {
            if(!abstractChainHandler.handler(requestParam)){
                break;
            }
        }
    }


    @Override
    public void run(String... args) {
        List<Class<?>> interfaces = getInterfacesInPackage("com.zh.demo.designpattern.chain.type");
        for (Class<?> interfaceType : interfaces) {
            Map<String, AbstractChainHandler> beansOfType = (Map<String, AbstractChainHandler>) ApplicationContextHolder.getBeansOfType(interfaceType);
            // 转成list
            List<AbstractChainHandler> sortedList = beansOfType.values().stream()
                    .sorted(Comparator.comparing(Ordered::getOrder))
                    .collect(Collectors.toList());
            int index = interfaceType.getName().lastIndexOf(".") + 1;
            abstractChainHandlerContainer.put(interfaceType.getName().substring(index), sortedList);
        }
    }

    public static List<Class<?>> getInterfacesInPackage(String packageName) {
        List<Class<?>> result = new ArrayList<>();
        try {
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            String path = packageName.replace('.', '/');
            Enumeration<URL> resources = classLoader.getResources(path);

            while (resources.hasMoreElements()) {
                URL resource = resources.nextElement();
                File directory = new File(resource.getFile());
                File[] files = directory.listFiles();

                if (files != null) {
                    for (File file : files) {
                        if (file.getName().endsWith(".class")) {
                            String className = packageName + '.' + file.getName().replace(".class", "");
                            Class<?> clazz = Class.forName(className);

                            if (clazz.isInterface()) {
                                result.add(clazz);
                            }
                        }
                    }
                }
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }

        return result;
    }
}

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

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

public enum DeviceChainMarkEnum {

    /**
     * A设备过滤器
     */
    DEVICE_TYPEA_FILTER("DeviceTypeAChainFilter"),

    /**
     * B设备过滤器
     */
    DEVICE_TYPEB_FILTER("DeviceTypeBChainFilter");

    String name;

    public String getName() {
        return name;
    }

    DeviceChainMarkEnum(String name) {
        this.name = name;
    }

}

Service的编写

@Service
@RequiredArgsConstructor
@Slf4j
public class DemoServiceImpl implements DemoService {

    private final AbstractChainContext<DeviceFilterBO, Object> devcieTypeChainContext;

    @Override
    public List<DeviceBO> filterDeviceTypeA(ParmDTO parmDTO) {
        ArrayList<DeviceBO> deviceList = new ArrayList<>();
        // 简化条件
        parmDTO.setCondition(2);
        // 实际情况应该是从数据库读取设备的信息
        for (int i = 0; i < 5; i++) {
            DeviceBO deviceDTO = DeviceBO.builder().condition(new Random().nextInt(100)).build();
            deviceList.add(deviceDTO);
        }
        ArrayList<DeviceBO> deviceRes = new ArrayList<>();
       // 把需要的结果放到threadLocal中,在具体的处理器中对结果List进行处理
        AbstractChainContext.threadLocal.set(deviceRes);
        // 筛选多个设备 对符合的设备加入到deviceRes
        for (DeviceBO deviceBo : deviceList) {
            DeviceFilterBO deviceFilterBO = DeviceFilterBO.builder().condition(parmDTO.getCondition()).deviceBO(deviceBo).build();
            // 以A规则进行处理
            devcieTypeChainContext.handler(DeviceChainMarkEnum.DEVICE_TYPEA_FILTER.getName(), deviceFilterBO);
        }
        AbstractChainContext.threadLocal.remove();
        System.out.println("筛选结果数量:" + deviceRes.size());
        return deviceRes;
    }

    @Override
    public List<DeviceBO> filterDeviceTypeB(ParmDTO parmDTO) {
        ArrayList<DeviceBO> deviceList = new ArrayList<>();
        // 简化条件
        parmDTO.setCondition(2);
        // 实际情况应该是从数据库读取设备的信息
        for (int i = 0; i < 5; i++) {
            DeviceBO deviceDTO = DeviceBO.builder().condition(new Random().nextInt(100)).build();
            deviceList.add(deviceDTO);
        }
        ArrayList<DeviceBO> deviceRes = new ArrayList<>();
        // 把需要的结果放到threadLocal中,在具体的处理器中对结果List进行处理
        AbstractChainContext.threadLocal.set(deviceRes);
        // 筛选多个设备 对符合的设备加入到deviceRes
        for (DeviceBO deviceBo : deviceList) {
            DeviceFilterBO deviceFilterBO = DeviceFilterBO.builder().condition(parmDTO.getCondition()).deviceBO(deviceBo).build();
            // 以B规则进行处理
            devcieTypeChainContext.handler(DeviceChainMarkEnum.DEVICE_TYPEB_FILTER.getName(), deviceFilterBO);
        }
        AbstractChainContext.threadLocal.remove();
        System.out.println("筛选结果数量:" + deviceRes.size());
        return deviceRes;
    }

}

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

DeviceBO类:

@Builder
@Data
public class DeviceBO {

    private int condition;

}

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

image-20240222103239661

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

image-20240222103338406

源码

Johnynzh/chain-of-responsibility-demo: 责任链模式与spring容器的搭配应用 (github.com)

标签:容器,return,搭配,spring,deviceRes,接口,处理器,筛选,public
From: https://www.cnblogs.com/Johnyzh/p/18026938

相关文章

  • Spring Kafka AckMode介绍
     原文链接:https://blog.csdn.net/qq1309664161/article/details/116994341一:AckMode介绍kafka消费端在读取数据后,会向Kafka服务端提交偏移量,来记录消费端读取数据的位置。提交偏移量分为手动提交和自动提交,为了保证数据读取的安全性,我们一般设置成手动提交偏移量。在Springb......
  • 华为二面:SpringBoot读取配置文件的原理是什么?加载顺序是什么?
    引言SpringBoot以其简化的配置和强大的开箱即用功能而备受欢迎,而配置文件的加载是SpringBoot应用启动过程中的关键步骤之一。深入理解SpringBoot启动时如何加载配置文件的源码,有助于开发者更好地理解其内部工作原理,提高配置管理的灵活性和可维护性。本文将从源码入手,解读Sprin......
  • 接私活利器!推荐一个基于SpringBoot3的后台管理框架
    大家好,我是Java陈序员。今天,给大家推荐一个后台管理框架,适合二次定制开发、接私活、源码学习等场景。关注微信公众号:【Java陈序员】,获取开源项目分享、AI副业分享、超200本经典计算机电子书籍等。项目介绍Admin3——一个轻巧的后台管理框架,项目后端基于Java17、SpringBo......
  • 掌握云容器网络:何为ipvs
    本文分享自华为云社区《【理解云容器网络】2-基础篇-ipvs介绍》,作者:可以交个朋友。IPVS简介ipvs是工作在Linux内核态的4层负载均衡;和用户态的负载均衡软件(如nginx、haproxy)功能类似:作为客户端访问的统一入口并将访问请求根据调度算法转给后端真实的服务器。相比于用户态负载......
  • pve 将硬盘挂载到ct容器
    1.配置文件方法vi/etc/pve/lxc/101.conf#101是ct容器编号mp0:/mnt/pve/hdd4,mp=/mnt/hdd4tmp1:/mnt/pve/hdd6,mp=/mnt/hdd6tmp2:/mnt/pve/sdd,mp=/mnt/sdd1t配置信息如图:配置完成后,可在容器资源里看到挂载信息:然后重启容器即可。......
  • 华为云帕鲁服务器-云耀云容器版,到底强在哪?
    本文分享自华为云社区《深入解析华为云帕鲁服务器-云耀云容器版》,作者:云容器大未来。随着游戏【幻兽帕鲁】爆火出圈,和好友联机玩帕鲁已成为当前很多玩家的日常,天下打工人一道“白天当帕鲁,晚上玩帕鲁”,各家云厂商自然不会放过这个带货的热点,纷纷跟进推出各自的帕鲁专用服务器。放......
  • C++ STL 容器-string类型
    C++STL第一部分-容器STL的介绍C++的STL分为六大部分容器分为String容器例子1std::stringstr1,str2,str3,str4;str1.assign("abcd");//给str1赋值abcdstr2.assign("abcd",3);//获取abcd中的3个,从0到2str3.assign(str1);//获取str1//注意str3()和str3.a......
  • 1 Spring5 自定义标签开发
    spring5 自定义脚本开发步骤1 定义bean,publicclassUser{privateStringid;privateStringuserName;privateStringemail;privateStringpassword;publicStringgetId(){returnid;}publicvoidsetId(St......
  • idea创建spring项目的时候只有java 21和17
    1.问题我们在用IDEA创建一个spring项目时,发现java版本只能选用java21,java17,导致我们的jdk版本无法选择jdk1.8(我最常用的版本)2.解决参考:idea创建项目的时候只有java21和17原因是spring2在23年11月24日停止维护了,所以通过spring来创建,没有spring2,只有spring3+,最低jdk版本也是1......
  • 【CVE-2024-21626】容器逃逸漏洞修复
    哈喽大家好,我是咸鱼。好久不见,最近有一个很火的CVE——runc容器逃逸漏洞。年前的时候我们已经在测试环境进行了相关操作打算年后线上进行修复。因为今天咸鱼才开工,所以文章也就拖到了现在......