Easytool 文檔

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 上下文
  1. 所有節點之間的數據交互都通過上下文進行傳遞
  2. 上下文包含了整個鏈路執行過程中所需要用到的數據,包括入參,中間數據,出參等字段。
  3. 只需要創建一個類,把所需要用到的字段加入進去即可,命名一般是鏈路名+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 其他組件
  1. ChainContext:鏈路上下文,用于創建統一的上下文,該上下文還包含了其他信息,我們只需要關心內部的contextInfo即可,也就是操作chainContext.getContextInfo()。
  2. ChainHandler:鏈路執行器,用于執行指定鏈路,并可指定需要返回的數據。
  3. Monitor:監控器,開啟后會記錄并定時打印鏈路及各節點執行的耗時等信息。
  4. 查看日志:需要注意鏈路內部打印的日志級別為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) {

    }

}
注意
  1. 關注getDependsOnNodes()方法,要確定好各個節點的依賴情況,也就是正確編排好各個任務的順序,注意只需要返回直接前置節點即可。
  2. 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;
    }

}
注意
  1. 關注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);
    }

}
說明
  1. ChainContext.create(TestContext.class)方法用于創建統一的上下文,該上下文還包含了其他信息,我們只需要關心內部的contextInfo即可,也就是操作chainContext.getContextInfo()
  2. ChainHandler.execute(DemoChain.class, chainContext)方法用于執行指定鏈路,包含多個重載方法,入參包括:鏈路類,上下文,要獲取的數據的方法。
  3. 查看日志:需要注意鏈路內部打印的日志級別為info!可以通過getThreadContextInitConfigs()內置方法設置鏈路id,然后通過下面的命令查詢
## 會有整個執行過程的細節日志
grep '鏈路id' xx.log

## 會有整個鏈路過程的細節日志,過濾一些可能無關的日志
grep '鏈路id' xx.log | grep 'process'

## 會有整個鏈路過程的細節日志,包括一些錯誤日志(錯誤棧信息可能換行)
grep -A 50 '鏈路id' xx.log | grep 'process'
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容