arcanum_jp’s blog

おっさんの日記

SpringBoot 例外処理についてのメモ

SpringBootでの例外処理について学んだのでメモ

基本

コントローラー内にて、メソッドに@ExceptionHandlerを指定すると例外が発生するとそのメソッドが呼び出される。下記で実行すると、http://localhost/expで例外が発生しますが、例外が発生するとexception( )が呼び出される。

ハンドラ内でリクエストやレスポンス、送出された例外にアクセスしたい場合はメソッド引数に定義すれば使える。便利すぎんぞ!!

コントローラーには下のケースでは@RestControllerを指定しているため、ハンドラではAPIの戻りとしてJSONなりを返せばいい。サンプルでは単純に文字列を返している。

コントローラーに@Controllerを指定している場合は、エラーページへのパスを返せばいいそうだ

@RestController
public class HelloController {

	@RequestMapping(value="exp", method=RequestMethod.GET)
	public String doException(HttpServletResponse resp) throws Exception {
		throw new Exception("HELLO Exception!!");
	}

        ... 他の処理

	@ExceptionHandler(Exception.class)
	public String exception(Exception e) {
		return "Exception!! " + e.toString();
	}

}

例外ごとのハンドラ

上記ではExceptionで補足してしまったので、非検査例外やら他のException配下を個別にハンドルしたい時はどうするか?と思い、各例外ごとにハンドラを作ってみた。

@RestController
public class HelloController {

        ...さっきのコード

	@RequestMapping(value="fio", method=RequestMethod.GET)
	public String doIOException(HttpServletResponse resp) throws Exception {
		throw new FileNotFoundException("HELLO FileNotFoundException!!");
	}

	@RequestMapping(value="nullpo", method=RequestMethod.GET)
	public String doNullPointerException(HttpServletResponse resp) {
		throw new NullPointerException("HELLO NullPointerException!!");
	}
	
	@RequestMapping(value="illegalArg", method=RequestMethod.GET)
	public String doIllegalArgumentException(HttpServletResponse resp) {
		throw new IllegalArgumentException("args error!!");
	}

	
	@ExceptionHandler(IOException.class)
	public String fioException(Exception e) {
		return "FileNotFoundException!! " + e.toString();
	}
	
	@ExceptionHandler(NullPointerException.class)
	public String nullPointerException(Exception e) {
		return "NullPointerException!! " + e.toString();
	}
	
	

結果としては、個別に補足してくれる。SpringBootはリクエストでマッピングされたメソッド内で例外が発生した場合、同一コントローラー内にハンドラが定義されていたら定義に従い通知する。ということ。まぁ至極あたりまえの感覚か・・

コントローラ以外にハンドラ定義

先ほど、「コントローラー内にて、メソッドに@ExceptionHandlerを指定する」と書いたが、じゃぁコントローラー以外にハンドラを定義した時はどうなのだろうと、上記のクラスに次のように追加およびコントローラーじゃないクラスにハンドラを定義してみた

@RestController
public class HelloController {
       ... さっきのコード

	@RequestMapping(value="illegalArg", method=RequestMethod.GET)
	public String doIllegalArgumentException(HttpServletResponse resp) {
		throw new IllegalArgumentException("args error!!");
	}

}


public class NotController {	
	@ExceptionHandler(IllegalArgumentException.class)
	public String notController() {
		return "IllegalArgumentException";
	}
	
}

予想では/illegalArgでアクセスした時、IllegalArgumentExceptionが発生するがハンドラが見当たらないのでexception( )が呼び出される。結果としてはコントローラじゃないクラスは無視。まぁ、当たり前的なことを確認した。アノテーションされていないクラスは知らんと。

全体で使えるハンドラ定義

じゃぁプログラミングするときにコントローラーごとに例外のハンドラを定義していけばいいのか?していくのか?って疑問が、、そりゃ大変そうだ、、収拾がつかなくなりそう。そのときは全体で共通のハンドラを定義が可能

全体のハンドラ用クラスを定義して、そのクラスに@ControllerAdviceまたは@RestControllerAdviceをつけ、その中でハンドラメソッドを定義する。下のサンプルではJSONで返すようにしている。

@RestControllerAdvice
public class ExpHandler {

	@ExceptionHandler(Exception.class)
	public ExpBean exceptionHandle(Exception e, HttpServletResponse resp) {
		resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
		return new ExpBean(e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
	}
	
}

この場合の疑問として、全体のハンドラとコントローラ内で定義されたローカルなハンドラはどちらが優先されるだろうって。。以下のコントローラーを定義してみた。

@RestController
public class HelloController2 {
	
	@RequestMapping(value="/exp2", method=RequestMethod.GET)
	@Transactional
	public String index() {
		throw new RuntimeException("他のコントロラーで例外");
	}
	
}


先ほどまでのサンプルを合わせて実行してみると、/expではコントローラー内で定義されたローカルなハンドラ、/exp2では、コントローラー内ではハンドラの定義がないので、共通で定義されたハンドラが呼び出されたのを確認。

うん、便 利 す ぎ !


なんでこんなのもっと早く覚えなかったのだろうかと