作者:京東物流 覃玉杰
1. pie 簡介
責任鏈模式是開發程序中常用的一種設計模式,在SpringMVC、Netty等許多框架中均有實作,我們日常的開發中如果要使用責任鏈模式,通常需要自己來實作,但自己臨時實作的責任鏈既不通用,也很容易產生框架與業務代碼耦合不清的問題,增加Code Review 的成本,
Netty的代碼向來以優雅著稱,早年我在閱讀Netty的原始碼時,萌生出將其責任鏈的實作應用到業務開發中的想法,之后花了點時間將Netty中責任鏈的實作代碼抽取出來,形成了本專案,也就是pie,pie的核心代碼均來自Netty,絕大部分的 API 與 Netty 是一致的,
pie 是一個可快速上手的責任鏈框架,開發者只需要專注業務,開發相應的業務Handler,即可完成業務的責任鏈落地,
一分鐘學會、三分鐘上手、五分鐘應用,歡迎 star,
pie 原始碼地址:https://github.com/feiniaojin/pie.git
pie 案例工程原始碼地址:https://github.com/feiniaojin/pie-example.git
2. 快速入門
2.1 引入 maven 依賴
pie 目前已打包發布到 maven 中央倉庫,開發者可以直接通過 maven 坐標將其引入到專案中,
<dependency>
<groupId>com.feiniaojin.ddd.ecosystem</groupId>
<artifactId>pie</artifactId>
<version>1.0</version>
</dependency>
目前最新的版本是 1.0
2.2 實作出參工廠
出參也就是執行結果,一般的執行程序都要求有執行結果回傳,實作 OutboundFactory 介面,用于產生介面默認回傳值,
例如:
public class OutFactoryImpl implements OutboundFactory {
@Override
public Object newInstance() {
Result result = new Result();
result.setCode(0);
result.setMsg("ok");
return result;
}
}
2.3 實作 handler 介面完成業務邏輯
在 pie 案例工程( https://github.com/feiniaojin/pie-example.git )的 Example1 中,為了展示 pie 的使用方法,實作了一個虛擬的業務邏輯:CMS類專案修改文章標題、正文,大家不要關注修改操作放到兩個 handler 中是否合理,僅作為講解案例,
三個 Handler 功能如下:
CheckParameterHandler:用于引數校驗,
ArticleModifyTitleHandler:用于修改文章的標題,
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("引數校驗:開始執行");
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("引數校驗:校驗通過,即將進入下一個Handler");
ctx.fireChannelProcess(in, out);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause,
Object in,
Object out) throws Exception {
logger.error("引數校驗:例外處理邏輯", cause);
Result re = (Result) out;
re.setCode(400);
re.setMsg("引數例外");
}
}
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("修改標題:進入修改標題的Handler");
ArticleTitleModifyCmd cmd = (ArticleTitleModifyCmd) in;
String title = cmd.getTitle();
//修改標題的業務邏輯
logger.info("修改標題:title={}", title);
logger.info("修改標題:執行完成,即將進入下一個Handler");
ctx.fireChannelProcess(in, out);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause,
Object in,
Object out) throws Exception {
logger.error("修改標題:例外處理邏輯");
Result re = (Result) out;
re.setCode(1501);
re.setMsg("修改標題發生例外");
}
}
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("修改正文:進入修改正文的Handler");
ArticleTitleModifyCmd cmd = (ArticleTitleModifyCmd) in;
logger.info("修改正文,content={}", cmd.getContent());
logger.info("修改正文:執行完成,即將進入下一個Handler");
ctx.fireChannelProcess(in, out);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause,
Object in,
Object out) throws Exception {
logger.error("修改標題:例外處理邏輯");
Result re = (Result) out;
re.setCode(1502);
re.setMsg("修改正文發生例外");
}
}
2.4 通過 BootStrap 拼裝并執行
public class ArticleModifyExample1 {
private final static Logger logger = LoggerFactory.getLogger(ArticleModifyExample1.class);
public static void main(String[] args) {
//構造入參
ArticleTitleModifyCmd dto = new ArticleTitleModifyCmd();
dto.setArticleId("articleId_001");
dto.setTitle("articleId_001_title");
dto.setContent("articleId_001_content");
//創建引導類
BootStrap bootStrap = new BootStrap();
//拼裝并執行
Result result = (Result) bootStrap
.inboundParameter(dto)//入參
.outboundFactory(new ResultFactory())//出參工廠
.channel(new ArticleModifyChannel())//自定義channel
.addChannelHandlerAtLast("checkParameter", new CheckParameterHandler())//第一個handler
.addChannelHandlerAtLast("modifyTitle", new ArticleModifyTitleHandler())//第二個handler
.addChannelHandlerAtLast("modifyContent", new ArticleModifyContentHandler())//第三個handler
.process();//執行
//result為執行結果
logger.info("result:code={},msg={}", result.getCode(), result.getMsg());
}
}
2.5 執行結果
以下是運行 ArticleModifyExample1 的 main 方法打出的日志,可以看到我們定義的 handler 被逐個執行了,

3. 例外處理
3.1 Handler 例外處理
當某個Handler執行發生例外時,我們可將其例外處理邏輯實作在當前 Handler 的 exceptionCaught 方法中,
在 pie 案例工程( https://github.com/feiniaojin/pie-example.git )的 example2 包中,展示了某個 Handler 拋出例外時的處理方式,
假設 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("修改標題:進入修改標題的Handler");
ArticleTitleModifyCmd cmd = (ArticleTitleModifyCmd) in;
String title = cmd.getTitle();
//此處的例外用于模擬執行程序中出現例外的場景
throw new RuntimeException("修改title發生例外");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause,
Object in,
Object out) throws Exception {
logger.error("修改標題:例外處理邏輯");
Result re = (Result) out;
re.setCode(1501);
re.setMsg("修改標題發生例外");
}
}
此時 ArticleModifyTitleHandler 的 channelProcess 方法一定會拋出例外, 在當前 Handler 的 exceptionCaught 方法中對例外進行了處理,
運行 ArticleModifyExample2 的 main 方法,輸出如下:

3.2 全域例外處理
有時候,我們不想每個 handler 都處理一遍例外,我們希望在執行鏈的最后統一進行處理,
在 ArticleModifyExample3 中,我們展示了通過一個全域例外進行最后的例外處理,其實作主要分為以下幾步:
3.2.1 業務 Handler 傳遞例外
如果業務 Handler 實作了 ChannelHandler 介面,那么需要手工呼叫 ctx.fireExceptionCaught 方法向下傳遞例外,
例如 CheckParameterHandler 捕獲到例外時的示例如下:
@Override
public class XXXHandler implements ChannelHandler {
//省略其他邏輯
//例外處理
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause,
Object in,
Object out) throws Exception {
logger.info("引數校驗的例外處理邏輯:不處理直接向后傳遞");
ctx.fireExceptionCaught(cause, in, out);
}
}
如果業務 Handler 繼承了 ChannelHandlerAdapter,如果沒有重寫 fireExceptionCaught 方法,則默認將例外向后傳遞,
3.2.2 實作全域例外處理的 Handler
我們把業務例外處理邏輯放到最后的 Handler 中進行處理,該 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("系統例外");
}
}
3.2.3 將 ExceptionHandler 加入到執行鏈中
直接通過 BootStrap 加入到執行鏈最后即可,示例代碼如下:
public class ArticleModifyExample3 {
private final static Logger logger = LoggerFactory.getLogger(ArticleModifyExample3.class);
public static void main(String[] args) {
//入參
ArticleTitleModifyCmd dto = new ArticleTitleModifyCmd();
dto.setArticleId("articleId_001");
dto.setTitle("articleId_001_title");
dto.setContent("articleId_001_content");
//創建引導類
BootStrap bootStrap = new BootStrap();
Result result = (Result) bootStrap
.inboundParameter(dto)//入參
.outboundFactory(new ResultFactory())//出參工廠
.channel(new ArticleModifyChannel())//自定義channel
.addChannelHandlerAtLast("checkParameter", new CheckParameterHandler())//第一個handler
.addChannelHandlerAtLast("modifyTitle", new ArticleModifyTitleHandler())//第二個handler
.addChannelHandlerAtLast("modifyContent", new ArticleModifyContentHandler())//第三個handler
.addChannelHandlerAtLast("exception", new ExceptionHandler())//例外處理handler
.process();//執行
//result為執行結果
logger.info("result:code={},msg={}", result.getCode(), result.getMsg());
}
}
3.2.4 運行 ArticleModifyExample3
運行 ArticleModifyExample3 的 main 方法,控制臺輸出如下,可以看到例外被傳遞到最后的 ExceptionHandler 中進行處理,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/551918.html
標籤:其他
下一篇:返回列表
