春節(jié)期間,我用責(zé)任鏈模式重構(gòu)了業(yè)務(wù)代碼
本文轉(zhuǎn)載自微信公眾號(hào)「源碼興趣圈」,作者龍臺(tái)。轉(zhuǎn)載本文請(qǐng)聯(lián)系源碼興趣圈公眾號(hào)。
前言
文章開篇,拋出一個(gè)老生常談的問題,學(xué)習(xí)設(shè)計(jì)模式有什么作用?
設(shè)計(jì)模式主要是為了應(yīng)對(duì)代碼的復(fù)雜性,讓其滿足開閉原則,提高代碼的擴(kuò)展性
另外,學(xué)習(xí)的設(shè)計(jì)模式 一定要在業(yè)務(wù)代碼中落實(shí),只有理論沒有真正實(shí)施,是無(wú)法真正掌握并且靈活運(yùn)用設(shè)計(jì)模式的
這篇文章主要說(shuō) 責(zé)任鏈設(shè)計(jì)模式,認(rèn)識(shí)此模式是在讀 Mybatis 源碼時(shí), Interceptor 攔截器主要使用的就是責(zé)任鏈,當(dāng)時(shí)讀過后就留下了很深的印象(內(nèi)心 OS:還能這樣玩)
文章先從基礎(chǔ)概念說(shuō)起,另外分析一波 Mybatis 源碼中是如何運(yùn)用的,最后按照 "習(xí)俗",設(shè)計(jì)一個(gè)真實(shí)業(yè)務(wù)場(chǎng)景上的應(yīng)用
責(zé)任鏈設(shè)計(jì)模式大綱如下:
- 什么是責(zé)任鏈模式
- 完成真實(shí)的責(zé)任鏈業(yè)務(wù)場(chǎng)景設(shè)計(jì)
- Mybatis Interceptor 底層實(shí)現(xiàn)
- 責(zé)任鏈模式總結(jié)
什么是責(zé)任鏈模式
舉個(gè)例子,SpringMvc 中可以定義攔截器,并且可以定義多個(gè)。當(dāng)一個(gè)用戶發(fā)起請(qǐng)求時(shí),順利的話請(qǐng)求會(huì)經(jīng)過所有攔截器,最終到達(dá)業(yè)務(wù)代碼邏輯,SpringMvc 攔截器設(shè)計(jì)就是使用了責(zé)任鏈模式
為什么說(shuō)順利的話會(huì)經(jīng)過所有攔截器?因?yàn)檎?qǐng)求不滿足攔截器自定義規(guī)則會(huì)被打回,但這并不是責(zé)任鏈模式的唯一處理方式,繼續(xù)往下看
在責(zé)任鏈模式中,多個(gè)處理器(參照上述攔截器)依次處理同一個(gè)請(qǐng)求。一個(gè)請(qǐng)求先經(jīng)過 A 處理器處理,然后再把請(qǐng)求傳遞給 B 處理器,B 處理器處理完后再傳遞給 C 處理器,以此類推,形成一個(gè)鏈條,鏈條上的每個(gè)處理器 各自承擔(dān)各自的處理職責(zé)
責(zé)任鏈模式中多個(gè)處理器形成的處理器鏈在進(jìn)行處理請(qǐng)求時(shí),有兩種處理方式:
- 請(qǐng)求會(huì)被 所有的處理器都處理一遍,不存在中途終止的情況,這里參照 MyBatis 攔截器理解
- 二則是處理器鏈執(zhí)行請(qǐng)求中,某一處理器執(zhí)行時(shí),如果不符合自制定規(guī)則的話,停止流程,并且剩下未執(zhí)行處理器就不會(huì)被執(zhí)行,大家參照 SpringMvc 攔截器理解
這里通過代碼的形式對(duì)兩種處理方式作出解答,方便讀者更好的理解。首先看下第一種,請(qǐng)求會(huì)經(jīng)過所有處理器執(zhí)行的情況
圖1 責(zé)任鏈模式一種實(shí)現(xiàn)
IHandler 負(fù)責(zé)抽象處理器行為,handle() 則是不同處理器具體需要執(zhí)行的方法,HandleA、HandleB 為具體需要執(zhí)行的處理器類,HandlerChain 則是將處理器串成一條鏈執(zhí)行的處理器鏈
- public class ChainApplication {
- public static void main(String[] args) {
- HandlerChain handlerChain = new HandlerChain();
- handlerChain.addHandler(Lists.newArrayList(new HandlerA(), new HandlerB()));
- handlerChain.handle();
- /**
- * 程序執(zhí)行結(jié)果:
- * HandlerA打印:執(zhí)行 HandlerA
- * HandlerB打?。簣?zhí)行 HandlerB
- */
- }
- }
這種責(zé)任鏈執(zhí)行方式會(huì)將所有的 處理器全部執(zhí)行一遍,不會(huì)被打斷。Mybatis 攔截器用的正是此類型,這種類型 重點(diǎn)在對(duì)請(qǐng)求過程中的數(shù)據(jù)或者行為進(jìn)行改變
圖2 參考Mybatis攔截器實(shí)現(xiàn)
而另外一種責(zé)任鏈模式實(shí)現(xiàn),則是會(huì)對(duì)請(qǐng)求有阻斷作用,阻斷產(chǎn)生的前置條件是在處理器中自定義的,代碼中的實(shí)現(xiàn)較簡(jiǎn)單,讀者可以聯(lián)想 SpringMvc 攔截器的實(shí)現(xiàn)流程
圖3 責(zé)任鏈模式一種實(shí)現(xiàn)
根據(jù)代碼看的出來(lái),在每一個(gè) IHandler 實(shí)現(xiàn)類中會(huì)返回一個(gè)布爾類型的返回值,如果返回布爾值為 false,那么責(zé)任鏈發(fā)起類會(huì)中斷流程,剩余處理器將不會(huì)被執(zhí)行。就像我們定義在 SpringMvc 中的 Token 攔截器,如果 Token 失效就不能繼續(xù)訪問系統(tǒng),處理器將請(qǐng)求打回
- public class ChainApplication {
- public static void main(String[] args) {
- HandlerChain handlerChain = new HandlerChain();
- handlerChain.addHandler(Lists.newArrayList(new HandlerA(), new HandlerB()));
- boolean resultFlag = handlerChain.handle();
- if (!resultFlag) {
- System.out.println("責(zé)任鏈中處理器不滿足條件");
- }
- }
- }
讀者可以自己在 IDEA 中實(shí)現(xiàn)兩種不同的責(zé)任鏈模式,對(duì)比其中的不同,設(shè)想下業(yè)務(wù)中真實(shí)的應(yīng)用場(chǎng)景,再或者可以跑 SpringBoot 項(xiàng)目,創(chuàng)建多個(gè)攔截器來(lái)佐證文中的說(shuō)辭
圖4 參考SpringMvc攔截器實(shí)現(xiàn)
本章節(jié)介紹了責(zé)任鏈設(shè)計(jì)模式的具體語(yǔ)義,以及不同責(zé)任鏈實(shí)現(xiàn)類型代碼舉例,并以 Mybatis、SpringMvc 攔截器為參照點(diǎn),介紹各自不同的代碼實(shí)現(xiàn)以及應(yīng)用場(chǎng)景
責(zé)任鏈業(yè)務(wù)場(chǎng)景設(shè)計(jì)
趁熱打鐵,本小節(jié)對(duì)使用的真實(shí)業(yè)務(wù)場(chǎng)景進(jìn)行舉例說(shuō)明。假設(shè)業(yè)務(wù)場(chǎng)景是這樣的,我們 系統(tǒng)處在一個(gè)下游服務(wù),因?yàn)闃I(yè)務(wù)需求,系統(tǒng)中所使用的 基礎(chǔ)數(shù)據(jù)需要從上游中臺(tái)同步到系統(tǒng)數(shù)據(jù)庫(kù)
基礎(chǔ)數(shù)據(jù)包含了很多類型數(shù)據(jù),雖然數(shù)據(jù)在中臺(tái)會(huì)有一定驗(yàn)證,但是 數(shù)據(jù)只要是人為錄入就極可能存在問題,遵從對(duì)上游系統(tǒng)不信任原則,需要對(duì)數(shù)據(jù)接收時(shí)進(jìn)行一系列校驗(yàn)
最初是要進(jìn)行一系列驗(yàn)證原則才能入庫(kù)的,后來(lái)因?yàn)楣て趩栴}只放了一套非空驗(yàn)證,趁著春節(jié)期間時(shí)間還算寬裕,把這套驗(yàn)證規(guī)則骨架放進(jìn)去
從我們系統(tǒng)的接入數(shù)據(jù)規(guī)則而言,個(gè)人覺得需要支持以下幾套規(guī)則
- 必填項(xiàng)校驗(yàn),如果數(shù)據(jù)無(wú)法滿足業(yè)務(wù)所必須字段要求,數(shù)據(jù)一旦落入庫(kù)中就會(huì)產(chǎn)生一系列問題
- 非法字符校驗(yàn),因?yàn)閿?shù)據(jù)如何錄入,上游系統(tǒng)的錄入規(guī)則是什么樣的我們都不清楚,這一項(xiàng)規(guī)則也是必須的
- 長(zhǎng)度校驗(yàn),理由同上,如果系統(tǒng)某字段長(zhǎng)度限制 50,但是接入來(lái)的數(shù)據(jù) 500長(zhǎng)度,這也會(huì)造成問題
為了讓讀者了解業(yè)務(wù)嵌入責(zé)任鏈模式的前因,這里列舉了三套校驗(yàn)規(guī)則,當(dāng)然真實(shí)中可能不止這三套。但是 一旦將責(zé)任鏈模式嵌入數(shù)據(jù)同步流程,就會(huì) 完全符合文初所提的開閉原則,提高代碼的擴(kuò)展性
本案例設(shè)計(jì)模式中的開閉原則通過 Spring 提供支持,后續(xù)添加新的校驗(yàn)規(guī)則就可以不必修改原有代碼
這里要再?gòu)?qiáng)調(diào)下,設(shè)計(jì)模式的應(yīng)用場(chǎng)景一定要靈活掌握,只有這樣才能在合適的業(yè)務(wù)場(chǎng)景合理運(yùn)用對(duì)象的設(shè)計(jì)模式
既然設(shè)計(jì)模式場(chǎng)景說(shuō)過了,最后說(shuō)一下需要達(dá)成的業(yè)務(wù)需求。將一個(gè)批量數(shù)據(jù)經(jīng)過處理器鏈的處理,返回出符合要求的數(shù)據(jù)分類
定義頂級(jí)驗(yàn)證接口和一系列處理器實(shí)現(xiàn)類沒什么難度,但是應(yīng)該如何進(jìn)行鏈?zhǔn)秸{(diào)用呢?
這一塊代碼需要有一定 Spring 基礎(chǔ)才能理解,一起來(lái)看下 VerifyHandlerChain 如何將所有處理器串成一條鏈
VerifyHandlerChain 處理流程如下:
- 實(shí)現(xiàn)自 InitializingBean 接口,在對(duì)應(yīng)實(shí)現(xiàn)方法中獲取 IOC 容器中類型為 VerifyHandler 的 Bean,也就是 EmptyVerifyHandler、SexyVerifyHandler
- 將 VerifyHandler 類型的 Bean 添加到處理器鏈容器中
- 定義校驗(yàn)方法 verify(),對(duì)入?yún)?shù)據(jù)展開處理器鏈的全部調(diào)用,如果過程中發(fā)現(xiàn)已無(wú)需要驗(yàn)證的數(shù)據(jù),直接返回
這里使用 SpringBoot 項(xiàng)目中默認(rèn)測(cè)試類,來(lái)測(cè)試一下如何調(diào)用
- @SpringBootTest
- class ChainApplicationTests {
- @Autowired
- private VerifyHandlerChain verifyHandlerChain;
- @Test
- void contextLoads() {
- List<Object> verify = verifyHandlerChain.verify(Lists.newArrayList("源碼興趣圈", "@龍臺(tái)"));
- System.out.println(verify);
- }
- }
這樣的話,如果客戶或者產(chǎn)品提校驗(yàn)相關(guān)的需求時(shí),我們只需要實(shí)現(xiàn) VerifyHandler 接口新建個(gè)校驗(yàn)規(guī)則實(shí)現(xiàn)類就 OK 了,這樣符合了設(shè)計(jì)模式的原則:滿足開閉原則,提高代碼的擴(kuò)展性
熟悉之前作者寫過設(shè)計(jì)模式的文章應(yīng)該知道,強(qiáng)調(diào)設(shè)計(jì)模式重語(yǔ)義,而不是具體的實(shí)現(xiàn)過程。所以,你看咱們這個(gè)校驗(yàn)代碼,把責(zé)任鏈兩種模式結(jié)合了使用
上面的代碼只是示例代碼,實(shí)際業(yè)務(wù)中的實(shí)現(xiàn)要比這復(fù)雜很多,比如:
- 如何定義處理器的先后調(diào)用順序。比如說(shuō)某一個(gè)處理器執(zhí)行時(shí)間很長(zhǎng)并且過濾數(shù)據(jù)很少,所以希望把它放到最后面執(zhí)行
- 這是為當(dāng)前業(yè)務(wù)的所有數(shù)據(jù)類型進(jìn)行過濾,如何自定義單個(gè)數(shù)據(jù)類型過濾。比如你接入學(xué)生數(shù)據(jù),學(xué)號(hào)有一定校驗(yàn)規(guī)則,這種處理器類肯定只適合單一類型
還有很多的業(yè)務(wù)場(chǎng)景,所以設(shè)計(jì)模式強(qiáng)調(diào)的應(yīng)該是一種思想,而不是固定的代碼寫法,需要結(jié)合業(yè)務(wù)場(chǎng)景靈活變通
責(zé)任鏈模式的好處
一定要使用責(zé)任鏈模式么?不使用能不能完成業(yè)務(wù)需求?
回答是肯定可以,設(shè)計(jì)模式只是幫助減少代碼的復(fù)雜性,讓其滿足開閉原則,提高代碼的擴(kuò)展性。如果不使用同樣可以完成需求
如果不使用責(zé)任鏈模式,上面說(shuō)的真實(shí)同步場(chǎng)景面臨兩個(gè)問題
- 如果把上述說(shuō)的代碼邏輯校驗(yàn)規(guī)則寫到一起,毫無(wú)疑問這個(gè)類或者說(shuō)這個(gè)方法函數(shù)奇大無(wú)比。減少代碼復(fù)雜性一貫方法是:將大塊代碼邏輯拆分成函數(shù),將大類拆分成小類,是應(yīng)對(duì)代碼復(fù)雜性的常用方法。如果此時(shí)說(shuō):可以把不同的校驗(yàn)規(guī)則拆分成不同的函數(shù),不同的類,這樣不也可以滿足減少代碼復(fù)雜性的要求么。這樣拆分是能解決代碼復(fù)雜性,但是這樣就會(huì)面臨第二個(gè)問題
- 開閉原則:添加一個(gè)新的功能應(yīng)該是,在已有代碼基礎(chǔ)上擴(kuò)展代碼,而非修改已有代碼。大家設(shè)想一下,假設(shè)你寫了三套校驗(yàn)規(guī)則,運(yùn)行過一段時(shí)間,這時(shí)候領(lǐng)導(dǎo)讓加第四套,是不是要在原有代碼上改動(dòng)
綜上所述,在合適的場(chǎng)景運(yùn)用適合的設(shè)計(jì)模式,能夠讓代碼設(shè)計(jì)復(fù)雜性降低,變得更為健壯。朝更遠(yuǎn)的說(shuō)也能讓自己的編碼設(shè)計(jì)能力有所提高,告別被人吐槽的爛代碼...
Mybatis Interceptor底層實(shí)現(xiàn)
上面說(shuō)了那么多,框架底層源碼是怎么設(shè)計(jì)并且使用責(zé)任鏈模式的?之前在看 Mybatis 3.4.x 源碼時(shí)了解到 Interceptor 底層實(shí)現(xiàn)就是責(zé)任鏈模式,這里和讀者分享 Interceptor 具體實(shí)現(xiàn)
開門見山,直接把視線聚焦到 Mybatis 源碼,版本號(hào) 3.4.7-SNAPSHOT
熟悉么?是不是和我們上面用到的責(zé)任鏈模式差不太多,有處理器集合 interceptors,有添加處理器方法
Mybatis Interceptor 不僅用到了責(zé)任鏈,還用到了動(dòng)態(tài)代理,服務(wù)于 Mybatis 四大 "護(hù)教法王",在創(chuàng)建對(duì)象時(shí)通過動(dòng)態(tài)代理和責(zé)任鏈相結(jié)合組裝而成插件模塊
- ParameterHandler
- ResultSetHandler
- StatementHandler
- Executor
使用過 Mybatis 的讀者應(yīng)該知道,查詢 SQL 的分頁(yè)語(yǔ)句就是使用 Interceptor 實(shí)現(xiàn),比如市場(chǎng)上的 PageHelper、Mybatis-Plus 分頁(yè)插件再或者我們自實(shí)現(xiàn)的分頁(yè)插件(應(yīng)該沒有項(xiàng)目組使用顯示調(diào)用多條語(yǔ)句組成分頁(yè)吧)
拿查詢語(yǔ)句舉例,如果定義了多個(gè)查詢相關(guān)的攔截器,會(huì)先經(jīng)過攔截器的代碼加工,所有的攔截器執(zhí)行完畢后才會(huì)走真正查詢數(shù)據(jù)庫(kù)操作
扯的話就扯遠(yuǎn)了,能夠知道如何用、在哪用就可以了。通過 Interceptor 也能知道一點(diǎn),想要讀框架源碼,需要一定的設(shè)計(jì)模式基礎(chǔ)。如果對(duì)責(zé)任鏈、動(dòng)態(tài)代理不清楚,那么就不能理解這一塊的精髓
結(jié)言
文章通過圖文并茂的方式幫助大家理解責(zé)任鏈設(shè)計(jì)模式,在兩種類型示例代碼以及舉例實(shí)際業(yè)務(wù)場(chǎng)景下,相信小伙伴已經(jīng)掌握了如何在合適的場(chǎng)景使用責(zé)任鏈設(shè)計(jì)模式
看完文章后可以結(jié)合 Mybatis、SpringMvc 攔截器更深入掌握責(zé)任鏈模式的應(yīng)用場(chǎng)景以及使用手法。另外可以結(jié)合項(xiàng)目中實(shí)際業(yè)務(wù)場(chǎng)景靈活使用,相信真正使用后的你會(huì)對(duì)責(zé)任鏈模式產(chǎn)生更深入的了解
文章參考:《設(shè)計(jì)模式之美:職責(zé)鏈模式》