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