著者: JD Logistics Qin Yujie
1.パイの紹介
Chain of Responsibility パターンは、開発プロセスで一般的に使用される設計パターンであり、SpringMVC や Netty などの多くのフレームワークに実装されています。日常の開発で責任連鎖モデルを使用したい場合、通常は自分で実装する必要がありますが、自分で一時的に実装した責任連鎖は普遍的ではなく、フレームワーク間の不明確な結合の問題を引き起こしやすいです。ビジネス コードは、コード レビューのコストを増加させます。
Netty のコードは常にその優雅さで知られています. 初期の頃に Netty のソース コードを読んでいたとき, 私はその責任の連鎖の実装をビジネス開発に適用するというアイデアを思いつきました. Netty での一連の責任の実装コードを形成するこのプロジェクトは、パイです。パイのコア コードは Netty から来ており、ほとんどの API は Netty と一致しています。
pie は、すぐに使用できる一連の責任フレームワークであり、開発者はビジネスに集中し、対応するビジネス ハンドラを開発するだけで、ビジネスの一連の責任の実装を完了できます。
学ぶのに 1 分、学ぶのに 3 分、適用するのに 5 分、スターへようこそ。
パイのソースアドレス: 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 ハンドラー インターフェイスを実装してビジネス ロジックを完成させる
パイ ケース プロジェクト ( https://github.com/feiniaojin/pie-example.git ) の例 1 では、パイの使用方法を示すために、仮想ビジネス ロジックが実装されています。記事、皆さん 変更操作を 2 つのハンドラーに入れることが合理的かどうかは気にしないでください。説明のケースとしてのみ使用されます。
3 つのハンドラの機能は次のとおりです。
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 メソッドを実行したログです。定義したハンドラーが 1 つずつ実行されていることがわかります。
3. 例外処理
3.1 ハンドラ例外処理
Handler の実行中に例外が発生した場合、その例外処理ロジックを現在の Handler の exceptionCaught メソッドに実装できます。
パイケースプロジェクト ( 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 のメイン メソッドを実行すると、出力は次のようになります。
3.2 グローバル例外処理
場合によっては、すべてのハンドラーで例外を処理するのではなく、実行チェーンの最後で均一に処理したいことがあります。
ArticleModifyExample3 では、グローバル例外による最終的な例外処理を示しました。その実装は、主に次の手順に分かれています。
3.2.1 例外を渡すビジネス ハンドラ
ビジネス ハンドラーが 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 は 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 に渡されることがわかります。