エキサイト株式会社エンジニアの佐々木です。エキサイトHDアドベントカレンダー1日目を担当させていただきます。
SpringBootのRestControllerAdvice
が便利で多用しているのですが、複数定義したときにハマりましたので共有になります。
コード
事象が発生した当時は下記のような実装がされていました。
- ExceptionController.java
public class ExceptionRestController { @ExceptionHandler(RuntimeException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Map<String, String> lineTokenInvalidException(RuntimeException ex) { log.error("error: {}", "RuntimeException"); return Map.of("error", "RuntimeException"); } }
- ExceptionRest2Controller.java
public class ExceptionRestController { @ExceptionHandler(RequestForbiddenException.class) @ResponseStatus(HttpStatus.FORBIDDEN) public Map<String, String> lineTokenInvalidException(RequestForbiddenException ex) { log.error("error: {}", "RequestForbiddenException"); return Map.of("error", "RequestForbiddenException"); } }
現象
上記のコードのときに、疑似的にRequestForbiddenException
を発火させてみます。
@RequestMapping("sample") public class SampleController { @GetMapping("request_forbidden") public String index(){ throw new RequestForbiddenException(); } }
curl localhost:8080/sample/request_forbidden `error: RuntimeException`
RequestForbiddenException
をthrowしましたのに、error: RuntimeException
が出力されました。RequestForbiddenException
を期待したのですが、1つめのExceptionRestController.java
が実行されてしまいました。
別々のクラスにすると、このような現象が起きるみたいです。
解決方法
解決方法は、1つのクラスにまとめると適切にハンドリングをしてくれます。
public class ExceptionRestController { @ExceptionHandler(RequestForbiddenException.class) @ResponseStatus(HttpStatus.FORBIDDEN) public Map<String, String> requestForbiddenException(RequestForbiddenException ex) { log.error("error: {}", "RequestForbiddenException"); return Map.of("error", "RequestForbiddenException"); } @ExceptionHandler(RuntimeException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Map<String, String> runtimeException(RuntimeException ex) { log.error("error: {}", "RuntimeException"); return Map.of("error", "RuntimeException"); } }
先程のコマンドを実行してみます。
curl localhost:8080/sample/request_forbidden `error: RequestForbiddenException`
まとめ
ControllerAdviceは、AOPを使用した便利な実装ですが、エラー等もでないのでちょっとこわいとおもいました。濫用はせずに同じ用途のクラスでまとめておくことが大事かもしれません。