Easytool
Easytool 的目標是干掉大部分冗余的復雜代碼,從而最大限度的避免“復制粘貼”代碼的問題,使我們能去更專注業務,提升我們的代碼質量。
簡介
Easytool 是一個小型的Java工具類庫,封裝了一些常用的通用的方法,降低了相關API的學習成本,提高工作效率,使Java擁有函數式語言般的優雅。
Easytool 中的大部分方法來自開發過程中的真實需求,它既是大型項目開發中解決小問題的利器,也是小型項目中的效率擔當。
完整代碼地址:https://github.com/Jinhx128/easytool
模塊
名稱 | 介紹 |
---|---|
easytool-all | 包含所有模塊的引用 |
easytool-core | 核心包,包括對集合處理、日期、各類Util等 |
easytool-crypto | 加密解密模塊,提供對稱、非對稱和摘要算法封裝 |
easytool-process | 基于spring封裝的任務編排輕量級框架 |
1. easytool-core
1.1 待完善...
2. easytool-crypto
2.1 待完善...
3. easytool-process
easytool-process是一個基于spring封裝的任務編排輕量級框架
由來
- 起初是因為自己在開發過程中遇到比較復雜的業務場景,還伴隨著并發操作,就抽象了一層方法方便自己在項目中使用。
- 后面在其他項目也遇到類似的場景,此時就想抽象出來公共的東西,方便在其他項目能快速接入,這樣可以干掉大部分重復的冗余代碼,同時也降低了出錯的可能性。
使用場景
- 比較復雜的業務場景,比如訂單詳情,需要聚合大量的數據,而且根據訂單狀態的變化,需要的數據也有所變化。
3.1 組成
主要包含:上下文,節點,鏈路,其他組件
easytool-process-組成.jpg
3.1.1 上下文
- 所有節點之間的數據交互都通過上下文進行傳遞
- 上下文包含了整個鏈路執行過程中所需要用到的數據,包括入參,中間數據,出參等字段。
- 只需要創建一個類,把所需要用到的字段加入進去即可,命名一般是鏈路名+Context后綴。
3.1.2 節點
一個節點代表一個任務,節點通過編排組成鏈路。創建節點需要繼承AbstractNode抽象類,并實現相關抽象方法,由于依賴spring,所以需要加@Component注解。
easytool-process-節點執行流程.jpg
詳解
名稱 | 說明 |
---|---|
getDependsOnNodes() | 抽象方法,返回當前節點依賴的前置節點集合,必須實現。通過該方法進行鏈路任務編排,注意:只需要返回直接前置節點,比如鏈路為A->B->C,此時C節點只需要返回B節點即可,另外如果無依賴的節點,比如A節點,此時返回空或空集合即可 |
isSkip(ChainContext<DemoContext> chainContext) | 抽象方法,用于跳過當前節點,必須實現。返回ture則跳過,false則不跳過,比如某種狀態下不需要執行當前節點的場景 |
execute(ChainContext<DemoContext> chainContext) | 抽象方法,用于編寫當前節點的邏輯,必須實現。重點在此通過上下文chainContext與其他節點進行數據傳遞,會按照鏈路編排的順序執行各個節點,最終獲取相應的數據/操作 |
businessFail(String msg)/businessFail(int code, String msg) | 內置方法,當遇到業務異常時,比如某個數據不存在,此時可以直接中斷整個鏈路,返回業務錯誤提示,需要用到時調用即可 |
onBusinessFail(ChainContext<DemoContext> chainContext, BusinessException e) | 抽象方法,是一個回調方法,必須實現。需要搭配businessFail方法使用,當執行了上述方法后會執行 |
onUnknowFail(ChainContext<DemoContext> chainContext, Exception e) | 抽象方法,是一個回調方法,必須實現。當節點執行遇到未知錯誤時會執行 |
onTimeoutFail(ChainContext<DemoContext> chainContext) | 抽象方法,是一個回調方法,必須實現。當節點執行遇到超時錯誤時會執行 |
onSuccess(@NonNull ChainContext<T> chainContext) | 內置方法,是一個回調方法,當節點執行成功時會執行,需要用到時重寫即可 |
afterExecute(@NonNull ChainContext<T> chainContext) | 內置方法,是一個回調方法,當節點執行遇到完成時會執行。注意,是執行完成,就是無論有沒有出現異常,需要用到時重寫即可 |
3.1.3 鏈路
一個鏈路包含了多個已經按照順序編排好的節點,創建鏈路需要繼承AbstractChain抽象類,并實現相關抽象方法,由于依賴spring,所以需要加@Component注解。
easytool-process-鏈路執行流程.jpg
詳解
名稱 | 說明 |
---|---|
getChainTimeout() | 內置方法,返回的是該鏈路的超時時間,執行超過該時間則中斷鏈路并返回超時異常結果,默認值是200ms,需要替換時重寫即可 |
getThreadPool() | 內置方法,返回的是執行該鏈路的線程池,內置默認線程池,需要替換時重寫即可 |
execute(ChainContext<DemoContext> chainContext) | 抽象方法,用于編寫當前節點的邏輯,必須實現。重點在此通過上下文chainContext與其他節點進行數據傳遞,會按照鏈路編排的順序執行各個節點,最終獲取相應的數據/操作 |
KeyThreadContextConfig/SingletonThreadContextConfig | 內置類,用于配置線程上下文信息,包含新增,獲取,刪除三個動作 |
getThreadContextInitConfigs() | 內置方法,返回的是用于配置線程上下文信息集合,也就是4中的兩種,一種需要key,一種不需要。最常見的場景就是用于鏈路追蹤的traceId,需要用到時重寫即可。 |
checkParams(ChainContext<DemoContext> chainContext) | 抽象方法,用于在執行鏈路之前做參數校驗使用,必須實現 |
businessFail(String msg)/businessFail(int code, String msg) | 內置方法,當遇到業務異常時,比如某個數據不存在,此時可以直接中斷整個鏈路,返回業務錯誤提示,需要用到時調用即可行 |
onBusinessFail(ChainContext<DemoContext> chainContext, BusinessException e) | 抽象方法,是一個回調方法,必須實現。需要搭配businessFail方法使用,當執行了上述方法后會執行 |
onUnknowFail(ChainContext<DemoContext> chainContext, Exception e) | 抽象方法,是一個回調方法,必須實現。當鏈路執行遇到未知錯誤時會執行 |
onTimeoutFail(ChainContext<DemoContext> chainContext) | 抽象方法,是一個回調方法,必須實現。當鏈路執行遇到超時錯誤時會執行 |
onSuccess(@NonNull ChainContext<T> chainContext) | 內置方法,是一個回調方法,當鏈路執行成功時會執行,需要用到時重寫即可 |
afterExecute(@NonNull ChainContext<T> chainContext) | 內置方法,是一個回調方法,當鏈路執行遇到完成時會執行。注意,是執行完成,就是無論有沒有出現異常,需要用到時重寫即可 |
openMonitor() | 內置方法,用于開啟鏈路監控,會記錄并定時打印鏈路及各節點執行的耗時等信息。返回ture則開啟,false則不開啟,需要用到時重寫即可 |
setNodeInfo() | 抽象方法,用于添加鏈路節點,必須實現。內部通過下面方法添加節點,注意:鏈路的執行順序跟此處的添加順序無關 |
addInterruptNode/addInterruptNodes | 內置方法,增加一個中斷節點。執行遇到異常時中斷鏈路,并返回異常信息。參數:節點類,獲取節點超時時間方法 |
addAbandonNode/addAbandonNodes | 內置方法,增加一個拋棄節點。執行遇到異常時拋棄該節點,繼續執行后續節點。參數:節點類,獲取節點超時時間方法 |
addRetryNode/addRetryNodes | 內置方法,增加一個重試節點。執行遇到異常時重試執行該節點,達到最大重試次數后還未執行成功,則中斷鏈路,并返回異常信息。參數:節點類,獲取節點超時時間方法,重試次數 |
3.1.4 其他組件
- ChainContext:鏈路上下文,用于創建統一的上下文,該上下文還包含了其他信息,我們只需要關心內部的contextInfo即可,也就是操作chainContext.getContextInfo()。
- ChainHandler:鏈路執行器,用于執行指定鏈路,并可指定需要返回的數據。
- Monitor:監控器,開啟后會記錄并定時打印鏈路及各節點執行的耗時等信息。
- 查看日志:需要注意鏈路內部打印的日志級別為info!可以通過getThreadContextInitConfigs()內置方法設置鏈路id,然后通過下面的命令查詢
## 會有整個執行過程的細節日志
grep '鏈路id' xx.log
## 會有整個鏈路過程的細節日志,過濾一些可能無關的日志
grep '鏈路id' xx.log | grep 'process'
## 會有整個鏈路過程的細節日志,包括一些錯誤日志(錯誤棧信息可能換行)
grep -A 50 '鏈路id' xx.log | grep 'process'
3.2 簡單講一下Demo
easytool-process-demo鏈路結構.jpg
第一步,在pom.xml加入依賴,如下
<dependency>
<groupId>cc.jinhx</groupId>
<artifactId>easytool-all</artifactId>
<version>自行查看最新版本</version>
</dependency>
可以根據需求對每個模塊單獨引入,也可以通過引入easytool-all方式引入所有模塊。
第二步,創建上下文,如下
DemoContext
import lombok.Data;
/**
* DemoContext
*
* @author jinhx
* @since 2022-03-29
*/
@Data
public class DemoContext {
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 入參 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 入參 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
private String req;
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 中間數據 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
private String dataA;
private String dataB;
private String dataC;
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 中間數據 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 結果 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
private String dataD;
private String dataE;
private String dataF;
private String dataG;
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 結果 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
}
第三步,創建多個節點,DemoGetDataANode,DemoGetDataBNode,DemoGetDataCNode,DemoGetDataDNode,DemoGetDataENode,DemoGetDataFNode,DemoGetDataGNode,如下
DemoGetDataANode
import cc.jinhx.easytool.process.BusinessException;
import cc.jinhx.easytool.process.chain.ChainContext;
import cc.jinhx.easytool.process.demo.context.DemoContext;
import cc.jinhx.easytool.process.demo.service.DemoService;
import cc.jinhx.easytool.process.node.AbstractNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Set;
/**
* DemoGetDataANode
*
* @author jinhx
* @since 2022-03-29
*/
@Component
public class DemoGetDataANode extends AbstractNode<DemoContext> {
@Autowired
private DemoService demoService;
@Override
public Set<Class<? extends AbstractNode>> getDependsOnNodes() {
return null;
}
@Override
protected boolean isSkip(ChainContext<DemoContext> chainContext) {
return false;
}
@Override
protected void execute(ChainContext<DemoContext> chainContext) {
DemoContext demoContextInfo = chainContext.getContextInfo();
if ("req".equals(demoContextInfo.getReq())){
demoContextInfo.setDataA(demoService.getDataA());
}
}
@Override
public void onUnknowFail(ChainContext<DemoContext> chainContext, Exception e) {
}
@Override
public void onBusinessFail(ChainContext<DemoContext> chainContext, BusinessException e) {
}
@Override
public void onTimeoutFail(ChainContext<DemoContext> chainContext) {
}
}
DemoGetDataBNode
import cc.jinhx.easytool.process.BusinessException;
import cc.jinhx.easytool.process.chain.ChainContext;
import cc.jinhx.easytool.process.demo.context.DemoContext;
import cc.jinhx.easytool.process.demo.service.DemoService;
import cc.jinhx.easytool.process.node.AbstractNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* DemoGetDataBNode
*
* @author jinhx
* @since 2022-03-29
*/
@Component
public class DemoGetDataBNode extends AbstractNode<DemoContext> {
@Autowired
private DemoService demoService;
@Override
public Set<Class<? extends AbstractNode>> getDependsOnNodes() {
return new HashSet<>(Collections.singletonList(DemoGetDataANode.class));
}
@Override
protected boolean isSkip(ChainContext<DemoContext> chainContext) {
return false;
}
@Override
protected void execute(ChainContext<DemoContext> chainContext) {
DemoContext demoContextInfo = chainContext.getContextInfo();
if ("dataA".equals(demoContextInfo.getDataA())){
demoContextInfo.setDataB(demoService.getDataB());
}
}
@Override
public void onUnknowFail(ChainContext<DemoContext> chainContext, Exception e) {
}
@Override
public void onBusinessFail(ChainContext<DemoContext> chainContext, BusinessException e) {
}
@Override
public void onTimeoutFail(ChainContext<DemoContext> chainContext) {
}
}
DemoGetDataCNode
import cc.jinhx.easytool.process.BusinessException;
import cc.jinhx.easytool.process.chain.ChainContext;
import cc.jinhx.easytool.process.demo.context.DemoContext;
import cc.jinhx.easytool.process.demo.service.DemoService;
import cc.jinhx.easytool.process.node.AbstractNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* DemoGetDataBNode
*
* @author jinhx
* @since 2022-03-29
*/
@Component
public class DemoGetDataCNode extends AbstractNode<DemoContext> {
@Autowired
private DemoService demoService;
@Override
public Set<Class<? extends AbstractNode>> getDependsOnNodes() {
return new HashSet<>(Collections.singletonList(DemoGetDataANode.class));
}
@Override
protected boolean isSkip(ChainContext<DemoContext> chainContext) {
return false;
}
@Override
protected void execute(ChainContext<DemoContext> chainContext) {
DemoContext demoContextInfo = chainContext.getContextInfo();
if ("dataA".equals(demoContextInfo.getDataA())){
demoContextInfo.setDataC(demoService.getDataC());
}
}
@Override
public void onUnknowFail(ChainContext<DemoContext> chainContext, Exception e) {
}
@Override
public void onBusinessFail(ChainContext<DemoContext> chainContext, BusinessException e) {
}
@Override
public void onTimeoutFail(ChainContext<DemoContext> chainContext) {
}
}
DemoGetDataDNode
import cc.jinhx.easytool.process.BusinessException;
import cc.jinhx.easytool.process.chain.ChainContext;
import cc.jinhx.easytool.process.demo.context.DemoContext;
import cc.jinhx.easytool.process.demo.service.DemoService;
import cc.jinhx.easytool.process.node.AbstractNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* DemoGetDataDNode
*
* @author jinhx
* @since 2022-03-29
*/
@Component
public class DemoGetDataDNode extends AbstractNode<DemoContext> {
@Autowired
private DemoService demoService;
@Override
public Set<Class<? extends AbstractNode>> getDependsOnNodes() {
return new HashSet<>(Collections.singletonList(DemoGetDataANode.class));
}
@Override
protected boolean isSkip(ChainContext<DemoContext> chainContext) {
return false;
}
@Override
protected void execute(ChainContext<DemoContext> chainContext) {
DemoContext demoContextInfo = chainContext.getContextInfo();
if ("dataA".equals(demoContextInfo.getDataA())){
demoContextInfo.setDataD(demoService.getDataD());
}
}
@Override
public void onUnknowFail(ChainContext<DemoContext> chainContext, Exception e) {
}
@Override
public void onBusinessFail(ChainContext<DemoContext> chainContext, BusinessException e) {
}
@Override
public void onTimeoutFail(ChainContext<DemoContext> chainContext) {
}
}
DemoGetDataENode
import cc.jinhx.easytool.process.BusinessException;
import cc.jinhx.easytool.process.chain.ChainContext;
import cc.jinhx.easytool.process.demo.context.DemoContext;
import cc.jinhx.easytool.process.demo.service.DemoService;
import cc.jinhx.easytool.process.node.AbstractNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* DemoGetDataENode
*
* @author jinhx
* @since 2022-03-29
*/
@Component
public class DemoGetDataENode extends AbstractNode<DemoContext> {
@Autowired
private DemoService demoService;
@Override
public Set<Class<? extends AbstractNode>> getDependsOnNodes() {
return new HashSet<>(Arrays.asList(DemoGetDataBNode.class, DemoGetDataCNode.class));
}
@Override
protected boolean isSkip(ChainContext<DemoContext> chainContext) {
return false;
}
@Override
protected void execute(ChainContext<DemoContext> chainContext) {
DemoContext demoContextInfo = chainContext.getContextInfo();
if ("dataB".equals(demoContextInfo.getDataB()) && "dataC".equals(demoContextInfo.getDataC())){
demoContextInfo.setDataE(demoService.getDataE());
}
}
@Override
public void onUnknowFail(ChainContext<DemoContext> chainContext, Exception e) {
}
@Override
public void onBusinessFail(ChainContext<DemoContext> chainContext, BusinessException e) {
}
@Override
public void onTimeoutFail(ChainContext<DemoContext> chainContext) {
}
}
DemoGetDataFNode
import cc.jinhx.easytool.process.BusinessException;
import cc.jinhx.easytool.process.chain.ChainContext;
import cc.jinhx.easytool.process.demo.context.DemoContext;
import cc.jinhx.easytool.process.demo.service.DemoService;
import cc.jinhx.easytool.process.node.AbstractNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* DemoGetDataENode
*
* @author jinhx
* @since 2022-03-29
*/
@Component
public class DemoGetDataFNode extends AbstractNode<DemoContext> {
@Autowired
private DemoService demoService;
@Override
public Set<Class<? extends AbstractNode>> getDependsOnNodes() {
return new HashSet<>(Collections.singletonList(DemoGetDataCNode.class));
}
@Override
protected boolean isSkip(ChainContext<DemoContext> chainContext) {
return false;
}
@Override
protected void execute(ChainContext<DemoContext> chainContext) {
DemoContext demoContextInfo = chainContext.getContextInfo();
if ("dataC".equals(demoContextInfo.getDataC())){
demoContextInfo.setDataF(demoService.getDataF());
}
}
@Override
public void onUnknowFail(ChainContext<DemoContext> chainContext, Exception e) {
}
@Override
public void onBusinessFail(ChainContext<DemoContext> chainContext, BusinessException e) {
}
@Override
public void onTimeoutFail(ChainContext<DemoContext> chainContext) {
}
}
DemoGetDataGNode
import cc.jinhx.easytool.process.BusinessException;
import cc.jinhx.easytool.process.chain.ChainContext;
import cc.jinhx.easytool.process.demo.context.DemoContext;
import cc.jinhx.easytool.process.demo.service.DemoService;
import cc.jinhx.easytool.process.node.AbstractNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* DemoGetDataENode
*
* @author jinhx
* @since 2022-03-29
*/
@Component
public class DemoGetDataGNode extends AbstractNode<DemoContext> {
@Autowired
private DemoService demoService;
@Override
public Set<Class<? extends AbstractNode>> getDependsOnNodes() {
return new HashSet<>(Collections.singletonList(DemoGetDataENode.class));
}
@Override
protected boolean isSkip(ChainContext<DemoContext> chainContext) {
return false;
}
@Override
protected void execute(ChainContext<DemoContext> chainContext) {
DemoContext demoContextInfo = chainContext.getContextInfo();
if ("dataE".equals(demoContextInfo.getDataE())){
demoContextInfo.setDataG(demoService.getDataG());
}
}
@Override
public void onUnknowFail(ChainContext<DemoContext> chainContext, Exception e) {
}
@Override
public void onBusinessFail(ChainContext<DemoContext> chainContext, BusinessException e) {
}
@Override
public void onTimeoutFail(ChainContext<DemoContext> chainContext) {
}
}
注意
- 關注getDependsOnNodes()方法,要確定好各個節點的依賴情況,也就是正確編排好各個任務的順序,注意只需要返回直接前置節點即可。
- chainContext鏈路上下文,該上下文還包含了其他信息,我們只需要關心內部的contextInfo即可,也就是操作chainContext.getContextInfo()
第四步,創建鏈路
DemoChain
import cc.jinhx.easytool.process.chain.*;
import cc.jinhx.easytool.process.demo.context.DemoContext;
import cc.jinhx.easytool.process.demo.node.*;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* DemoChain
*
* @author jinhx
* @since 2022-03-29
*/
@Slf4j
@Component
public class DemoChain extends AbstractChain<DemoContext> {
private static final AtomicInteger CHAIN_THREAD_POOL_COUNTER = new AtomicInteger(0);
private static final int CPU_NUM = Runtime.getRuntime().availableProcessors();
/**
* 自定義鏈路線程池
*/
public static final ThreadPoolExecutor CHAIN_THREAD_POOL =
new ThreadPoolExecutor(
2, CPU_NUM * 2,
10, TimeUnit.MINUTES,
new LinkedBlockingQueue<>(1024),
(Runnable r) -> new Thread(r, "chain_thread_" + CHAIN_THREAD_POOL_COUNTER.incrementAndGet()),
(r, executor) -> log.info("chain has bean rejected" + r));
private final static long DEFAULT_CHAIN_TIMEOUT = 1000L;
private final static long DEFAULT_NODE_TIMEOUT = 500L;
@Override
protected long getChainTimeout() {
return DEFAULT_CHAIN_TIMEOUT;
}
@Override
protected ExecutorService getThreadPool() {
// 執行該鏈路的線程池
return CHAIN_THREAD_POOL;
}
@Override
protected Set<AbstractThreadContextConfig> getThreadContextInitConfigs() {
return new HashSet<>(Collections.singletonList(new KeyThreadContextConfig<>("traceId", MDC::get, MDC::put, MDC::remove)));
}
@Override
protected void checkParams(ChainContext<DemoContext> chainContext) {
}
/**
* 設置節點信息,鏈路的執行順序跟此處的添加順序無關
*/
@Override
protected void setNodeInfo() {
// 添加重試節點DemoGetDataANode,重試次數為1,并配置節點超時時間。執行遇到異常時重試執行該節點,達到最大重試次數后還未執行成功,則中斷鏈路,并返回異常信息
this.addRetryNode(DemoGetDataANode.class, ChainNode.RetryTimesEnum.ONE, DemoChain::getNodeTimeout);
// 添加中斷節點DemoGetDataBNode,DemoGetDataCNode,并配置節點超時時間。執行遇到異常時中斷鏈路,并返回異常信息
this.addInterruptNodes(Arrays.asList(DemoGetDataBNode.class, DemoGetDataCNode.class), DemoChain::getNodeTimeout);
// 添加拋棄節點DemoGetDataDNode,并配置節點超時時間。執行遇到異常時拋棄該節點,繼續執行后續節點
this.addAbandonNode(DemoGetDataDNode.class, DemoChain::getNodeTimeout);
// 添加中斷節點DemoGetDataENode,DemoGetDataFNode,DemoGetDataGNode,并配置節點超時時間。執行遇到異常時中斷鏈路,并返回異常信息
this.addInterruptNodes(Arrays.asList(DemoGetDataENode.class, DemoGetDataFNode.class, DemoGetDataGNode.class), DemoChain::getNodeTimeout);
}
public static long getNodeTimeout() {
return DEFAULT_NODE_TIMEOUT;
}
}
注意
- 關注setNodeInfo()方法,如果少添加了某個節點,則鏈路沒法正常執行起來,會報異常提示鏈路不完整。注意:鏈路的執行順序跟此處的添加順序無關。
第五步,編寫單元測試類,如下
DemoTest
import cc.jinhx.easytool.process.ProcessResult;
import cc.jinhx.easytool.process.SpringContextConfig;
import cc.jinhx.easytool.process.chain.ChainContext;
import cc.jinhx.easytool.process.chain.ChainHandler;
import cc.jinhx.easytool.process.demo.chain.DemoChain;
import cc.jinhx.easytool.process.demo.context.DemoContext;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* DemoTest
*
* @author jinhx
* @since 2022-03-21
*/
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringContextConfig.class)
public class DemoTest {
@Test
public void test1() {
// 創建上下文
ChainContext<DemoContext> chainContext = ChainContext.create(DemoContext.class);
// 設置入參
chainContext.getContextInfo().setReq("req");
// 執行指定鏈路,并返回所有數據
ProcessResult<DemoContext> processResult1 = ChainHandler.execute(DemoChain.class, chainContext);
System.out.println(processResult1);
// 執行指定鏈路,并返回指定數據
ProcessResult<String> processResult2 = ChainHandler.execute(DemoChain.class, chainContext, DemoContext::getDataG);
System.out.println(processResult2);
}
}
說明
- ChainContext.create(TestContext.class)方法用于創建統一的上下文,該上下文還包含了其他信息,我們只需要關心內部的contextInfo即可,也就是操作chainContext.getContextInfo()
- ChainHandler.execute(DemoChain.class, chainContext)方法用于執行指定鏈路,包含多個重載方法,入參包括:鏈路類,上下文,要獲取的數據的方法。
- 查看日志:需要注意鏈路內部打印的日志級別為info!可以通過getThreadContextInitConfigs()內置方法設置鏈路id,然后通過下面的命令查詢
## 會有整個執行過程的細節日志
grep '鏈路id' xx.log
## 會有整個鏈路過程的細節日志,過濾一些可能無關的日志
grep '鏈路id' xx.log | grep 'process'
## 會有整個鏈路過程的細節日志,包括一些錯誤日志(錯誤棧信息可能換行)
grep -A 50 '鏈路id' xx.log | grep 'process'