文章目錄
- 前因
- 責任鏈:像工作臺一樣組織代碼
- Code
- SEQ
- 3.1 定義處理器規范
- 3.2 實現具體處理器
- 3.3 共享上下文
- 3.4 組裝責任鏈
- 適用場景
- 優勢
前因
2000多行的業務邏輯里,各種校驗規則、促銷計算、庫存操作像意大利面條一樣纏繞在一起。最要命的是這樣的代碼結構:
public void createOrder(OrderRequest request) {// 參數校驗if(request.getUserId() == null){throw new Exception("用戶ID不能為空");}if(request.getItems().isEmpty()){throw new Exception("商品不能為空");}// 庫存檢查for(Item item : request.getItems()){if(!inventoryService.checkStock(item)){throw new Exception("庫存不足");}}// 優惠計算if(request.getCouponId() != null){// 復雜的優惠計算邏輯...}// 風控檢查if(riskService.checkRisk(request)){throw new Exception("風控攔截");}// 保存訂單// ...
}
每次需求變更都像在雷區跳舞,稍有不慎就會引發連鎖反應。新來的小伙伴看著滿屏的if-else,戰戰兢兢地問:“咱們能不能換個寫法?”
責任鏈:像工作臺一樣組織代碼
這時候就該責任鏈模式登場了!這個設計模式的核心思想是:把每個處理步驟拆成獨立的處理器,像流水線一樣連接起來。
想象一下快遞分揀系統:
- 包裹先過安檢機(校驗處理器)
- 然后到分揀區(路由處理器)
- 接著稱重計費(價格處理器)
- 最后裝車發貨(持久化處理器)
每個環節只關心自己的職責,處理完就交給下一個環節。這樣無論是要調整環節順序,還是增加新的處理環節,都變得非常靈活。
Code
SEQ
3.1 定義處理器規范
package com.artisan.chain.handler;import com.artisan.chain.model.OrderContext;/*** 處理器接口定義*/
public interface OrderHandler {/*** 處理 : 返回true ,繼續處理,返回false,終止處理** @param orderContext* @return*/boolean handle(OrderContext orderContext);/*** 獲取處理順序** @return*/int getOrder();
}
3.2 實現具體處理器
參數校驗處理器:
package com.artisan.chain.handler;import com.artisan.chain.model.OrderContext;
import com.artisan.chain.model.OrderRequest;
import com.artisan.chain.model.OrderResult;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;@Component
@Order(100)
public class ValidationHandler implements OrderHandler {/*** 處理訂單請求的函數* 該函數主要用于驗證訂單請求中的必要信息是否完整* 如果驗證失敗,它會設置上下文結果并返回false** @param context 訂單上下文,包含訂單請求和結果* @return 如果驗證通過,返回true;否則返回false*/@Overridepublic boolean handle(OrderContext context) {// 獲取訂單請求OrderRequest request = context.getRequest();// 驗證用戶ID是否為空if (StringUtils.isEmpty(request.getUserId())) {// 如果用戶ID為空,設置驗證失敗的結果并返回context.setResult(OrderResult.fail("VALIDATION_ERROR", "用戶ID不能為空"));return false;}// 驗證訂單商品列表是否為空if (CollectionUtils.isEmpty(request.getItems())) {// 如果訂單商品列表為空,設置驗證失敗的結果并返回context.setResult(OrderResult.fail("VALIDATION_ERROR", "訂單商品不能為空"));return false;}// 如果所有驗證都通過,返回truereturn true;}/*** 獲取當前對象的順序值** 順序值用于確定對象處理的優先級或顯示順序* 值越小 優先級越高** @return 返回順序值100,表示當前對象的默認順序位置*/@Overridepublic int getOrder() {return 100;}
}
庫存檢查處理器:
package com.artisan.chain.handler;import com.artisan.chain.model.OrderContext;
import com.artisan.chain.model.OrderRequest;
import com.artisan.chain.model.OrderResult;
import com.artisan.chain.service.InventoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;/*** 庫存檢查處理器,用于在訂單處理流程中檢查商品庫存*/
@Component
@Order(200)
public class StockCheckHandler implements OrderHandler{// 注入庫存服務,用于檢查商品庫存情況@Autowiredprivate InventoryService inventoryService;/*** 處理訂單中的庫存檢查邏輯* 遍歷訂單中的每項商品,檢查庫存是否充足* 如果任何商品的庫存不足,訂單處理失敗,并更新訂單上下文的結果** @param context 訂單上下文,包含訂單請求和處理結果* @return 如果所有商品庫存充足,返回true;否則返回false*/@Overridepublic boolean handle(OrderContext context) {// 遍歷訂單中的每個商品項,檢查庫存for (OrderRequest.OrderItem item : context.getRequest().getItems()) {// 如果庫存檢查失敗,更新訂單上下文的結果為庫存錯誤,并返回falseif (!inventoryService.checkStock(item.getSkuId(), item.getQuantity())) {context.setResult(OrderResult.fail("STOCK_ERROR","商品[" + item.getSkuId() + "]庫存不足"));return false;}}// 所有商品庫存充足,返回truereturn true;}/*** 獲取訂單處理的順序* 用于確定在訂單處理流程中執行庫存檢查的順序** @return 訂單處理的順序值*/@Overridepublic int getOrder() {return 200;}
}
package com.artisan.chain.handler;import com.artisan.chain.model.OrderContext;
import com.artisan.chain.model.OrderRequest;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import java.util.List;/*** 定義一個處理折扣的組件*/
@Component
@Order(300)
public class DiscountHandler implements OrderHandler {/*** 處理訂單中的折扣計算** @param context 訂單上下文,包含訂單請求和屬性* @return 總是返回true,表示處理成功*/@Overridepublic boolean handle(OrderContext context) {// 模擬優惠計算double total = calculateTotal(context.getRequest().getItems());double discount = calculateDiscount(total, context.getRequest().getCouponId());// 將最終金額存入上下文中context.getAttributes().put("finalAmount", total - discount);return true;}/*** 計算訂單總金額** @param items 訂單中的商品列表* @return 訂單總金額*/private double calculateTotal(List<OrderRequest.OrderItem> items) {// 模擬價格計算,這里簡化處理,實際應根據商品價格和數量計算return items.stream().mapToDouble(item -> item.getQuantity() * 100.0) // 模擬價格.sum();}/*** 計算折扣金額** @param total 訂單總金額* @param couponId 優惠券ID,如果為空則不應用折扣* @return 折扣金額*/private double calculateDiscount(double total, String couponId) {// 模擬優惠計算,如果有優惠券ID,則應用10%的折扣return couponId != null ? total * 0.1 : 0;}/*** 獲取處理順序** @return 處理順序值*/@Overridepublic int getOrder() {return 300;}
}
3.3 共享上下文
package com.artisan.chain.model;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.HashMap;
import java.util.Map;/*** 上下文對象(共享數據載體)** 訂單上下文類,用于處理訂單請求并生成訂單結果* 它封裝了訂單請求、處理結果以及相關屬性**/@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderContext {/*** 訂單請求對象,包含訂單的相關請求信息*/private OrderRequest request;/*** 訂單處理結果對象,用于存儲訂單處理后的信息*/private OrderResult result = new OrderResult();/*** 訂單相關屬性集合,用于存儲訂單處理過程中需要的臨時信息* 鍵為屬性名稱,值為屬性值*/private Map<String, Object> attributes = new HashMap<>();/*** 構造方法,初始化訂單上下文** @param request 訂單請求對象,不能為空*/public OrderContext(OrderRequest request) {this.request = request;}
}
3.4 組裝責任鏈
package com.artisan.chain.manager;import com.artisan.chain.handler.OrderHandler;
import com.artisan.chain.model.OrderContext;
import com.artisan.chain.model.OrderResult;
import org.springframework.stereotype.Component;import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;/*** 訂單鏈式處理器管理類* 通過鏈式調用多個OrderHandler來處理訂單邏輯* 該類負責構建和管理這些處理訂單的處理器鏈*/
@Component
public class OrderChainManager {/*** 存儲所有的訂單處理器,按照處理順序排序*/private final List<OrderHandler> handlers;/*** 構造函數,初始化訂單處理器鏈* @param handlers 一個未排序的訂單處理器集合*/public OrderChainManager(List<OrderHandler> handlers) {// 根據每個處理器的順序值進行排序,確保它們按照正確的順序執行this.handlers = handlers.stream().sorted(Comparator.comparingInt(OrderHandler::getOrder)).collect(Collectors.toList());}/*** 執行訂單處理邏輯* 遍歷每個訂單處理器,直到所有處理器都處理完畢或某個處理器決定中斷鏈式處理* @param context 訂單上下文,包含訂單的處理信息和結果* @return 處理后的訂單結果*/public OrderResult execute(OrderContext context) {// 遍歷處理器鏈,如果某個處理器處理失敗(返回false),則中斷鏈式處理for (OrderHandler handler : handlers) {if (!handler.handle(context)) {break;}}// 返回最終的訂單處理結果return context.getResult();}}
package com.artisan.chain.config;import com.artisan.chain.handler.OrderHandler;
import com.artisan.chain.manager.OrderChainManager;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.Collections;
import java.util.List;/*** 配置類,用于定義和配置訂單處理鏈相關的Bean*/
@Configuration
public class HandlerConfig {/*** 創建并配置OrderChainManager Bean** @param handlersProvider 一個對象提供者,用于提供訂單處理器列表如果未找到則提供一個空列表* @return 返回一個OrderChainManager實例,用于管理訂單處理鏈*/@Beanpublic OrderChainManager orderChainManager(ObjectProvider<List<OrderHandler>> handlersProvider) {// 初始化OrderChainManager,使用提供的訂單處理器列表,如果沒有提供則使用空列表return new OrderChainManager(handlersProvider.getIfAvailable(Collections::emptyList));}
}
# 測試成功案例
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{"userId": "user123","items": [{"skuId": "SKU001", "quantity": 2},{"skuId": "SKU002", "quantity": 1}],"couponId": "COUPON2023"
}'# 測試庫存不足
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{"userId": "user123","items": [{"skuId": "SKU001", "quantity": 200}]
}'
適用場景
-
多步驟流程:訂單創建、審批流、支付流程等
-
動態業務:需要頻繁調整步驟順序的業務
-
復雜校驗:多層次、多條件的校驗場景
-
插件式架構:需要動態加載/卸載功能的系統
優勢
- 解耦性:每個處理邏輯獨立成Handler,修改單個處理器不影響其他組件
- 可擴展性:新增業務邏輯只需添加新Handler,無需修改主流程
- 動態編排:通過配置靈活調整處理器執行順序和啟用狀態
- 可測試性:每個Handler可單獨進行單元測試
- 復用性:通用處理器(如日志記錄)可跨多個業務場景復用