IDE 激活

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

终码一生 · 2026-09-29

点击“终码一生”,关注,置顶公众号 每日技术干货,第一时间送达! 项目中有一个 OpenApi 接口提供给客户(上游系统)调用。 这个接口中包含十几个功能点,比如:入参校验、系统配置校验、基本数据入库、核心数据入库、发送给消息中心、发送给 MQ..... 不同的客户对这个接口的要求也不同,有些功能不需要,有些需要添加特定功能。 01 思路 基于以上背景,考虑把十几个功能点进行拆分形成独立的功能。因此使用责任链模式实现。 创建一个抽象类(ComponentAbstract.java),每个拆分功能点继承抽象类形成子类。 子类创建时,需要在 @Component("1") 注解中设置类名,如果不设置咋使用默认的(小驼峰)名称 子类之间的数据通信使用自定义的上下文类(Contxt.java)子类中可以对上下文数据进行修改。(业务解耦) 通过事先定义好的执行顺序,通过 spring 的上下文 ApplicationContext 根据子类名称循环获取子类对象,执行抽象类中handlerRequest() 方法。 “事先定义好的执行顺序”,可以保存到数据库中项目启动的时候加载到内存,或者直接维护到Redis中。我这边直接使用接口进行演示:http://localhost:8082/test/chain?index=2,1,3,4 02 代码 maven依赖,没有特别的依赖fastjson用于测试时打印日志 <dependency>     <groupId>com.alibaba</groupId>     <artifactId>fastjson</artifactId>     <version>1.2.76</version> </dependency> <dependency>     <groupId>org.projectlombok</groupId>     <artifactId>lombok</artifactId>     <version>1.18.12</version> </dependency> ComponentAbstract.java 抽象类实现责任链的基础 import com.liran.middle.liteflow.slot.Contxt; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StopWatch; /**  * 组件抽象类  */ @Slf4j publicabstractclassComponentAbstract{     publicvoidhandlerRequest(Contxt contxt){         StopWatch stopWatch = new StopWatch();         stopWatch.start();         // 执行子类业务逻辑         this.doHandler(contxt);         stopWatch.stop();         long cost = stopWatch.getTotalTimeMillis();         if (cost <= 10) {             log.info("-----------监控统方法执行时间,执行 {} 方法, 用时优秀: {} ms -----------", getClass(), cost);         } elseif (cost <= 50) {             log.info("-----------监控统方法执行时间,执行 {} 方法, 用时一般: {} ms -----------", getClass(), cost);         } elseif (cost <= 500) {             log.info("-----------监控统方法执行时间,执行 {} 方法, 用时延迟: {} ms -----------", getClass(), cost);         } elseif (cost <= 1000) {             log.info("-----------监控统法执行时间,执行 {} 方法, 用时缓慢: {} ms -----------", getClass(), cost);         } else {             log.info("-----------监控方法执行时间,执行 {} 方法, 用时卡顿: {} ms -----------", getClass(), cost);         }     }     abstractpublicvoiddoHandler(Contxt contxt); } Test1.java 业务类1 继承抽象类实现doHandler()方法,在@Component中设置类名1 import com.alibaba.fastjson.JSON; import com.liran.middle.liteflow.slot.Contxt; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Component("1") @Slf4j publicclassTest1extendsComponentAbstract{     @Override     publicvoiddoHandler(Contxt contxt){         log.info("Test1-顺序1-上下文内容为:{}", JSON.toJSONString(contxt));         contxt.setName("Test1");         contxt.setAge("Test1");         contxt.setAdrss("Test1");         contxt.setUserid("Test1");     } } Test2.java 业务类2 继承抽象类实现doHandler()方法,在@Component中设置类名2 import com.alibaba.fastjson.JSON; import com.liran.middle.liteflow.slot.Contxt; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Component("2") @Slf4j publicclassTest2extendsComponentAbstract{     @Override     publicvoiddoHandler(Contxt contxt){         log.info("Test2-顺序2-上下文内容为:{}", JSON.toJSONString(contxt));         contxt.setName("Test2");         contxt.setAge("Test2");         contxt.setAdrss("Test2");         contxt.setUserid("Test2");     } } Test3.java 业务类3 继承抽象类实现doHandler()方法,在@Component中设置类名3 import com.alibaba.fastjson.JSON; import com.liran.middle.liteflow.slot.Contxt; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Component("3") @Slf4j publicclassTest3extendsComponentAbstract{     @Override     publicvoiddoHandler(Contxt contxt){         log.info("Test3-顺序3-上下文内容为:{}", JSON.toJSONString(contxt));         contxt.setName("Test3");         contxt.setAge("Test3");         contxt.setAdrss("Test3");         contxt.setUserid("Test3");     } } Test4.java 业务类4 继承抽象类实现doHandler()方法,在@Component中设置类名4 import com.alibaba.fastjson.JSON; import com.liran.middle.liteflow.slot.Contxt; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Component("4") @Slf4j publicclassTest4extendsComponentAbstract{     @Override     publicvoiddoHandler(Contxt contxt){         log.info("Test4-顺序4-上下文内容为:{}", JSON.toJSONString(contxt));         contxt.setName("Test4");         contxt.setAge("Test4");         contxt.setAdrss("Test4");         contxt.setUserid("Test4");     } } Contxt.java 业务上下文 用于每个子类(每个功能点)之间的数据通信。需要什么数据可以在此类中添加字段进行写入,后面执行的类可以读取。 import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Data @Builder @AllArgsConstructor @NoArgsConstructor publicclassContxt{     private String name;     private String age;     private String adrss;     private String userid; } AopProxyUtils.java spring 管理的上下文,用于根据类名获取类实体。 import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component publicclassAopProxyUtilsimplementsApplicationContextAware{     privatestatic ApplicationContext applicationContext;     /**      * 实现ApplicationContextAware接口的setApplicationContext方法,      * 用于注入ApplicationContext。      */     @Override     publicvoidsetApplicationContext(ApplicationContext context)throws BeansException {         applicationContext = context;     }     /**      * 获取指定类的代理对象,适用于需要事务或其他AOP增强的场景。      *      * @param clazz 要获取代理的对象的类      * @param <T>   泛型标记      * @return 代理对象实例      */     publicstatic <T> T getProxyBean(Class<T> clazz){         if (applicationContext == null) {             thrownew IllegalStateException("ApplicationContext not initialized.");         }         return applicationContext.getBean(clazz);     }     publicstatic Object getProxyBean(String name){         return applicationContext.getBean(name);     } } LiteFlowController.java 用于测试 演示如何动态编排。调用接口http://localhost:8082/test/chain?index=2,1,3,4 传入不同的index顺序,业务逻辑中执行的顺序也不同。 import com.alibaba.fastjson.JSON; import com.liran.middle.liteflow.component.pattern.chain.ComponentAbstract; import com.liran.middle.liteflow.slot.Contxt; import com.liran.middle.liteflow.utils.AopProxyUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping(value = "/test") @Slf4j publicclassLiteFlowController{     /**      * 不使用框架,手动实现动态业务编排      *      * @param index 类名称      * @return      */     @GetMapping(value = "chain")     public String pattern(@RequestParam String index){         Contxt contxt = new Contxt().builder()                 .age("初始化")                 .adrss("初始化")                 .name("初始化")                 .userid("初始化")                 .age("初始化")                 .build();         String[] split = index.split(",");         for (String className : split) {             // 此处直接根据类名从 spring 管理的上下文中进行获取。这里的类名是子类注解@Component("1")中自定义的,如果没有定义的话,默认使用类名             // 使用这种方式可以保证类名不重复。             ComponentAbstract msgHandler = (ComponentAbstract) AopProxyUtils.getProxyBean(className);             if (ObjectUtils.isNotEmpty(msgHandler)) {                 msgHandler.handlerRequest(contxt);             } else {                 log.info("没有找到对应的组件: {}", className);             }         }         return JSON.toJSONString(contxt);     } } 03 注意 其实要实现这个功能使用 LiteFlow 框架最合适,文档友好,接入简单,功能强大。 官网:https://liteflow.cc/pages/5816c5/ 代码示例:https://gitee.com/bryan31/liteflow-example 我是因为公司内部对依赖包的引入有要求审核严格,所以自己实现了一个简单版本的。 来源:juejin.cn/post/7402435366647758857 —END— PS:防止找不到本篇文章,可以收藏点赞,方便翻阅查找哦。 往期推荐 阿里终面:10 亿数据如何快速插入 MySQL? 再见了SpringBoot,后端AI已成气候 最牛逼的监控系统,不接受反驳!! 干掉 frp ?又一个更快更稳的内网穿透工具来了~ Java+LangChain=王炸! Kafka已正式接入AI

查看完整内容

更多文章