关于netty:一分钟学会三分钟上手五分钟应用快速上手开源责任链框架详解-京东云技术团队

作者:京东物流 覃玉杰

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 中进行解决。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理