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

一、背景
项目中有一个 OpenApi 接口提供给客户(上游系统)调用。
这个接口中包含十几个功能点,比如:入参校验、系统配置校验、基本数据入库、核心数据入库、发送给消息中心、发送给 MQ.....
不同的客户对这个接口的要求也不同,有些功能不需要,有些需要添加特定功能。
二、思路
基于以上背景,考虑把十几个功能点进行拆分形成独立的功能。因此使用责任链模式实现。
创建一个抽象类(
ComponentAbstract.java),每个拆分功能点继承抽象类形成子类。子类创建时,需要在
@Component("1")注解中设置类名,如果不设置咋使用默认的(小驼峰)名称子类之间的数据通信使用自定义的上下文类(
Contxt.java)子类中可以对上下文数据进行修改。(业务解耦)通过事先定义好的执行顺序,通过 spring 的上下文
ApplicationContext根据子类名称循环获取子类对象,执行抽象类中handlerRequest()方法。“事先定义好的执行顺序”,可以保存到数据库中项目启动的时候加载到内存,或者直接维护到Redis中。我这边直接使用接口进行演示:
http://localhost:8082/test/chain?index=2,1,3,4(不能直接访问,仅作参考)
三、代码
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
public abstract class ComponentAbstract {public void handlerRequest(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);} else if (cost <= 50) {log.info("-----------监控统方法执行时间,执行 {} 方法, 用时一般: {} ms -----------", getClass(), cost);} else if (cost <= 500) {log.info("-----------监控统方法执行时间,执行 {} 方法, 用时延迟: {} ms -----------", getClass(), cost);} else if (cost <= 1000) {log.info("-----------监控统法执行时间,执行 {} 方法, 用时缓慢: {} ms -----------", getClass(), cost);} else {log.info("-----------监控方法执行时间,执行 {} 方法, 用时卡顿: {} ms -----------", getClass(), cost);}}abstract public void doHandler(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
public class Test1 extends ComponentAbstract {@Overridepublic void doHandler(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
public class Test2 extends ComponentAbstract {@Overridepublic void doHandler(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
public class Test3 extends ComponentAbstract {@Overridepublic void doHandler(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
public class Test4 extends ComponentAbstract {@Overridepublic void doHandler(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
public class Contxt {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
public class AopProxyUtils implements ApplicationContextAware {private static ApplicationContext applicationContext;/*** 实现ApplicationContextAware接口的setApplicationContext方法,* 用于注入ApplicationContext。*/@Overridepublic void setApplicationContext(ApplicationContext context) throws BeansException {applicationContext = context;}/*** 获取指定类的代理对象,适用于需要事务或其他AOP增强的场景。** @param clazz 要获取代理的对象的类* @param <T>   泛型标记* @return 代理对象实例*/public static <T> T getProxyBean(Class<T> clazz) {if (applicationContext == null) {throw new IllegalStateException("ApplicationContext not initialized.");}return applicationContext.getBean(clazz);}public static 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
public class LiteFlowController {/*** 不使用框架,手动实现动态业务编排** @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);}
}
四、注意
其实要实现这个功能使用 LiteFlow 框架最合适,文档友好,接入简单,功能强大。
LiteFlow 框架官网:
https://liteflow.cc/pages/5816c5/
作者提供的代码示例:
https://gitee.com/bryan31/liteflow-example
因为公司内部对依赖包的引入有要求审核严格,所以自己实现了一个简单版本的。
最后说一句(求关注,别白嫖我)
如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下,您的支持是我坚持写作最大的动力。
求一键三连:点赞、转发、在看。
我从清晨走过,也拥抱夜晚的星辰,人生没有捷径,你我皆平凡,你好,陌生人,一起共勉
