1. 前言
設(shè)計(jì)模式在軟件開發(fā)中被廣泛使用。通過使用設(shè)計(jì)模式,開發(fā)人員可以更加高效地開發(fā)出高質(zhì)量的軟件系統(tǒng),提高代碼的可讀性、可維護(hù)性和可擴(kuò)展性。
責(zé)任鏈模式是一種常用的行為型設(shè)計(jì)模式,它將請(qǐng)求沿著處理鏈進(jìn)行發(fā)送,直到其中一個(gè)處理者對(duì)請(qǐng)求進(jìn)行處理為止。在責(zé)任鏈模式中,通常會(huì)有多個(gè)處理者,每個(gè)處理者都有一個(gè)處理請(qǐng)求的方法。當(dāng)一個(gè)請(qǐng)求到達(dá)處理鏈的起點(diǎn)時(shí),會(huì)依次傳遞給每個(gè)處理者進(jìn)行處理,直到某個(gè)處理者能夠處理該請(qǐng)求。這樣可以保證每個(gè)請(qǐng)求都能被處理,并且可以根據(jù)實(shí)際情況動(dòng)態(tài)地添加或刪除處理者,以滿足不同的需求。
責(zé)任鏈模式可以幫助降低系統(tǒng)的耦合度,增加系統(tǒng)的靈活性和可擴(kuò)展性,其在SpringMVC、Netty等許多框架中均有實(shí)現(xiàn)。責(zé)任鏈模式常用于以下場(chǎng)景:處理復(fù)雜的請(qǐng)求邏輯,例如權(quán)限驗(yàn)證、日志記錄等;避免請(qǐng)求發(fā)送者和接收者之間的耦合關(guān)系;動(dòng)態(tài)地組織處理流程,以適應(yīng)不同的請(qǐng)求類型和復(fù)雜度。
我們?cè)谌粘i_發(fā)中如果要使用責(zé)任鏈模式,通常需要自己來實(shí)現(xiàn),但自己臨時(shí)實(shí)現(xiàn)的責(zé)任鏈既不通用,也很容易產(chǎn)生框架與業(yè)務(wù)代碼耦合不清等問題,增加Code Review 的成本。
Netty的代碼向來以優(yōu)雅著稱,早年我在閱讀Netty的源碼時(shí),萌生出將其責(zé)任鏈的實(shí)現(xiàn)應(yīng)用到業(yè)務(wù)開發(fā)中的想法,之后花了點(diǎn)時(shí)間將Netty中責(zé)任鏈的實(shí)現(xiàn)代碼抽取出來,形成了本項(xiàng)目,也就是pie。pie的核心代碼均來自Netty,絕大部分的 API 與 Netty 是一致的。
pie 是一個(gè)可快速上手的責(zé)任鏈框架,開發(fā)者只需要專注業(yè)務(wù),開發(fā)相應(yīng)的業(yè)務(wù)Handler,即可完成業(yè)務(wù)的責(zé)任鏈落地。
一分鐘學(xué)會(huì)、三分鐘上手、五分鐘應(yīng)用,歡迎 star。
pie 源碼地址:https://github.com/feiniaojin/pie.git
pie 案例工程源碼地址:https://github.com/feiniaojin/pie-example.git
2. 快速入門
2.1 引入 maven 依賴
pie 目前已打包發(fā)布到 maven 中央倉庫,開發(fā)者可以直接通過 maven 坐標(biāo)將其引入到項(xiàng)目中。
com.feiniaojin.ddd.ecosystem/groupId?> pie/artifactId?> 1.0/version?> /dependency?>
目前最新的版本是 1.0
2.2 實(shí)現(xiàn)出參工廠
出參也就是執(zhí)行結(jié)果,一般的執(zhí)行過程都要求有執(zhí)行結(jié)果返回。實(shí)現(xiàn) OutboundFactory 接口,用于產(chǎn)生接口默認(rèn)返回值。
例如:
public class OutFactoryImpl implements OutboundFactory { @Override public Object newInstance() { Result result = new Result(); result.setCode(0); result.setMsg("ok"); return result; } }
2.3 實(shí)現(xiàn) handler 接口完成業(yè)務(wù)邏輯
在 pie 案例工程( https://github.com/feiniaojin/pie-example.git )的 Example1 中,為了展示 pie 的使用方法,實(shí)現(xiàn)了一個(gè)虛擬的業(yè)務(wù)邏輯:CMS類項(xiàng)目修改文章標(biāo)題、正文,大家不要關(guān)注修改操作放到兩個(gè) handler 中是否合理,僅作為講解案例。
三個(gè) Handler 功能如下:
CheckParameterHandler:用于參數(shù)校驗(yàn)。
ArticleModifyTitleHandler:用于修改文章的標(biāo)題。
ArticleModifyContentHandler:用于修改文章的正文。
CheckParameterHandler 的代碼如下:
public class CheckParameterHandler implements ChannelHandler { private Logger logger = LoggerFactory.getLogger(CheckParameterHandler.class); @Override public void channelProcess(ChannelHandlerContext ctx, Object in, Object out) throws Exception { logger.info("參數(shù)校驗(yàn):開始執(zhí)行"); if (in instanceof ArticleTitleModifyCmd) { ArticleTitleModifyCmd cmd = (ArticleTitleModifyCmd) in; String articleId = cmd.getArticleId(); Objects.requireNonNull(articleId, "articleId不能為空"); String title = cmd.getTitle(); Objects.requireNonNull(title, "title不能為空"); String content = cmd.getContent(); Objects.requireNonNull(content, "content不能為空"); } logger.info("參數(shù)校驗(yàn):校驗(yàn)通過,即將進(jìn)入下一個(gè)Handler"); ctx.fireChannelProcess(in, out); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause, Object in, Object out) throws Exception { logger.error("參數(shù)校驗(yàn):異常處理邏輯", cause); Result re = (Result) out; re.setCode(400); re.setMsg("參數(shù)異常"); } }
ArticleModifyTitleHandler 的代碼如下:
public class ArticleModifyTitleHandler implements ChannelHandler { private Logger logger = LoggerFactory.getLogger(ArticleModifyTitleHandler.class); @Override public void channelProcess(ChannelHandlerContext ctx, Object in, Object out) throws Exception { logger.info("修改標(biāo)題:進(jìn)入修改標(biāo)題的Handler"); ArticleTitleModifyCmd cmd = (ArticleTitleModifyCmd) in; String title = cmd.getTitle(); //修改標(biāo)題的業(yè)務(wù)邏輯 logger.info("修改標(biāo)題:title={}", title); logger.info("修改標(biāo)題:執(zhí)行完成,即將進(jìn)入下一個(gè)Handler"); ctx.fireChannelProcess(in, out); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause, Object in, Object out) throws Exception { logger.error("修改標(biāo)題:異常處理邏輯"); Result re = (Result) out; re.setCode(1501); re.setMsg("修改標(biāo)題發(fā)生異常"); } }
ArticleModifyContentHandler 的代碼如下:
public class ArticleModifyContentHandler implements ChannelHandler { private Logger logger = LoggerFactory.getLogger(ArticleModifyContentHandler.class); @Override public void channelProcess(ChannelHandlerContext ctx, Object in, Object out) throws Exception { logger.info("修改正文:進(jìn)入修改正文的Handler"); ArticleTitleModifyCmd cmd = (ArticleTitleModifyCmd) in; logger.info("修改正文,content={}", cmd.getContent()); logger.info("修改正文:執(zhí)行完成,即將進(jìn)入下一個(gè)Handler"); ctx.fireChannelProcess(in, out); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause, Object in, Object out) throws Exception { logger.error("修改標(biāo)題:異常處理邏輯"); Result re = (Result) out; re.setCode(1502); re.setMsg("修改正文發(fā)生異常"); } }
2.4 通過 BootStrap 拼裝并執(zhí)行
public class ArticleModifyExample1 { private final static Logger logger = LoggerFactory.getLogger(ArticleModifyExample1.class); public static void main(String[] args) { //構(gòu)造入?yún)? ArticleTitleModifyCmd dto = new ArticleTitleModifyCmd(); dto.setArticleId("articleId_001"); dto.setTitle("articleId_001_title"); dto.setContent("articleId_001_content"); //創(chuàng)建引導(dǎo)類 BootStrap bootStrap = new BootStrap(); //拼裝并執(zhí)行 Result result = (Result) bootStrap .inboundParameter(dto)//入?yún)? .outboundFactory(new ResultFactory())//出參工廠 .channel(new ArticleModifyChannel())//自定義channel .addChannelHandlerAtLast("checkParameter", new CheckParameterHandler())//第一個(gè)handler .addChannelHandlerAtLast("modifyTitle", new ArticleModifyTitleHandler())//第二個(gè)handler .addChannelHandlerAtLast("modifyContent", new ArticleModifyContentHandler())//第三個(gè)handler .process();//執(zhí)行 //result為執(zhí)行結(jié)果 logger.info("result:code={},msg={}", result.getCode(), result.getMsg()); } }
2.5 執(zhí)行結(jié)果
以下是運(yùn)行 ArticleModifyExample1 的 main 方法打出的日志,可以看到我們定義的 handler 被逐個(gè)執(zhí)行了。
3. 異常處理
3.1 Handler 異常處理
當(dāng)某個(gè)Handler執(zhí)行發(fā)生異常時(shí),我們可將其異常處理邏輯實(shí)現(xiàn)在當(dāng)前 Handler 的 exceptionCaught 方法中。
在 pie 案例工程( https://github.com/feiniaojin/pie-example.git )的 example2 包中,展示了某個(gè) Handler 拋出異常時(shí)的處理方式。
假設(shè) ArticleModifyTitleHandler 的業(yè)務(wù)邏輯會(huì)拋出異常,實(shí)例代碼如下:
public class ArticleModifyTitleHandler implements ChannelHandler { private Logger logger = LoggerFactory.getLogger(ArticleModifyTitleHandler.class); @Override public void channelProcess(ChannelHandlerContext ctx, Object in, Object out) throws Exception { logger.info("修改標(biāo)題:進(jìn)入修改標(biāo)題的Handler"); ArticleTitleModifyCmd cmd = (ArticleTitleModifyCmd) in; String title = cmd.getTitle(); //此處的異常用于模擬執(zhí)行過程中出現(xiàn)異常的場(chǎng)景 throw new RuntimeException("修改title發(fā)生異常"); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause, Object in, Object out) throws Exception { logger.error("修改標(biāo)題:異常處理邏輯"); Result re = (Result) out; re.setCode(1501); re.setMsg("修改標(biāo)題發(fā)生異常"); } }
此時(shí) ArticleModifyTitleHandler 的 channelProcess 方法一定會(huì)拋出異常, 在當(dāng)前 Handler 的 exceptionCaught 方法中對(duì)異常進(jìn)行了處理。
運(yùn)行 ArticleModifyExample2 的 main 方法,輸出如下:
3.2 全局異常處理
有時(shí)候,我們不想每個(gè) handler 都處理一遍異常,我們希望在執(zhí)行鏈的最后統(tǒng)一進(jìn)行處理。
在 ArticleModifyExample3 中,我們展示了通過一個(gè)全局異常進(jìn)行最后的異常處理,其實(shí)現(xiàn)主要分為以下幾步:
3.2.1 業(yè)務(wù) Handler 傳遞異常
如果業(yè)務(wù) Handler 實(shí)現(xiàn)了 ChannelHandler 接口,那么需要手工調(diào)用 ctx.fireExceptionCaught 方法向下傳遞異常。
例如 CheckParameterHandler 捕獲到異常時(shí)的示例如下:
@Override public class XXXHandler implements ChannelHandler { //省略其他邏輯 //異常處理 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause, Object in, Object out) throws Exception { logger.info("參數(shù)校驗(yàn)的異常處理邏輯:不處理直接向后傳遞"); ctx.fireExceptionCaught(cause, in, out); } }
如果業(yè)務(wù) Handler 繼承了 ChannelHandlerAdapter,如果沒有重寫 fireExceptionCaught 方法,則默認(rèn)將異常向后傳遞。
3.2.2 實(shí)現(xiàn)全局異常處理的 Handler
我們把業(yè)務(wù)異常處理邏輯放到最后的 Handler 中進(jìn)行處理,該 Handler 繼承了ChannelHandlerAdapter,只需要重寫異常處理的exceptionCaught
方法。
示例代碼如下:
public class ExceptionHandler extends ChannelHandlerAdapter { private Logger logger = LoggerFactory.getLogger(ExceptionHandler.class); @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause, Object in, Object out) throws Exception { logger.error("異常處理器中的異常處理邏輯"); Result re = (Result) out; re.setCode(500); re.setMsg("系統(tǒng)異常"); } }
3.2.3 將 ExceptionHandler 加入到執(zhí)行鏈中
直接通過 BootStrap 加入到執(zhí)行鏈最后即可,示例代碼如下:
public class ArticleModifyExample3 { private final static Logger logger = LoggerFactory.getLogger(ArticleModifyExample3.class); public static void main(String[] args) { //入?yún)? ArticleTitleModifyCmd dto = new ArticleTitleModifyCmd(); dto.setArticleId("articleId_001"); dto.setTitle("articleId_001_title"); dto.setContent("articleId_001_content"); //創(chuàng)建引導(dǎo)類 BootStrap bootStrap = new BootStrap(); Result result = (Result) bootStrap .inboundParameter(dto)//入?yún)? .outboundFactory(new ResultFactory())//出參工廠 .channel(new ArticleModifyChannel())//自定義channel .addChannelHandlerAtLast("checkParameter", new CheckParameterHandler())//第一個(gè)handler .addChannelHandlerAtLast("modifyTitle", new ArticleModifyTitleHandler())//第二個(gè)handler .addChannelHandlerAtLast("modifyContent", new ArticleModifyContentHandler())//第三個(gè)handler .addChannelHandlerAtLast("exception", new ExceptionHandler())//異常處理handler .process();//執(zhí)行 //result為執(zhí)行結(jié)果 logger.info("result:code={},msg={}", result.getCode(), result.getMsg()); } }
3.2.4 運(yùn)行 ArticleModifyExample3
運(yùn)行 ArticleModifyExample3 的 main 方法,控制臺(tái)輸出如下,可以看到異常被傳遞到最后的 ExceptionHandler 中進(jìn)行處理。
4. 總結(jié)
本文通過簡(jiǎn)單的例子,向讀者介紹了如何使用pie框架快速進(jìn)行責(zé)任鏈模式開發(fā),包括責(zé)任鏈初始化和異常處理等日常開發(fā)中常見的場(chǎng)景。讀者可以參考這些案例,并將pie框架應(yīng)用于實(shí)際開發(fā)中,以快速實(shí)現(xiàn)通用的責(zé)任鏈模式,最終降低代碼的耦合度、增加代碼的可擴(kuò)展性和提高代碼的可讀性。
審核編輯 黃宇
-
接口
+關(guān)注
關(guān)注
33文章
8577瀏覽量
151023 -
開源
+關(guān)注
關(guān)注
3文章
3320瀏覽量
42473 -
源碼
+關(guān)注
關(guān)注
8文章
639瀏覽量
29185
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論