@ControllerAdviceで全体のエラーを一括管理する方法を徹底解説【初心者向け】
新人
「先輩、Springでいろんな場所に@ExceptionHandlerを書いてたら、どれがどの例外を処理してるのか分からなくなってきました…」
先輩
「それは全体設計の観点からも整理しておきたいところだね。Springでは@ControllerAdviceを使うことで、例外処理を一元管理できるんだよ。」
新人
「へえ!その@ControllerAdviceって何ですか?どんなときに使うんですか?」
先輩
「じゃあ今回は、@ControllerAdviceとは何か、そしてなぜ必要なのかをわかりやすく説明していこう。」
1. @ControllerAdviceとは何か?
@ControllerAdviceは、Spring MVCで提供されているアノテーションで、すべての@Controllerに対して共通の処理を定義するための機能です。特に「Spring 全体 エラーハンドリング」を行いたいときに有効です。
これを使えば、アプリケーション全体に共通する@ExceptionHandlerを一か所にまとめて定義できるため、クラスごとにバラバラに記述する必要がなくなります。
下記は@ControllerAdviceを使った基本的なコード例です。プロジェクトはGradle構成、Pleiadesを使った開発環境を前提にしています。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public String handleIllegalArgument(IllegalArgumentException ex, Model model) {
model.addAttribute("errorMessage", ex.getMessage());
return "error/input-error";
}
}
このコードでは、アプリケーション全体でIllegalArgumentExceptionが発生したときに共通の処理を実行し、Thymeleafテンプレートinput-error.htmlを表示しています。
このように、@ControllerAdviceとは複数の@Controllerにまたがって発生する例外を、まとめて一括で管理できる機能です。特に大規模なプロジェクトではメンテナンス性や保守性が格段に向上します。
2. なぜ個別の@ExceptionHandlerだけでは不十分なのか?
@ExceptionHandlerは便利な仕組みですが、すべての@Controllerに個別に記述していると、以下のような問題が発生します。
- 例外処理のロジックが各クラスに分散し、全体像が把握しにくい
- 同じ処理を複数箇所にコピペしてしまい、メンテナンス性が悪くなる
- 新しい例外を追加するたびに、各
@Controllerに変更が必要になる
特に開発が進んでコントローラーの数が増えてくると、「Spring 例外処理 設計」の面で見ても不便です。開発チームのメンバーが増えた場合にも、共通化されていないとコードの整合性が崩れやすくなります。
ここで@ControllerAdviceを導入すれば、共通の例外処理を中央で管理できるため、保守性が劇的に向上します。
例えば次のような場合でも、各コントローラーに@ExceptionHandlerを書く必要はありません。
@Controller
public class SampleController {
@GetMapping("/calc")
public String calc(@RequestParam("num") int num, Model model) {
if (num < 0) {
throw new IllegalArgumentException("負の数は指定できません");
}
model.addAttribute("result", num * 2);
return "result";
}
}
このIllegalArgumentExceptionは、先ほどの@ControllerAdviceによって処理されるため、SampleControllerには個別の@ExceptionHandlerを書く必要がありません。
このように、例外処理の共通化を意識した「Spring 例外処理 設計」は、コードの品質を保つうえでも重要な考え方です。
3. @ControllerAdviceを使った基本的なコード例
ここでは@ControllerAdvice 使い方の基本として、実際にIllegalArgumentExceptionを処理するコードの流れを確認してみましょう。Springでエラーハンドリングを共通化する際には、どのようにコントローラーと連携させるかが重要です。
// コントローラークラス(@Controller使用)
@Controller
public class MemberController {
@GetMapping("/member")
public String getMember(@RequestParam("id") int id, Model model) {
if (id <= 0) {
// 不正なIDが渡された場合に例外をスロー
throw new IllegalArgumentException("IDは1以上の整数で指定してください。");
}
model.addAttribute("memberName", "田中太郎");
return "member-detail";
}
}
// グローバル例外ハンドラー(@ControllerAdvice使用)
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public String handleIllegalArgument(IllegalArgumentException ex, Model model) {
// エラーメッセージを画面に渡す
model.addAttribute("errorMessage", ex.getMessage());
return "error/illegal-argument";
}
}
このように、どの@ControllerからIllegalArgumentExceptionがスローされても、共通の処理としてGlobalExceptionHandlerが対応します。
4. 複数の例外をまとめて管理する方法
実際の開発では、IOExceptionやRuntimeExceptionなど、さまざまな例外が発生します。それらをすべて個別の@ExceptionHandlerに分けて処理するのは大変なので、@ControllerAdviceでまとめて管理することができます。
以下は、複数の例外に対応した例です。
@ControllerAdvice
public class GlobalExceptionHandler {
// 不正なリクエストパラメータに対応
@ExceptionHandler(IllegalArgumentException.class)
public String handleIllegalArgument(IllegalArgumentException ex, Model model) {
model.addAttribute("errorMessage", ex.getMessage());
return "error/illegal-argument";
}
// ファイル読み込み失敗などに対応
@ExceptionHandler(IOException.class)
public String handleIOException(IOException ex, Model model) {
model.addAttribute("errorMessage", "ファイルの読み込み中にエラーが発生しました。");
return "error/io-error";
}
// その他の想定外の例外に対応
@ExceptionHandler(RuntimeException.class)
public String handleRuntimeException(RuntimeException ex, Model model) {
model.addAttribute("errorMessage", "システム内部で予期しないエラーが発生しました。");
return "error/runtime-error";
}
}
このようにSpring エラー ページ表示の考え方に従って、例外ごとに異なるテンプレートを返すことで、ユーザーに分かりやすいエラー画面を提供できます。
5. 画面遷移とエラーメッセージの表示方法
例外処理を共通化したら、次はThymeleafテンプレートと連携して、ユーザーにわかりやすいエラー画面を表示する必要があります。
以下は、Modelに渡したエラーメッセージをテンプレート側で表示する基本的な構成です。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>エラー</title>
</head>
<body>
<h1>エラーが発生しました</h1>
<p th:text="${errorMessage}">ここにエラーメッセージが表示されます</p>
<a th:href="@{/}">トップページへ戻る</a>
</body>
</html>
このテンプレートファイルは、src/main/resources/templates/error/illegal-argument.htmlなどに配置してください。
もしテンプレートが存在しないと、Whitelabel Error Pageが表示されてしまいます。これはSpring Bootのデフォルトのエラー画面であり、ユーザーには非常に不親切な印象を与えてしまいます。
そのため、Java 例外処理 共通化を正しく設計するためには、テンプレートファイルの準備と、Modelとの連携を忘れずに実装することが重要です。
さらに、エラーページは可能な限り日本語で丁寧に記述し、システム用語を避けてわかりやすくすることで、ユーザーの不安を軽減できます。
6. よくある間違いと注意点
初心者が@ControllerAdviceを使う際に特につまずきやすいのが、「例外が拾われない」「テンプレートが見つからない」といったトラブルです。ここでは「Spring 例外処理 全体設計」の観点から、実際によくある注意点を紹介します。
まず多いのが、独自の例外クラスを作成したにもかかわらず、@ExceptionHandlerでそのクラスを正しく指定していないケースです。以下は正しい定義例です。
// 独自の例外クラス(Exceptionを継承)
public class MemberNotFoundException extends RuntimeException {
public MemberNotFoundException(String message) {
super(message);
}
}
// 例外ハンドラー側での対応
@ExceptionHandler(MemberNotFoundException.class)
public String handleMemberNotFound(MemberNotFoundException ex, Model model) {
model.addAttribute("errorMessage", ex.getMessage());
return "error/member-not-found";
}
このとき、テンプレートファイル名やパスを間違えるとエラー画面が表示されず、Whitelabel Error Pageが出てしまいます。ファイルのパスはsrc/main/resources/templates/error/member-not-found.htmlである必要があります。
また、Thymeleafのタグがth:textであるべきところをth:valueにしてしまったり、${errorMessage}の変数名が一致していないことも原因になります。細かい部分ですが、こうしたミスが「例外が拾われない」原因となります。
7. 個別の@ExceptionHandlerとの使い分け
@ControllerAdviceによる共通化は便利ですが、すべての例外処理を一括で書くのが必ずしも最適とは限りません。ここでは@ExceptionHandlerと@ControllerAdvice 違いを意識しながら、適切な使い分けの考え方を解説します。
たとえば、特定のコントローラーだけで発生する業務固有の例外や、その画面固有のエラーメッセージを出したい場合は、個別の@ExceptionHandlerの方が向いています。
@Controller
public class OrderController {
@GetMapping("/order")
public String order(Model model) {
throw new IllegalStateException("注文処理中に不正な状態が検出されました");
}
@ExceptionHandler(IllegalStateException.class)
public String handleIllegalState(IllegalStateException ex, Model model) {
model.addAttribute("errorMessage", ex.getMessage());
return "error/order-error";
}
}
このように、コントローラー内に@ExceptionHandlerを記述すれば、そのクラス内でのみ例外処理が有効になります。@ControllerAdviceとの主な違いは、適用範囲の広さです。
全体に共通する処理は@ControllerAdviceにまとめ、画面単位の独自処理は個別に記述するというのが、実践的な「Spring 例外処理 全体設計」の基本です。
8. 実践的な設計ポイント(ログ出力・開発と運用の視点・ユーザー視点)
最後に、実際の業務で「Spring エラー ログ出力」や運用を考慮したときに意識すべき設計ポイントを紹介します。例外を処理するだけでなく、ログへの記録やユーザーに配慮したエラーメッセージも大切です。
たとえば、ログには詳細な情報を出力しつつ、画面上には簡潔で分かりやすいメッセージだけを見せるという方法があります。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(Exception.class)
public String handleAllExceptions(Exception ex, Model model) {
// ログには例外の詳細を出力(開発・運用用)
logger.error("予期しないエラーが発生しました", ex);
// ユーザーには簡潔なメッセージを表示
model.addAttribute("errorMessage", "申し訳ありません。内部エラーが発生しました。");
return "error/general-error";
}
}
このようにしておくことで、開発者や運用担当はログファイルを元に原因を特定しやすくなります。特に障害対応では、スタックトレースを含めたログが非常に役立ちます。
また、ユーザー視点では、専門用語や英語の例外メッセージをそのまま表示すると混乱を招くため、必ず日本語で丁寧なメッセージに変換するのが基本です。
そして開発と運用のどちらにも有効な手段として、共通テンプレートにエラーコードを表示するという方法もあります。たとえば、「E001」などのコードを画面に表示し、問い合わせ時に参照できるようにすることで、サポート対応の効率も上がります。
このように、「Spring 例外処理 全体設計」を意識した構成にすることで、エラー処理の品質や運用効率を大きく向上させることが可能です。