Learn in one minute, get started in three minutes, and apply in five minutes. Get started quickly with a detailed explanation of the chain of responsibility framework | JD Cloud technical team

Author: JD Logistics Qin Yujie

1. Introduction to pie

The Chain of Responsibility pattern is a commonly used design pattern in the development process, and it is implemented in many frameworks such as SpringMVC and Netty. If we want to use the chain of responsibility model in our daily development, we usually need to implement it ourselves, but the chain of responsibility implemented temporarily by ourselves is not universal, and it is easy to cause the problem of unclear coupling between the framework and the business code, which increases the cost of Code Review.

Netty's code has always been known for its elegance. When I was reading Netty's source code in the early years, I came up with the idea of ​​applying its chain of responsibility implementation to business development. After that, I spent some time extracting the implementation code of the chain of responsibility in Netty to form This project, which is pie. The core code of pie comes from Netty, and most of the APIs are consistent with Netty.

pie is a chain of responsibility framework that can be used quickly. Developers only need to focus on the business and develop the corresponding business Handler to complete the implementation of the chain of responsibility of the business.

One minute to learn, three minutes to learn, five minutes to apply, welcome to star.

pie source address: https://github.com/feiniaojin/pie.git

Pie example project source code address: https://github.com/feiniaojin/pie-example.git

2. Quick Start

2.1 Introducing maven dependencies

pie has been packaged and released to the maven central warehouse, and developers can directly introduce it into the project through maven coordinates.

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

The latest version is 1.0

2.2 Realize the ginseng factory

The output parameter is the execution result, and the general execution process requires the execution result to be returned. Implement the OutboundFactory interface, which is used to generate the default return value of the interface.

For example:

public class OutFactoryImpl implements OutboundFactory {
    @Override
    public Object newInstance() {
        Result result = new Result();
        result.setCode(0);
        result.setMsg("ok");
        return result;
    }
}

2.3 Implement the handler interface to complete the business logic

In Example 1 of the pie case project ( https://github.com/feiniaojin/pie-example.git ), in order to demonstrate how to use pie, a virtual business logic is implemented: CMS projects modify the title and text of articles, everyone Don't pay attention to whether it is reasonable to put the modification operation in the two handlers, it is only used as an explanation case.

The functions of the three Handlers are as follows:

CheckParameterHandler: used for parameter verification.

ArticleModifyTitleHandler: used to modify the title of the article.

ArticleModifyContentHandler: used to modify the body of the article.

The code of CheckParameterHandler is as follows:

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("参数异常");
    }
}

The code for ArticleModifyTitleHandler is as follows:

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("修改标题发生异常");
    }
}

The code of ArticleModifyContentHandler is as follows:

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 Assemble and execute through 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 Execution Results

The following is the log of running the main method of ArticleModifyExample1, we can see that the handlers we defined are executed one by one.

3. Exception handling

3.1 Handler exception handling

When an exception occurs in the execution of a Handler, we can implement its exception handling logic in the exceptionCaught method of the current Handler.

In the example2 package of the pie case project ( https://github.com/feiniaojin/pie-example.git ), it shows how to handle when a Handler throws an exception.

Assuming that the business logic of ArticleModifyTitleHandler will throw an exception, the example code is as follows:

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("修改标题发生异常");
    }
}

At this time, the channelProcess method of ArticleModifyTitleHandler will definitely throw an exception, and the exception is handled in the exceptionCaught method of the current Handler.

Run the main method of ArticleModifyExample2, the output is as follows:

3.2 Global exception handling

Sometimes, we don't want every handler to handle the exception, we want to handle it uniformly at the end of the execution chain.
In ArticleModifyExample3, we demonstrated the final exception handling through a global exception, and its implementation is mainly divided into the following steps:

3.2.1 Business Handler passing exception

If the business Handler implements the ChannelHandler interface, you need to manually call the ctx.fireExceptionCaught method to pass the exception down.
For example, the example when CheckParameterHandler catches an exception is as follows:


@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);
    }
}

If the business Handler inherits ChannelHandlerAdapter, if the fireExceptionCaught method is not rewritten, the exception will be passed backward by default.

3.2.2 Handler for global exception handling

We put the business exception handling logic in the final Handler for processing. The Handler inherits the ChannelHandlerAdapter, and only needs to rewrite the exceptionCaught
method of exception handling.
The sample code is as follows:

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 Add ExceptionHandler to the execution chain

Just add it to the end of the execution chain directly through Bootstrap. The sample code is as follows:


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 Running ArticleModifyExample3

Run the main method of ArticleModifyExample3, the console output is as follows, you can see that the exception is passed to the final ExceptionHandler for processing.

{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4090830/blog/8707474