巧妙利用 SpringBoot 責任連模式,讓編程事半功倍!
一、什么是責任鏈模式?
責任鏈模式(Chain of Responsibility Pattern),顧名思義,為請求者和接受者之間創(chuàng)建一條對象處理鏈路,避免請求發(fā)送者與接收者耦合在一起!
責任鏈模式,是一種實用性非常強的設計模式,比較典型的應用場景有:
- Apache Tomcat 對 Encoding 編碼處理的處理
- SpringBoot ??的攔截器、過濾器鏈
- netty 中的處理鏈
- 支付風控的機制
- ?志處理級別
尤其是當程序的處理流程很長的時候,采用責任鏈設計模式,不僅實現(xiàn)優(yōu)雅,而且易復用可擴展!
今天我們就一起來了解一下在 SpringBoot 中如何應用責任鏈模式!
二、代碼實踐
在 SpringBoot 中,責任鏈模式的實踐方式有好幾種,今天我們主要抽三種實踐方式給大家介紹。
我們以某下單流程為例,將其切成多個獨立檢查邏輯,可能會經(jīng)過的數(shù)據(jù)驗證處理流程如下:
采用責任鏈設計模式來編程,代碼實踐如下!
2.1、方式一
首先,我們定義一個簡易版的下單對象OrderContext
。
public class OrderContext {
/**
* 請求唯一序列ID
*/
private String seqId;
/**
* 用戶ID
*/
private String userId;
/**
* 產品skuId
*/
private Long skuId;
/**
* 下單數(shù)量
*/
private Integer amount;
/**
* 用戶收貨地址ID
*/
private String userAddressId;
//..set、get
}
然后,我們定義一個數(shù)據(jù)處理接口OrderHandleIntercept
,用于標準化執(zhí)行!
public interface OrderHandleIntercept {
/**
* 指定執(zhí)行順序
* @return
*/
int sort();
/**
* 對參數(shù)進行處理
* @param context
* @return
*/
OrderAddContext handle(OrderAddContext context);
}
接著,我們分別創(chuàng)建三個不同的接口實現(xiàn)類,并指定執(zhí)行順序,內容如下:
RepeatOrderHandleInterceptService
:用于重復下單的邏輯驗證ValidOrderHandleInterceptService
:用于驗證請求參數(shù)是否合法BankOrderHandleInterceptService
:用于檢查客戶賬戶余額是否充足
@Component
public class RepeatOrderHandleInterceptService implements OrderHandleIntercept {
@Override
public int sort() {
//用于重復下單的邏輯驗證,在執(zhí)行順序為1
return 1;
}
@Override
public OrderAddContext handle(OrderAddContext context) {
System.out.println("通過seqId,檢查客戶是否重復下單");
return context;
}
}
@Component
public class ValidOrderHandleInterceptService implements OrderHandleIntercept {
@Override
public int sort() {
//用于驗證請求參數(shù)是否合法,執(zhí)行順序為2
return 2;
}
@Override
public OrderAddContext handle(OrderAddContext context) {
System.out.println("檢查請求參數(shù),是否合法,并且獲取客戶的銀行賬戶");
return context;
}
}
@Component
public class BankOrderHandleInterceptService implements OrderHandleIntercept {
@Override
public int sort() {
//用于檢查客戶賬戶余額是否充足,在執(zhí)行順序為3
return 3;
}
@Override
public OrderAddContext handle(OrderAddContext context) {
System.out.println("檢查銀行賬戶是否合法,調用銀行系統(tǒng)檢查銀行賬戶余額是否滿足下單金額");
return context;
}
}
再然后,我們還需要創(chuàng)建一個訂單數(shù)據(jù)驗證管理器OrderHandleChainService
,用于管理這些實現(xiàn)類。
@Component
public class OrderHandleChainService implements ApplicationContextAware {
private List<OrderHandleIntercept> handleList = new ArrayList<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//獲取指定的接口實現(xiàn)類,并按照sort進行排序,放入List中
Map<String, OrderHandleIntercept> serviceMap = applicationContext.getBeansOfType(OrderHandleIntercept.class);
handleList = serviceMap.values().stream()
.sorted(Comparator.comparing(OrderHandleIntercept::sort))
.collect(Collectors.toList());
}
/**
* 執(zhí)行處理
* @param context
* @return
*/
public OrderAddContext execute(OrderAddContext context){
for (OrderHandleIntercept handleIntercept : handleList) {
context =handleIntercept.handle(context);
}
return context;
}
}
最后,我們編寫單元測試來看看效果如何!
@RunWith(SpringRunner.class)
@SpringBootTest
public class CalculatorServiceTest {
@Autowired
private OrderHandleChainService orderHandleChainService;
@Test
public void test(){
orderHandleChainService.execute(new OrderAddContext());
}
}
執(zhí)行結果如下:
通過seqId,檢查客戶是否重復下單
檢查請求參數(shù),是否合法,并且獲取客戶的銀行賬戶
檢查銀行賬戶是否合法,調用銀行系統(tǒng)檢查銀行賬戶余額是否滿足下單金額
如果需要繼續(xù)加驗證流程或者處理流程,只需要重新實現(xiàn)OrderHandleIntercept
接口就行,其他的代碼無需改動!
當然,有的同學可能覺得這種方法用的不習慣,不喜歡通過sort()
來指定順序,也可以通過如下方式進行手動add
排序。
@Component
public class OrderHandleChainService {
private List<OrderHandleIntercept> handleList = new ArrayList<>();
@Autowired
private ValidOrderHandleInterceptService validOrderHandleInterceptService;
@Autowired
private RepeatOrderHandleInterceptService repeatOrderHandleInterceptService;
@Autowired
private BankOrderHandleInterceptService bankOrderHandleInterceptService;
@PostConstruct
public void init(){
//依次手動add對象
handleList.add(repeatOrderHandleInterceptService);
handleList.add(validOrderHandleInterceptService);
handleList.add(bankOrderHandleInterceptService);
}
/**
* 執(zhí)行處理
* @param context
* @return
*/
public OrderAddContext execute(OrderAddContext context){
for (OrderHandleIntercept handleIntercept : handleList) {
context =handleIntercept.handle(context);
}
return context;
}
}
2.2、方式二
第二種實現(xiàn)方式,就更簡單了,我們通過注解@Order
來指定排序,代替手動方法排序sort()
,操作方式如下:
/**
* 指定注入順序為1
*
*/
@Order(1)
@Component
public class RepeatOrderHandleInterceptService implements OrderHandleIntercept {
//...省略
}
/**
* 指定注入順序為2
*
*/
@Order(2)
@Component
public class ValidOrderHandleInterceptService implements OrderHandleIntercept {
//...省略
}
/**
* 指定注入順序為3
*
*/
@Order(3)
@Component
public class BankOrderHandleInterceptService implements OrderHandleIntercept {
//...省略
}
@Component
public class OrderHandleChainService {
@Autowired
private List<OrderHandleIntercept> handleList;
/**
* 執(zhí)行處理
* @param context
* @return
*/
public OrderAddContext execute(OrderAddContext context){
for (OrderHandleIntercept handleIntercept : handleList) {
context =handleIntercept.handle(context);
}
return context;
}
}
運行單元測試,你會發(fā)現(xiàn)結果與上面運行的結果一致,原因Spring
的ioc
容器,支持通過Map
或者List
來直接注入對象,省去自己排序。
2.3、方式三
通過定義抽象類來實現(xiàn)責任鏈設計模式,還是以上面的案例為例,我們需要先定義一個抽象類,比如AbstractOrderHandle
。
public abstract class AbstractOrderHandle {
/**
* 責任鏈,下一個鏈接節(jié)點
*/
private AbstractOrderHandle next;
/**
* 執(zhí)行入口
* @param context
* @return
*/
public OrderAddContext execute(OrderAddContext context){
context = handle(context);
// 判斷是否還有下個責任鏈節(jié)點,沒有的話,說明已經(jīng)是最后一個節(jié)點
if(getNext() != null){
getNext().execute(context);
}
return context;
}
/**
* 對參數(shù)進行處理
* @param context
* @return
*/
public abstract OrderAddContext handle(OrderAddContext context);
public AbstractOrderHandle getNext() {
return next;
}
public void setNext(AbstractOrderHandle next) {
this.next = next;
}
}
然后,分別創(chuàng)建三個處理類,并排好序號。
@Order(1)
@Component
public class RepeatOrderHandle extends AbstractOrderHandle {
@Override
public OrderAddContext handle(OrderAddContext context) {
System.out.println("通過seqId,檢查客戶是否重復下單");
return context;
}
}
@Order(2)
@Component
public class ValidOrderHandle extends AbstractOrderHandle {
@Override
public OrderAddContext handle(OrderAddContext context) {
System.out.println("檢查請求參數(shù),是否合法,并且獲取客戶的銀行賬戶");
return context;
}
}
@Order(3)
@Component
public class BankOrderHandle extends AbstractOrderHandle {
@Override
public OrderAddContext handle(OrderAddContext context) {
System.out.println("檢查銀行賬戶是否合法,調用銀行系統(tǒng)檢查銀行賬戶余額是否滿足下單金額");
return context;
}
}
接著,創(chuàng)建一個責任鏈管理器,比如OrderHandleManager
。
@Component
public class OrderHandleManager {
@Autowired
private List<AbstractOrderHandle> orderHandleList;
@PostConstruct
public void init(){
//如果List沒有按照@Order注解方式排序,可以通過如下方式手動排序
Collections.sort(orderHandleList, AnnotationAwareOrderComparator.INSTANCE);
int size = orderHandleList.size();
for (int i = 0; i < size; i++) {
if(i == size -1){
orderHandleList.get(i).setNext(null);
} else {
orderHandleList.get(i).setNext(orderHandleList.get(i + 1));
}
}
}
/**
* 執(zhí)行處理
* @param context
* @return
*/
public OrderAddContext execute(OrderAddContext context){
context = orderHandleList.get(0).execute(context);
return context;
}
}
最后,我們編寫單元測試,來看看效果。
@RunWith(SpringRunner.class)
@SpringBootTest
public class CalculatorServiceTest {
@Autowired
private OrderHandleManager orderHandleManager;
@Test
public void test(){
orderHandleManager.execute(new OrderAddContext());
}
}
運行結果與預期一致!
通過seqId,檢查客戶是否重復下單
檢查請求參數(shù),是否合法,并且獲取客戶的銀行賬戶
檢查銀行賬戶是否合法,調用銀行系統(tǒng)檢查銀行賬戶余額是否滿足下單金額
三、小結
本文主要圍繞在 SpringBoot 中如何引入責任鏈設計模式,介紹了三種玩法,其中第二種用法最多,其次就是第一種,第三種用的比較少,第三種本質是一種鏈式寫法,可能理解上不如第一種直觀,但是效果是一樣的。
有效的使用責任鏈設計模式,可以顯著降低業(yè)務代碼的復雜度,可讀性更好,更容易擴展,希望對大家有幫助!