Skip to content

feiniaojin/pie

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

p9MwvM8.png

GitHub license GitHub stars GitHub forks GitHub issues Maven Central

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

vK8vJs.png

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中是否合理。

三个Hadnler功能如下:

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被逐个执行了。

vKSLpq.png

3. 异常处理

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

我们可将其异常处理逻辑实现在当前Handler的exceptionCaught方法中。

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

vKGnQx.png

4. 全局异常处理

有时候,我们不想每个handler都处理一遍异常,我们希望在执行链的最后统一进行处理。

ArticleModifyExample3中,我们展示了通过一个全局异常进行最后的异常处理,其实现主要分为以下几步:

4.1 业务Handler传递异常

如果实现了Handler接口,那么需要手工调用 ctx.fireExceptionCaught方法向下传递异常。

例如CheckParameterHandler捕获到异常时的示例如下:

@Override
public void exceptionCaught(ChannelHandlerContext ctx,
        Throwable cause,
        Object in,
        Object out) throws Exception {
    
    logger.info("参数校验的异常处理逻辑:不处理直接向后传递");
    ctx.fireExceptionCaught(cause, in, out);
}

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

4.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("系统异常");
    }
}

4.3 将ExceptionHandler加入到执行链中

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

//创建引导类
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());

4.4 运行ArticleModifyExample3

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

vKGCLT.png


使用过程中如遇到问题,可以通过公众号或者作者微信联系作者。

公众号: MarkWord,欢迎关注

pi1rmB6.jpg
微信号:
pi1rmB6.jpg

About

pie是一个可快速上手的责任链框架

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages