@RestControllerAdviceを複数定義するときに注意すること

エキサイト株式会社エンジニアの佐々木です。エキサイト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を使用した便利な実装ですが、エラー等もでないのでちょっとこわいとおもいました。濫用はせずに同じ用途のクラスでまとめておくことが大事かもしれません。