1はじめに
どのシステムでも、すべての場所で例外をばかげてキャッチして処理することはありません。システム全体で、通常は1か所で例外を処理します。Springbootのグローバル例外処理は非常に簡単です。
紹介の前に、少し話を外します。現在の開発システムは、フロントとバックから完全に分離されています。バックエンドはRESTfull APIのみを提供します。インターフェイスを含めることは禁止されています。ThymeleafおよびJSPバックエンドテンプレートは、絶対に禁止されています。 。ゴミ箱を捨てて、研究に多くの若者を無駄にしないでください。それは堕落です。フロントエンドは、インターフェイス関連の一般的に使用されるVueを担当します。会社がフロントエンドとバックエンドを分離していない場合は、そして、thymeleafで一緒に書いてください。そうすれば、早めに転職を計画する必要があります。、彼らはあなたをサポートすることはできません。ましてやあなたの家族をサポートすることもできません。
フロントエンドとバックエンドの分離、バックエンドAPIは、通常、例外処理のために2つのことを行う必要があります。
1.ログと、内部の対応する通知処理を記録します。
2.外部のAPI呼び出し元に結果を返すことです。
API呼び出し元の場合、返される結果(エラーコードとプロンプト情報を含む)は1つだけで、他の結果は気にしません。
バックエンドの場合、関連事項を処理するために、ログを記録し、対応するメッセージを他のキューに通知または公開するだけで済みます。
だから:私は多くの人が多くのカスタム例外クラスをカプセル化するのを見てきました。実際、それは完全に不要です。すべての例外を処理するために必要な例外ハンドラーは1つだけで、エラー識別コードとプロンプトメッセージの列挙がカプセル化されてReturn to API呼び出し元。この場合、バックエンド処理を1つの例外処理メソッドで直接処理できます。N個の複数のカスタム例外をカプセル化する必要はありません。これには意味がありません。
異常を考える
一部の例外は私たち自身の主導でスローされますが、すべての例外はシステムの異常な動作であり、欠陥であり、すべてBUGに属していることを理解する必要があります。
私たちがしなければならないのは、システムを改善するために完全な例外処理を探すのではなく、システムの可用性を最大化し、可能な限り例外を回避することです。
例外処理は、異常が避けられない場合に講じられる緊急措置です。主な目的は、外部への親しみやすさを高め、内部で改善の手がかりを提供することです。
完全な例外処理がシステムの中核であるとは思わないでください。完全な例外処理を期待しないでください。例外処理がシステムの欠陥の根底を一掃することを期待しないでください。
システム例外が多すぎる場合は、例外処理メカニズムを改善するのではなく、システムアーキテクチャの設計が妥当かどうか、およびシステムロジックの設計が妥当かどうかを検討する必要があります。
2.グローバル例外処理の最初のメソッド(@ControllerAdviceおよび@ExceptionHandler)
=================================================
開発中、次のシナリオが発生します。特定のインターフェイスで、いくつかのビジネス例外があります。たとえば、ユーザーが入力したパラメーターの検証に失敗し、ユーザー名とパスワードが存在しません。これらのビジネス例外がトリガーされたら、これらのカスタムビジネス例外をスローして処理する必要があります。通常、例外情報のステータスコードと例外の説明を発信者にわかりやすく返す必要があり、発信者はステータスコードとその他の情報を使用して、例外の特定の状況を判断します。
以前は、コントローラーレイヤーでtry / catchを処理する必要がある場合がありました。最初にカスタム例外をキャッチし、次に他の例外をキャッチします。さまざまな例外について、キャッチ中に返されるオブジェクトをカプセル化する必要があります。ただし、これの欠点は、コードが冗長になることです。次のコードに示すように、各インターフェイスはtry / catch処理である必要があり、調整が必要になると、すべてのインターフェイスを再度変更する必要があります。これは、コードのメンテナンスには非常に不利です。
@RequestMapping (value = "/test")
public ResponseEntity test() {
ResponseEntity re = new ResponseEntity();
// 业务处理
// ...
try {
// 业务
} catch (BusinessException e) {
logger.info("业务发生异常,code:" + e.getCode() + "msg:" + e.getMsg());
re.setCode(e.getCode());
re.setMsg(e.getMsg());
return re;
} catch (Exception e) {
logger.error("服务错误:", e);
re.setCode("xxxxx");
re.setMsg("服务错误");
return re;
}
return re;
}
では、これらの異常な情報を簡単に処理する方法はありますか?答えはイエスです。Spring 3.2では、@ ControllerAdviceアノテーションが追加されています。これを使用して、@ ExceptionHandler、@ InitBinder、@ ModelAttributeを定義し、すべての@RequestMappingに適用できます。簡単に言うと、@ ControllerAdviceアノテーションを使用してグローバル例外処理クラスを構成し、コントローラーレイヤーで例外を均一に処理できます。同時に、コントローラーにtry / catchを記述する必要がないため、コードがクリーンで簡単になります。維持する。
指示
カスタム例外を定義する
カスタム例外に関連するナレッジポイントについては、ここでは詳しく説明していません。わからない場合は、自分で検索してください。単純なカスタムビジネス例外クラスをここに貼り付けます。
/**
* 自定义业务异常类
*
* @author Yuzhe Ma
* @date 2018/11/28
*/
@Data
public class BusinessException extends RuntimeException {
private String code;
private String msg;
public BusinessException(String code, String msg) {
this.code = code;
this.msg = msg;
}
}
注:@DataはLombokプラグインです。set / getメソッドを自動的に生成します。具体的な使用方法はここでは紹介しません。
@ControllerAdvice + @ ExceptionHand`グローバル例外処理クラスを構成します
/**
* 全局异常处理器
*
* @author Yuzhe Ma
* @date 2018/11/12
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 处理 Exception 异常
*
* @param httpServletRequest httpServletRequest
* @param e 异常
* @return
*/
@ResponseBody
@ExceptionHandler(value = Exception.class)
public ResponseEntity exceptionHandler(HttpServletRequest httpServletRequest, Exception e) {
logger.error("服务错误:", e);
return new ResponseEntity("xxx", "服务出错");
}
/**
* 处理 BusinessException 异常
*
* @param httpServletRequest httpServletRequest
* @param e 异常
* @return
*/
@ResponseBody
@ExceptionHandler(value = BusinessException.class)
public ResponseEntity businessExceptionHandler(HttpServletRequest httpServletRequest, BusinessException e) {
logger.info("业务异常。code:" + e.getCode() + "msg:" + e.getMsg());
return new ResponseEntity(e.getCode(), e.getMsg());
}
}
@ControllerAdvice
このクラスをグローバル例外処理クラスとして定義します。
@ExceptionHandler
このメソッドを例外処理メソッドとして定義します。valueの値は、処理される例外クラスのクラスファイルです。この例では、メソッドは2つのパラメーターを渡します。1つは対応するException例外クラスで、もう1つはHttpServletRequestクラスです。もちろん、これら2つのパラメーターに加えて、他のいくつかのパラメーターもサポートされています。詳細については、ドキュメントhttps://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/ExceptionHandler.htmlを参照してください。
このようにして、さまざまな例外を均一に処理できます。通常、コントローラーがtry / catchを使用しないようにするために、GlobalExceptionHandlerで例外の統一された処理を行うこともできます。このようにして、@ ExceptionHandlerで構成されていない他の例外は均一に処理されます。
例外が発生したときに例外をスローする
ビジネス例外が発生するビジネスでは、throwを使用して対応するビジネス例外を直接スローします。例えば
throw new BusinessException("3000", "账户密码错误");
コントローラーでの書き方
Controllerでは、特別な目的を除いて、try / catchを記述する必要はありません。
@RequestMapping(value = "/test")
public ResponseEntity test() {
ResponseEntity re = new ResponseEntity();
// 业务处理
// ...
return re;
}
結果表示
例外がスローされた後、次の結果が返されます。
{
"code": "3000",
"msg": "账户密码错误",
"data": null
}
注意
- GlobalExceptionHandlerによって処理されるようにコントローラー層自体で例外をスローする必要はありません。例外が最終的にコントローラー層からスローされる限り、グローバル例外ハンドラーによって処理できます。
- 非同期メソッドの例外は、グローバル例外によって処理されません。
- スローされた例外がコード内のtry / catchによってキャッチされた場合、GlobalExceptionHandlerによって処理されません。
総括する
この記事では、SpringBootでグローバル例外ハンドラーを構成することにより、コントローラー層によって発生した例外を処理する方法を紹介します。
利点
コードの冗長性を減らし、コードの保守が容易
不利益
コントローラレイヤーによってスローされた例外のみを処理できます。たとえば、インターセプター(インターセプター)レイヤーの例外、タイミングタスクの例外、非同期メソッドの例外は処理されません。
上記は、@ ControllerAdvice + @ExceptionHandを使用して、SpringBootのコントローラーレイヤーでグローバル例外をキャッチして処理するメソッドを実装することです。
3.グローバル例外処理(AOP)の2番目の方法
@ControllerAdviceアノテーションは通常、グローバル例外処理のために@ExceptionHandlerアノテーションとともに使用されます。
ただし、このメソッドには、ツールクラスや他のクラスの例外など、コントロールレイヤーの例外のみをインターセプトし、インターセプトしないという欠点があります。
プログラムの実行中にエラーが発生しないことを保証できないため、コードを記述するためにtry-catchを追加する必要がありますが、try-catchを頻繁に追加すると、コード構造が混乱を招きます。そのため、最適化が必要です。
原則:問題がある場合、例外がチェックされ、実行時例外に変換されます。
コア原則:エージェントの動的思考-------> AOP操作
傍受は、カスタムAOPを使用して実現できます。
いくつかの重要なポイントがあります
- エントリポイントを最大のプロジェクトパッケージとして定義します
- AOPの@AfterThrowingアノテーションを使用して、グローバル例外キャプチャパッケージcom.example.promethuesdemo.exception; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; importorg.aspectj.lang.annotation.AfterThrowingの例を取得します。 ; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; / ** * @author chenzhen * 2020/7/20にchenzhenによって作成されました。
*/
@Aspect
@Slf4j
@Component
public class GlobalExceptionAspect {
@Pointcut("execution(* com.example..*.*(..))")
public void pointcut(){
}
@AfterThrowing(pointcut = "pointcut()",throwing = "e")
public void afterThrowing(JoinPoint joinPoint,Throwable e){
log.error("全局捕获到异常了..............");
//纪录错误信息
log.error("系统错误:{}", e.getMessage());
// todo 想要执行的操作
}
}
aopの関連概念
- アスペクト(アスペクト):アスペクト宣言はJavaのクラス宣言に似ています。アスペクトには、いくつかのポイントカットと対応するアドバイスが含まれます。*ジョイントポイント(ジョイントポイント):プログラム内で明確に定義されたポイントを示します。通常、メソッド呼び出し、クラスメンバーへのアクセス、例外ハンドラブロックの実行などが含まれます
。他のジョイントポイントをネストすることもできます。*ポイントカット(ポイントカット):ジョイントポイントのセットを表します。これらのジョイントポイントは、論理的な関係によって結合されるか、対応するアドバイスが発生する場所を定義するワイルドカードや正規表現などによって集中化されます。*アドバイス(拡張):アドバイスは、ポイントカットで定義されたプログラムポイントで実行される特定の操作を定義します。アドバイスは、コードを実行する前、後、または実行する代わりに、前、後、および前後を区別します。*ターゲット(ターゲットオブジェクト):アドバイスに織り込まれたターゲットオブジェクト。ウィービング:アスペクトと他のオブジェクトを接続し、アドバイスされたオブジェクトを作成するプロセス
アドバイス(拡張)タイプ
- ビフォアアドバイスは、ジョインポイントの前に実行されるアドバイスです。ビフォアアドバイスは、ジョインポイントの前に実行されますが、例外が発生しない限り(つまり、ビフォアアドバイスコードを
人為的に実行することはできません)、ジョインポイントの実行を妨げることはできません。ジョインポイントでコードの実行を続行するかどうかを決定します)*リターンアドバイスの後、ジョインポイントの後に実行されるアドバイスは正常に戻ります*アドバイスをスローした後、ジョインポイントの後に実行されるアドバイスは例外をスローします*(最終)アドバイスの後、誰でも、ジョインポイントが正常に終了するか例外が発生するかに関係なく、実行されるアドバイスになります。*アドバイスの周囲では、ジョインポイントの終了前と終了後に実行されるアドバイスです。これは最も一般的に使用されるアドバイスです。 。*イントロダクション、イントロダクションはオリジナルにすることができますオブジェクトは新しいプロパティとメソッドを追加します。
注意
春のAfterThrowing拡張処理AOPはターゲットメソッドの例外を処理できますが、この処理は、catchを直接使用して例外を処理する方法とは異なります。キャッチキャッチとは、例外を完全に処理できることを意味します。つまり、catchブロックがあればそれ自体は新しいExceptionをスローせず、処理された例外は上位レベルの呼び出し元にそれ以上伝播されません。ただし、AfterThrowing拡張処理を使用して例外を処理する場合、例外は処理後に上位レベルの呼び出し元に伝播されます。以下のスクリーンショットに示すように、メインのTargetメソッドで呼び出された場合、例外はJVMに直接送信されます。
また、ターゲットメソッドで例外が発生し、catchによってキャッチされ、catchが新しい例外をスローしない場合、ターゲットメソッドのAfterThrowing拡張処理は実行されないことに注意してください。