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


来源:京东技术

导读

设计模式在软件开发中被广泛使用。通过使用设计模式,开发人员可以更加高效地开发出高质量的软件系统,提高代码的可读性、可维护性和可扩展性。而责任链模式是一种常用的设计模式,在 SpringMVC、Netty 等许多框架中均有实现。pie 源于 Netty 中责任链的实现代码,绝大部分的 API 与 Netty 是一致的。pie 是一个可快速上手的责任链框架,开发者只需要专注业务,开发相应的业务 Handler,即可完成业务的责任链落地。本文介绍了如何使用 pie 框架进行责任链模式开发。




01 
前言


在今年的敏捷团队建设中,我通过Suite执行器实现了一键自动化单元测试。Juint除了Suite执行器还有哪些执行器呢?由此我的Runner探索之旅开始了!

设计模式在软件开发中被广泛使用。通过使用设计模式,开发人员可以更加高效地开发出高质量的软件系统,提高代码的可读性、可维护性和可扩展性。而责任链模式是一种常用的设计模式,在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



02 
  快速入门  


理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。     

2.1  引入 maven 依赖


    

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






    com.feiniaojin.ddd.ecosystem    pie    1.0

目前最新的版本是 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 被逐个执行了。

图1.运行 ArticleModifyExample1 的 main 方法打出的日志


03 
  

异常处理

  


理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。

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 方法,输出如下:

图2.运行 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);
   @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 中进行处理。
图3.运行 ArticleModifyExample3 的 main 方法控制台输出示意


04 
    


理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。

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

请使用浏览器的分享功能分享到微信等