SpringBoot 项目基于责任链模式实现复杂接口的解耦和动态编排

SpringBoot 项目基于责任链模式实现复杂接口的解耦和动态编排 一、背景项目中有一个 OpenApi 接口提供给客户上游系统调用。这个接口中包含十几个功能点比如入参校验、系统配置校验、基本数据入库、核心数据入库、发送给消息中心、发送给 MQ…不同的客户对这个接口的要求也不同有些功能不需要有些需要添加特定功能。基于 Spring Boot MyBatis Plus Vue Element 实现的后台管理系统 用户小程序支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能项目地址https://github.com/YunaiV/ruoyi-vue-pro视频教程https://doc.iocoder.cn/video/二、思路基于以上背景考虑把十几个功能点进行拆分形成独立的功能。因此使用责任链模式实现。创建一个抽象类ComponentAbstract.java每个拆分功能点继承抽象类形成子类。子类创建时需要在 Component(“1”) 注解中设置类名如果不设置咋使用默认的小驼峰名称子类之间的数据通信使用自定义的上下文类Contxt.java子类中可以对上下文数据进行修改。业务解耦通过事先定义好的执行顺序通过 spring 的上下文 ApplicationContext 根据子类名称循环获取子类对象执行抽象类中handlerRequest() 方法。“事先定义好的执行顺序”可以保存到数据库中项目启动的时候加载到内存或者直接维护到Redis中。我这边直接使用接口进行演示http://localhost:8082/test/chain?index2,1,3,4基于 Spring Cloud Alibaba Gateway Nacos RocketMQ Vue Element 实现的后台管理系统 用户小程序支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能项目地址https://github.com/YunaiV/yudao-cloud视频教程https://doc.iocoder.cn/video/三、代码maven依赖没有特别的依赖fastjson用于测试时打印日志dependencygroupIdcom.alibaba/groupIdartifactIdfastjson/artifactIdversion1.2.76/version/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion1.18.12/version/dependencyComponentAbstract.java 抽象类实现责任链的基础importcom.liran.middle.liteflow.slot.Contxt;importlombok.extern.slf4j.Slf4j;importorg.springframework.util.StopWatch;/** * 组件抽象类 */Slf4jpublicabstractclassComponentAbstract{publicvoidhandlerRequest(Contxtcontxt){StopWatchstopWatchnewStopWatch();stopWatch.start();// 执行子类业务逻辑this.doHandler(contxt);stopWatch.stop();longcoststopWatch.getTotalTimeMillis();if(cost10){log.info(-----------监控统方法执行时间执行 {} 方法, 用时优秀: {} ms -----------,getClass(),cost);}elseif(cost50){log.info(-----------监控统方法执行时间执行 {} 方法, 用时一般: {} ms -----------,getClass(),cost);}elseif(cost500){log.info(-----------监控统方法执行时间执行 {} 方法, 用时延迟: {} ms -----------,getClass(),cost);}elseif(cost1000){log.info(-----------监控统法执行时间执行 {} 方法, 用时缓慢: {} ms -----------,getClass(),cost);}else{log.info(-----------监控方法执行时间执行 {} 方法, 用时卡顿: {} ms -----------,getClass(),cost);}}abstractpublicvoiddoHandler(Contxtcontxt);}Test1.java 业务类1继承抽象类实现doHandler()方法在Component中设置类名1importcom.alibaba.fastjson.JSON;importcom.liran.middle.liteflow.slot.Contxt;importlombok.extern.slf4j.Slf4j;importorg.springframework.stereotype.Component;Component(1)Slf4jpublicclassTest1extendsComponentAbstract{OverridepublicvoiddoHandler(Contxtcontxt){log.info(Test1-顺序1-上下文内容为{},JSON.toJSONString(contxt));contxt.setName(Test1);contxt.setAge(Test1);contxt.setAdrss(Test1);contxt.setUserid(Test1);}}Test2.java 业务类2继承抽象类实现doHandler()方法在Component中设置类名2importcom.alibaba.fastjson.JSON;importcom.liran.middle.liteflow.slot.Contxt;importlombok.extern.slf4j.Slf4j;importorg.springframework.stereotype.Component;Component(2)Slf4jpublicclassTest2extendsComponentAbstract{OverridepublicvoiddoHandler(Contxtcontxt){log.info(Test2-顺序2-上下文内容为{},JSON.toJSONString(contxt));contxt.setName(Test2);contxt.setAge(Test2);contxt.setAdrss(Test2);contxt.setUserid(Test2);}}Test3.java 业务类3继承抽象类实现doHandler()方法在Component中设置类名3importcom.alibaba.fastjson.JSON;importcom.liran.middle.liteflow.slot.Contxt;importlombok.extern.slf4j.Slf4j;importorg.springframework.stereotype.Component;Component(3)Slf4jpublicclassTest3extendsComponentAbstract{OverridepublicvoiddoHandler(Contxtcontxt){log.info(Test3-顺序3-上下文内容为{},JSON.toJSONString(contxt));contxt.setName(Test3);contxt.setAge(Test3);contxt.setAdrss(Test3);contxt.setUserid(Test3);}}Test4.java 业务类4继承抽象类实现doHandler()方法在Component中设置类名4importcom.alibaba.fastjson.JSON;importcom.liran.middle.liteflow.slot.Contxt;importlombok.extern.slf4j.Slf4j;importorg.springframework.stereotype.Component;Component(4)Slf4jpublicclassTest4extendsComponentAbstract{OverridepublicvoiddoHandler(Contxtcontxt){log.info(Test4-顺序4-上下文内容为{},JSON.toJSONString(contxt));contxt.setName(Test4);contxt.setAge(Test4);contxt.setAdrss(Test4);contxt.setUserid(Test4);}}Contxt.java 业务上下文用于每个子类每个功能点之间的数据通信。需要什么数据可以在此类中添加字段进行写入后面执行的类可以读取。importlombok.AllArgsConstructor;importlombok.Builder;importlombok.Data;importlombok.NoArgsConstructor;DataBuilderAllArgsConstructorNoArgsConstructorpublicclassContxt{privateStringname;privateStringage;privateStringadrss;privateStringuserid;}AopProxyUtils.javaspring 管理的上下文用于根据类名获取类实体。importorg.springframework.beans.BeansException;importorg.springframework.context.ApplicationContext;importorg.springframework.context.ApplicationContextAware;importorg.springframework.stereotype.Component;ComponentpublicclassAopProxyUtilsimplementsApplicationContextAware{privatestaticApplicationContextapplicationContext;/** * 实现ApplicationContextAware接口的setApplicationContext方法 * 用于注入ApplicationContext。 */OverridepublicvoidsetApplicationContext(ApplicationContextcontext)throwsBeansException{applicationContextcontext;}/** * 获取指定类的代理对象适用于需要事务或其他AOP增强的场景。 * * param clazz 要获取代理的对象的类 * param T 泛型标记 * return 代理对象实例 */publicstaticTTgetProxyBean(ClassTclazz){if(applicationContextnull){thrownewIllegalStateException(ApplicationContext not initialized.);}returnapplicationContext.getBean(clazz);}publicstaticObjectgetProxyBean(Stringname){returnapplicationContext.getBean(name);}}LiteFlowController.java 用于测试演示如何动态编排。调用接口http://localhost:8082/test/chain?index2,1,3,4 传入不同的index顺序业务逻辑中执行的顺序也不同。importcom.alibaba.fastjson.JSON;importcom.liran.middle.liteflow.component.pattern.chain.ComponentAbstract;importcom.liran.middle.liteflow.slot.Contxt;importcom.liran.middle.liteflow.utils.AopProxyUtils;importlombok.extern.slf4j.Slf4j;importorg.apache.commons.lang3.ObjectUtils;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.RestController;RestControllerRequestMapping(value/test)Slf4jpublicclassLiteFlowController{/** * 不使用框架手动实现动态业务编排 * * param index 类名称 * return */GetMapping(valuechain)publicStringpattern(RequestParamStringindex){ContxtcontxtnewContxt().builder().age(初始化).adrss(初始化).name(初始化).userid(初始化).age(初始化).build();String[]splitindex.split(,);for(StringclassName:split){// 此处直接根据类名从 spring 管理的上下文中进行获取。这里的类名是子类注解Component(1)中自定义的如果没有定义的话默认使用类名// 使用这种方式可以保证类名不重复。ComponentAbstractmsgHandler(ComponentAbstract)AopProxyUtils.getProxyBean(className);if(ObjectUtils.isNotEmpty(msgHandler)){msgHandler.handlerRequest(contxt);}else{log.info(没有找到对应的组件: {},className);}}returnJSON.toJSONString(contxt);}}四、注意其实要实现这个功能使用 LiteFlow 框架最合适文档友好接入简单功能强大。LiteFlow 框架官网https://liteflow.cc/pages/5816c5/作者提供的代码示例https://gitee.com/bryan31/liteflow-example我是因为公司内部对依赖包的引入有要求审核严格所以自己实现了一个简单版本的。