SpringBoot+責任鏈實現接口動態編排!
一、背景
項目中有一個 OpenApi 接口提供給客戶(上游系統)調用。
這個接口中包含十幾個功能點,比如:入參校驗、系統配置校驗、基本數據入庫、核心數據入庫、發送給消息中心、發送給 MQ.....
不同的客戶對這個接口的要求也不同,有些功能不需要,有些需要添加特定功能。
二、思路
- 基于以上背景,考慮把十幾個功能點進行拆分形成獨立的功能。因此使用責任鏈模式實現。
- 創建一個抽象類(ComponentAbstract.java),每個拆分功能點繼承抽象類形成子類。
- 子類創建時,需要在 @Component("1") 注解中設置類名,如果不設置咋使用默認的(小駝峰)名稱;關注公眾號:碼猿技術專欄,回復關鍵詞:1111 獲取阿里內部java性能調優手冊
- 子類之間的數據通信使用自定義的上下文類(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
publicabstractclass 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);
} 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);
}
}
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
publicclass Test1 extends ComponentAbstract {
@Override
public 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
publicclass Test2 extends ComponentAbstract {
@Override
public 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
publicclass Test3 extends ComponentAbstract {
@Override
public 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
publicclass Test4 extends ComponentAbstract {
@Override
public 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
publicclass 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
publicclass AopProxyUtils implements ApplicationContextAware {
privatestatic ApplicationContext applicationContext;
/**
* 實現ApplicationContextAware接口的setApplicationContext方法,
* 用于注入ApplicationContext。
*/
@Override
public void setApplicationContext(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);
}
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
publicclass 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
我是因為公司內部對依賴包的引入有要求審核嚴格,所以自己實現了一個簡單版本的。