一分钟学会、三分钟上手、五分钟应用,快速上手责任链框架详解 | 京东云技术团队

作者:京东物流 覃玉杰

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 坐标将其引入到项目中。

1<dependency> 2 <groupId>com.feiniaojin.ddd.ecosystem</groupId> 3 <artifactId>pie</artifactId> 4 <version>1.0</version> 5</dependency> 6 7

目前最新的版本是 1.0

2.2 实现出参工厂

出参也就是执行结果,一般的执行过程都要求有执行结果返回。实现 OutboundFactory 接口,用于产生接口默认返回值。

例如:

1public class OutFactoryImpl implements OutboundFactory { 2 @Override 3 public Object newInstance() { 4 Result result = new Result(); 5 result.setCode(0); 6 result.setMsg("ok"); 7 return result; 8 } 9} 10 11

2.3 实现 handler 接口完成业务逻辑

在 pie 案例工程( https://github.com/feiniaojin/pie-example.git )的 Example1 中,为了展示 pie 的使用方法,实现了一个虚拟的业务逻辑:CMS类项目修改文章标题、正文,大家不要关注修改操作放到两个 handler 中是否合理,仅作为讲解案例。

三个 Handler 功能如下:

CheckParameterHandler:用于参数校验。

ArticleModifyTitleHandler:用于修改文章的标题。

ArticleModifyContentHandler:用于修改文章的正文。

CheckParameterHandler 的代码如下:

1public class CheckParameterHandler implements ChannelHandler { 2 3 private Logger logger = LoggerFactory.getLogger(CheckParameterHandler.class); 4 5 @Override 6 public void channelProcess(ChannelHandlerContext ctx, 7 Object in, 8 Object out) throws Exception { 9 10 logger.info("参数校验:开始执行"); 11 12 if (in instanceof ArticleTitleModifyCmd) { 13 ArticleTitleModifyCmd cmd = (ArticleTitleModifyCmd) in; 14 String articleId = cmd.getArticleId(); 15 Objects.requireNonNull(articleId, "articleId不能为空"); 16 String title = cmd.getTitle(); 17 Objects.requireNonNull(title, "title不能为空"); 18 String content = cmd.getContent(); 19 Objects.requireNonNull(content, "content不能为空"); 20 } 21 logger.info("参数校验:校验通过,即将进入下一个Handler"); 22 ctx.fireChannelProcess(in, out); 23 } 24 25 @Override 26 public void exceptionCaught(ChannelHandlerContext ctx, 27 Throwable cause, 28 Object in, 29 Object out) throws Exception { 30 logger.error("参数校验:异常处理逻辑", cause); 31 Result re = (Result) out; 32 re.setCode(400); 33 re.setMsg("参数异常"); 34 } 35} 36 37

ArticleModifyTitleHandler 的代码如下:

1public class ArticleModifyTitleHandler implements ChannelHandler { 2 3 private Logger logger = LoggerFactory.getLogger(ArticleModifyTitleHandler.class); 4 5 @Override 6 public void channelProcess(ChannelHandlerContext ctx, 7 Object in, 8 Object out) throws Exception { 9 10 logger.info("修改标题:进入修改标题的Handler"); 11 12 ArticleTitleModifyCmd cmd = (ArticleTitleModifyCmd) in; 13 14 String title = cmd.getTitle(); 15 //修改标题的业务逻辑 16 logger.info("修改标题:title={}", title); 17 18 logger.info("修改标题:执行完成,即将进入下一个Handler"); 19 ctx.fireChannelProcess(in, out); 20 } 21 22 @Override 23 public void exceptionCaught(ChannelHandlerContext ctx, 24 Throwable cause, 25 Object in, 26 Object out) throws Exception { 27 logger.error("修改标题:异常处理逻辑"); 28 Result re = (Result) out; 29 re.setCode(1501); 30 re.setMsg("修改标题发生异常"); 31 } 32} 33 34

ArticleModifyContentHandler 的代码如下:

1public class ArticleModifyContentHandler implements ChannelHandler { 2 3 private Logger logger = LoggerFactory.getLogger(ArticleModifyContentHandler.class); 4 5 @Override 6 public void channelProcess(ChannelHandlerContext ctx, 7 Object in, 8 Object out) throws Exception { 9 10 logger.info("修改正文:进入修改正文的Handler"); 11 ArticleTitleModifyCmd cmd = (ArticleTitleModifyCmd) in; 12 logger.info("修改正文,content={}", cmd.getContent()); 13 logger.info("修改正文:执行完成,即将进入下一个Handler"); 14 ctx.fireChannelProcess(in, out); 15 } 16 17 @Override 18 public void exceptionCaught(ChannelHandlerContext ctx, 19 Throwable cause, 20 Object in, 21 Object out) throws Exception { 22 23 logger.error("修改标题:异常处理逻辑"); 24 25 Result re = (Result) out; 26 re.setCode(1502); 27 re.setMsg("修改正文发生异常"); 28 } 29} 30 31

2.4 通过 BootStrap 拼装并执行

1public class ArticleModifyExample1 { 2 3 private final static Logger logger = LoggerFactory.getLogger(ArticleModifyExample1.class); 4 5 public static void main(String[] args) { 6 //构造入参 7 ArticleTitleModifyCmd dto = new ArticleTitleModifyCmd(); 8 dto.setArticleId("articleId_001"); 9 dto.setTitle("articleId_001_title"); 10 dto.setContent("articleId_001_content"); 11 12 //创建引导类 13 BootStrap bootStrap = new BootStrap(); 14 15 //拼装并执行 16 Result result = (Result) bootStrap 17 .inboundParameter(dto)//入参 18 .outboundFactory(new ResultFactory())//出参工厂 19 .channel(new ArticleModifyChannel())//自定义channel 20 .addChannelHandlerAtLast("checkParameter", new CheckParameterHandler())//第一个handler 21 .addChannelHandlerAtLast("modifyTitle", new ArticleModifyTitleHandler())//第二个handler 22 .addChannelHandlerAtLast("modifyContent", new ArticleModifyContentHandler())//第三个handler 23 .process();//执行 24 //result为执行结果 25 logger.info("result:code={},msg={}", result.getCode(), result.getMsg()); 26 } 27} 28 29

2.5 执行结果

以下是运行 ArticleModifyExample1 的 main 方法打出的日志,可以看到我们定义的 handler 被逐个执行了。

3. 异常处理

3.1 Handler 异常处理

当某个Handler执行发生异常时,我们可将其异常处理逻辑实现在当前 Handler 的 exceptionCaught 方法中。

在 pie 案例工程( https://github.com/feiniaojin/pie-example.git )的 example2 包中,展示了某个 Handler 抛出异常时的处理方式。

假设 ArticleModifyTitleHandler 的业务逻辑会抛出异常,实例代码如下:

1public class ArticleModifyTitleHandler implements ChannelHandler { 2 3 private Logger logger = LoggerFactory.getLogger(ArticleModifyTitleHandler.class); 4 5 @Override 6 public void channelProcess(ChannelHandlerContext ctx, 7 Object in, 8 Object out) throws Exception { 9 10 logger.info("修改标题:进入修改标题的Handler"); 11 ArticleTitleModifyCmd cmd = (ArticleTitleModifyCmd) in; 12 String title = cmd.getTitle(); 13 //此处的异常用于模拟执行过程中出现异常的场景 14 throw new RuntimeException("修改title发生异常"); 15 } 16 17 @Override 18 public void exceptionCaught(ChannelHandlerContext ctx, 19 Throwable cause, 20 Object in, 21 Object out) throws Exception { 22 logger.error("修改标题:异常处理逻辑"); 23 Result re = (Result) out; 24 re.setCode(1501); 25 re.setMsg("修改标题发生异常"); 26 } 27} 28 29

此时 ArticleModifyTitleHandler 的 channelProcess 方法一定会抛出异常, 在当前 Handler 的 exceptionCaught 方法中对异常进行了处理。

运行 ArticleModifyExample2 的 main 方法,输出如下:

3.2 全局异常处理

有时候,我们不想每个 handler 都处理一遍异常,我们希望在执行链的最后统一进行处理。
在 ArticleModifyExample3 中,我们展示了通过一个全局异常进行最后的异常处理,其实现主要分为以下几步:

3.2.1 业务 Handler 传递异常

如果业务 Handler 实现了 ChannelHandler 接口,那么需要手工调用 ctx.fireExceptionCaught 方法向下传递异常。
例如 CheckParameterHandler 捕获到异常时的示例如下:

1 2@Override 3public class XXXHandler implements ChannelHandler { 4 5 //省略其他逻辑 6 7 //异常处理 8 public void exceptionCaught(ChannelHandlerContext ctx, 9 Throwable cause, 10 Object in, 11 Object out) throws Exception { 12 13 logger.info("参数校验的异常处理逻辑:不处理直接向后传递"); 14 ctx.fireExceptionCaught(cause, in, out); 15 } 16} 17 18

如果业务 Handler 继承了 ChannelHandlerAdapter,如果没有重写 fireExceptionCaught 方法,则默认将异常向后传递。

3.2.2 实现全局异常处理的 Handler

我们把业务异常处理逻辑放到最后的 Handler 中进行处理,该 Handler 继承了ChannelHandlerAdapter,只需要重写异常处理的exceptionCaught
方法。
示例代码如下:

1public class ExceptionHandler extends ChannelHandlerAdapter { 2 3 private Logger logger = LoggerFactory.getLogger(ExceptionHandler.class); 4 5 @Override 6 public void exceptionCaught(ChannelHandlerContext ctx, 7 Throwable cause, 8 Object in, 9 Object out) throws Exception { 10 11 logger.error("异常处理器中的异常处理逻辑"); 12 Result re = (Result) out; 13 re.setCode(500); 14 re.setMsg("系统异常"); 15 } 16} 17 18

3.2.3 将 ExceptionHandler 加入到执行链中

直接通过 BootStrap 加入到执行链最后即可,示例代码如下:

1 2public class ArticleModifyExample3 { 3 4 private final static Logger logger = LoggerFactory.getLogger(ArticleModifyExample3.class); 5 6 public static void main(String[] args) { 7 //入参 8 ArticleTitleModifyCmd dto = new ArticleTitleModifyCmd(); 9 dto.setArticleId("articleId_001"); 10 dto.setTitle("articleId_001_title"); 11 dto.setContent("articleId_001_content"); 12 //创建引导类 13 BootStrap bootStrap = new BootStrap(); 14 15 Result result = (Result) bootStrap 16 .inboundParameter(dto)//入参 17 .outboundFactory(new ResultFactory())//出参工厂 18 .channel(new ArticleModifyChannel())//自定义channel 19 .addChannelHandlerAtLast("checkParameter", new CheckParameterHandler())//第一个handler 20 .addChannelHandlerAtLast("modifyTitle", new ArticleModifyTitleHandler())//第二个handler 21 .addChannelHandlerAtLast("modifyContent", new ArticleModifyContentHandler())//第三个handler 22 .addChannelHandlerAtLast("exception", new ExceptionHandler())//异常处理handler 23 .process();//执行 24 //result为执行结果 25 logger.info("result:code={},msg={}", result.getCode(), result.getMsg()); 26 } 27} 28 29

3.2.4 运行 ArticleModifyExample3

运行 ArticleModifyExample3 的 main 方法,控制台输出如下,可以看到异常被传递到最后的 ExceptionHandler 中进行处理。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

PPDB:今晚老齐直播

【今晚老齐直播】今晚(本周三晚)20:0021:00小白开始“用”飞桨(https://www.oschina.net/action/visit/ad?id1185)由PPDE(飞桨(https://www.oschina.net/action/visit/ad?id1185)开发者专家计划)成员老齐,为深度学习小白指点迷津。

FLV文件格式

1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  

一分钟学会、三分钟上手、五分钟应用,快速上手责任链框架详解 | 京东云技术团队 - HelloWorld