写点什么

还在自己实现责任链?我建议你造轮子之前先看看这个开源项目

  • 2024-09-20
    北京
  • 本文字数:5721 字

    阅读完需:约 19 分钟

  1. 前言


设计模式在软件开发中被广泛使用。通过使用设计模式,开发人员可以更加高效地开发出高质量的软件系统,提高代码的可读性、可维护性和可扩展性。


责任链模式是一种常用的行为型设计模式,它将请求沿着处理链进行发送,直到其中一个处理者对请求进行处理为止。在责任链模式中,通常会有多个处理者,每个处理者都有一个处理请求的方法。当一个请求到达处理链的起点时,会依次传递给每个处理者进行处理,直到某个处理者能够处理该请求。这样可以保证每个请求都能被处理,并且可以根据实际情况动态地添加或删除处理者,以满足不同的需求。


责任链模式可以帮助降低系统的耦合度,增加系统的灵活性和可扩展性,其在 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.git2. 快速入门 2.1 引入 maven 依赖


pie 目前已打包发布到 maven 中央仓库,开发者可以直接通过 maven 坐标将其引入到项目中。


<dependency><groupId>com.feiniaojin.ddd.ecosystem</groupId><artifactId>pie</artifactId><version>1.0</version></dependency>


目前最新的版本是 1.02.2 实现出参工厂


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


例如:


public class OutFactoryImpl implements OutboundFactory {@Overridepublic 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);
@Overridepublic 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);}
@Overridepublic 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);
@Overridepublic 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);}
@Overridepublic 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);
@Overridepublic 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);}
@Overridepublic 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);
@Overridepublic 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发生异常");}
@Overridepublic 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 捕获到异常时的示例如下:


@Overridepublic 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);
@Overridepublic 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 中进行处理。4. 总结


本文通过简单的例子,向读者介绍了如何使用 pie 框架快速进行责任链模式开发,包括责任链初始化和异常处理等日常开发中常见的场景。读者可以参考这些案例,并将 pie 框架应用于实际开发中,以快速实现通用的责任链模式,最终降低代码的耦合度、增加代码的可扩展性和提高代码的可读性。

发布于: 2 小时前阅读数: 6
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
还在自己实现责任链?我建议你造轮子之前先看看这个开源项目_京东科技开发者_InfoQ写作社区