?目錄
開始調用
初始化?
歡迎關注我的博客!26屆java選手,一起加油💘💦👨?🎓😄😂
引入
最近在學習一個項目中的鏈式多分枝規則樹模型的使用,模型如下:
如圖所示:
這是一種?鏈式多分支規則樹模型?設計模式,核心是通過功能節點自主決策后續流程執行鏈路,相比責任鏈模式,它允許更靈活的分支擴展,每個節點像 “決策者” 一樣根據規則選擇下一步走向。以下是核心組件拆解:
StrategyMapper、StrategyHandler、AbstractStrategyRouter定義在type層,與業務層隔離,然后將DefaultActivityStrategyFactory、AbstractGroupBuyMarketSupport與各節點定義在業務層進行業務邏輯處理。
- 策略映射器(StrategyMapper)與策略處理器(StrategyHandler):
實現抽象類?AbstractStrategyRouter
,前者負責策略映射(如定義不同場景對應規則),后者處理具體策略邏輯(如執行規則計算)。 - 策略路由抽象類(AbstractStrategyRouter):
定義路由規則的抽象框架,規范策略映射、處理的通用邏輯,是整個流程的 “規則模板”。 - 策略工廠(DefaultActivityStrategyFactory):
作為 “對象制造工廠”,負責創建拼團活動相關的策略實例,確保策略對象的統一管理與創建。 - 功能服務支撐類(AbstractGroupBuyMarketSupport):
提供底層通用服務(如數據校驗、基礎計算),像 “后勤保障”,供上層節點流程調用。 - 節點體系(RootNode、SwitchRoot 等):
- 根節點(RootNode):流程起點,類似 “入口”。
- 開關節點(SwitchRoot):核心決策點,根據條件(如用戶類型、活動規則)選擇分支(走向其他節點或默認分支)。
- 營銷節點(MarketNode):處理營銷相關邏輯(如優惠計算)。
- 結尾節點(EndNode):流程終點,標志鏈路結束。
開始調用
@Resourceprivate DefaultActivityStrategyFactory defaultActivityStrategyFactory;@Overridepublic TrialBalanceEntity indexMarketTrial(MarketProductEntity marketProductEntity) throws Exception {// 獲取執行策略StrategyHandler<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> strategyHandler = defaultActivityStrategyFactory.strategyHandler();// 受理試算操作return strategyHandler.apply(marketProductEntity, new DefaultActivityStrategyFactory.DynamicContext());}
初始化
起始點為DefaultActivityStrategyFactory 策略工廠 ,返回了rootNode給我們,也就是此時的 strategyHandler是rootNode類型的,如下:
@Service
public class DefaultActivityStrategyFactory {private final RootNode rootNode;public DefaultActivityStrategyFactory(RootNode rootNode) {this.rootNode = rootNode;}public StrategyHandler<MarketProductEntity, DynamicContext, TrialBalanceEntity> strategyHandler() {return rootNode;}@Data@Builder@AllArgsConstructor@NoArgsConstructorpublic static class DynamicContext {// 拼團活動營銷配置值對象private GroupBuyActivityDiscountVO groupBuyActivityDiscountVO;// 商品信息private SkuVO skuVO;// 折扣金額private BigDecimal deductionPrice;// 支付金額private BigDecimal payPrice;// 活動可見性限制private boolean visible;// 活動private boolean enable;}}
執行流程
// 受理試算操作return strategyHandler.apply(marketProductEntity, new DefaultActivityStrategyFactory.DynamicContext());
rootNode繼承自AbstractGroupBuyMarketSupport——功能服務支撐類,AbstractGroupBuyMarketSupport又繼承自AbstractMultiThreadStrategyRouter——策略路由抽象類,就會去執行下面這里的apply方法,
public abstract class AbstractMultiThreadStrategyRouter<T, D, R> implements StrategyMapper<T, D, R>, StrategyHandler<T, D, R> {@Getter@Setterprotected StrategyHandler<T, D, R> defaultStrategyHandler = StrategyHandler.DEFAULT;public R router(T requestParameter, D dynamicContext) throws Exception {StrategyHandler<T, D, R> strategyHandler = get(requestParameter, dynamicContext);if(null != strategyHandler) return strategyHandler.apply(requestParameter, dynamicContext);return defaultStrategyHandler.apply(requestParameter, dynamicContext);}@Overridepublic R apply(T requestParameter, D dynamicContext) throws Exception {// 異步加載數據multiThread(requestParameter, dynamicContext);// 業務流程受理return doApply(requestParameter, dynamicContext);}/*** 異步加載數據*/protected abstract void multiThread(T requestParameter, D dynamicContext) throws ExecutionException, InterruptedException, TimeoutException;/*** 業務流程受理*/protected abstract R doApply(T requestParameter, D dynamicContext) throws Exception;}
會先去AbstractGroupBuyMarketSupport看看有沒有重寫multiThread和doApply方法,
public abstract class AbstractGroupBuyMarketSupport<MarketProductEntity, DynamicContext, TrialBalanceEntity> extends AbstractMultiThreadStrategyRouter<cn.bugstack.domain.activity.model.entity.MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, cn.bugstack.domain.activity.model.entity.TrialBalanceEntity> {protected long timeout = 500;@Resourceprotected IActivityRepository repository;@Overrideprotected void multiThread(cn.bugstack.domain.activity.model.entity.MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws ExecutionException, InterruptedException, TimeoutException {// 缺省的方法}}
當我們的rootNode不想用到多線程加載數據的時候就沒有重寫這個方法,為空,但是rootNode重寫了doApply方法,也就是在這里處理rootNode想要處理的業務,
@Slf4j
@Service
public class RootNode extends AbstractGroupBuyMarketSupport<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> {@Resourceprivate SwitchNode switchNode;@Overrideprotected TrialBalanceEntity doApply(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {log.info("商品查詢試算服務-RootNode userId:{} requestParameter:{}", requestParameter.getUserId(), JSON.toJSONString(requestParameter));// 參數判斷if (StringUtils.isBlank(requestParameter.getUserId()) || StringUtils.isBlank(requestParameter.getGoodsId()) ||StringUtils.isBlank(requestParameter.getSource()) || StringUtils.isBlank(requestParameter.getChannel())) {throw new AppException(ResponseCode.ILLEGAL_PARAMETER.getCode(), ResponseCode.ILLEGAL_PARAMETER.getInfo());}return router(requestParameter, dynamicContext);}@Overridepublic StrategyHandler<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> get(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {return switchNode;}}
執行完doApply方法,就執行return router()方法,這里帶上請求參數和上下文對象,router方法在AbstractMultiThreadStrategyRouter類中,負責流轉節點
public R router(T requestParameter, D dynamicContext) throws Exception {StrategyHandler<T, D, R> strategyHandler = get(requestParameter, dynamicContext);if(null != strategyHandler) return strategyHandler.apply(requestParameter, dynamicContext);return defaultStrategyHandler.apply(requestParameter, dynamicContext);}
這里調用的get是StrategyMapper——策略映射器的get方法,因為當前對象是rootNode,如果rootNode實現了get就會回到rootNode的get中
public interface StrategyMapper<T, D, R> {/*** 獲取待執行策略** @param requestParameter 入參* @param dynamicContext 上下文* @return 返參* @throws Exception 異常*/StrategyHandler<T, D, R> get(T requestParameter, D dynamicContext) throws Exception;}
回到rootNode,這里重寫了get,也就是返回我們需要從rootNode去往的下一個節點
public class RootNode extends AbstractGroupBuyMarketSupport<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> {@Resourceprivate SwitchNode switchNode;@Overrideprotected TrialBalanceEntity doApply(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {log.info("拼團商品查詢試算服務-RootNode userId:{} requestParameter:{}", requestParameter.getUserId(), JSON.toJSONString(requestParameter));// 參數判斷if (StringUtils.isBlank(requestParameter.getUserId()) || StringUtils.isBlank(requestParameter.getGoodsId()) ||StringUtils.isBlank(requestParameter.getSource()) || StringUtils.isBlank(requestParameter.getChannel())) {throw new AppException(ResponseCode.ILLEGAL_PARAMETER.getCode(), ResponseCode.ILLEGAL_PARAMETER.getInfo());}return router(requestParameter, dynamicContext);}@Overridepublic StrategyHandler<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> get(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {return switchNode;}}
這里會返回switchNode給AbstractMultiThreadStrategyRouter,此時會執行switchNode的apply方法。
public R router(T requestParameter, D dynamicContext) throws Exception {StrategyHandler<T, D, R> strategyHandler = get(requestParameter, dynamicContext);if(null != strategyHandler) return strategyHandler.apply(requestParameter, dynamicContext);return defaultStrategyHandler.apply(requestParameter, dynamicContext);}
與上述過程一樣,如果switchNode實現了apply中的方法,就會執行,如果沒有實現,就不會執行。再次執行doApply后就會再執行router,然后執行switch的get,這里返回了market Node,就會繼續往下,以此類推的執行下去,
@Slf4j
@Service
public class SwitchNode extends AbstractGroupBuyMarketSupport<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> {//業務邏輯return router(requestParameter, dynamicContext);}@Overridepublic StrategyHandler<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> get(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {return marketNode;}}
MarketNode,在這里我們重寫MultiThread方法,使用FutureTask異步查詢數據后放入上下文,然后在DoApplay中還可以獲取到上下文的數據進行業務處理,處理完畢后在此處還能按照業務進入下一個節點或者返回錯誤節點。
@Slf4j
@Service
public class MarketNode extends AbstractGroupBuyMarketSupport<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> {@Resourceprivate ErrorNode errorNode;@Resourceprivate TagNode tagNode;@Overrideprotected void multiThread(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws ExecutionException, InterruptedException, TimeoutException {// 異步查詢活動配置// 異步查詢商品信息 - 在實際生產中,商品有同步庫或者調用接口查詢。這里暫時使用DB方式查詢。// 寫入上下文 - 對于一些復雜場景,獲取數據的操作,有時候會在下N個節點獲取,這樣前置查詢數據,可以提高接口響應效率}@Overridepublic TrialBalanceEntity doApply(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {// 獲取上面查詢得到數據的上下文數據// 執行業務,繼續放入上下文return router(requestParameter, dynamicContext);}@Overridepublic StrategyHandler<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> get(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {//走異常節點if (null == dynamicContext.getGroupBuyActivityDiscountVO() || null == dynamicContext.getSkuVO() || null == dynamicContext.getDeductionPrice()) {return errorNode;}return tagNode;}}