[SpringBoot]RestController正常時と異常時で別の型のJSONを返却する

SpringBootでは`@RestController`を使えばお手軽にJSONを返すコントローラーが作れて大変便利。

```
@RestController
public class HogeController{
    @Autowired
    EmpRepository empRepository;

    @GetMapping
    public List<Employee> getEmp(){
        return empRepository.findAll();
    }
}
```

こんな感じにしてあげればEmployeeクラスのリストをJSONにして返却してくれる。

でもでも、エラーのときはエラー情報返してあげたい。
そんでもってその時の型はEmployeeのリストじゃなくてエラーメッセージとかエラーコードとかを返したい。
もちろんHTTPステータスもつけたい。
戻り値の型をStringにしてJSON文字列に変換して・・・なんてやったら台無しだし！

というわけでいい感じにエラー情報を返してみましょう。
先にざっくりまとめると、通常処理はExceptionで抜けて例外ハンドリングして別のレスポンス作って返せばOKです。


#1.例外ハンドラーを作成
`@ControllerAdvice`と`@ExceptionHandler`を使って例外をハンドリングするクラスを作ります。
共通処理にするんじゃなくて、元のクラス専用の例外ハンドリングをしたい場合は、元のクラス内に`@ExceptionHandler`を付けたメソッドを作成すればOK。

```:例外ハンドラーその１
@ControllerAdvice
public class BadRequestExceptionHandler {

	@ExceptionHandler(BadRequestException.class)
	public ResponseEntity<ErrorResponse> getException(HttpServletRequest req, BadRequestException e){
        // エラーレスポンス。のちほど解説
		return ErrorResponse.createResponse(e);

	}
}
```


#2.自前のExceptionを作成
これは必須ではないですが、任意のタイミング・内容でExceptionを投げるために作っておくと便利。
上で既に使っている`BadRequestException`ですね。
(getter、setterは省略してます。)

```:自前Exception
public class BadRequestException extends Exception {

	private String keyName;
	private String keyValue;
	private String error;
	
	public BadRequestException(String error, String keyName, String keyValue){
		this.keyName = keyName;
		this.keyValue = keyValue;
		this.error = error;
	}

}
```

エラーとなった項目、その値、エラー情報を渡せるようにしてみました。

#3.エラーレスポンス用のクラスを作成
エラーレスポンス用の情報を入れるクラスを作ります。POJOです。

```:エラーレスポンス用のクラス
public class ErrorResponse {
	private String KeyName;
	private String KeyValue;
	private String Message;
	
	public ErrorResponse(String keyName,String  keyValue,String  message){
		this.KeyName = keyName;
		this.KeyValue = keyValue;
		this.Message = message;
	}
}
```

##3.1.返却するレスポンスについて
レスポンスを作成するには`ResponseEntity<T>`を使います。
```
new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.BAD_REQUEST)
```
みたいなかんじですね。
これは先ほど作ったErrorResponseクラスの中で生成してもらいましょう。

```:ErrorResponse.createResponse
public ResponseEntity<ErrorResponse> createResponse(HttpStatus status){
	return new ResponseEntity<ErrorResponse>(this, status);
}
```

ついでにExceptionをからResponseEntityを作成するstaticクラスも作ってしまいましょう。

```:ErrorResponse.createResponse(static)
public static ResponseEntity<ErrorResponse> createResponse(BadRequestException e){
	return new ResponseEntity<ErrorResponse>(
			new ErrorResponse(e.getKeyName(),e.getKeyValue(),e.getMessage())
				,HttpStatus.BAD_REQUEST);
}
```

#4.できた！
ここまで実装してあげたら`BadRequestExceptionHandler`が例外をキャッチして`ErrorResponse.createResponse(e)`でレスポンスを作成して`ErrorResponse`のJSONをクライアントに返却できるようになります。
やったね！

#5.一般例外のハンドリング
せっかくなので一般例外もハンドリングします。
リクエストパラメータが受け取り側の変数型に変換できないときに発生する`HttpMessageNotReadableException`、
該当URL無しの`NoHandlerFoundException`、
GET用処理にPOSTしてきたときなどに発生する`HttpRequestMethodNotSupportedException`、
とりあえず全部例外拾っとくかの`Exception`。
これらを追加した例外ハンドラーがこちら。

```
@ControllerAdvice
public class BadRequestExceptionHandler {

	@ExceptionHandler(BadRequestException.class)
	public ResponseEntity<ErrorResponse> getException(HttpServletRequest req, BadRequestException e){

		return ErrorResponse.createResponse(e);

	}
	
	/**
	 * 400 - Bad Reque
	 */
	@ExceptionHandler(HttpMessageNotReadableException.class)
	public ResponseEntity<ErrorResponse> notReadable(HttpServletRequest req){
		return new ResponseEntity<ErrorResponse>(
				new ErrorResponse("All","unknown","NotReadable")
				,HttpStatus.BAD_REQUEST);
	}
	
	/**
	 * 404 - Not Found
	 */
	@ExceptionHandler(NoHandlerFoundException.class)
	public ResponseEntity<ErrorResponse> notFound(HttpServletRequest req){
		return new ResponseEntity<ErrorResponse>(
				new ErrorResponse(null,null,"Not Found")
				,HttpStatus.NOT_FOUND);
	}
	
	/**
	 * 405 - Method Not Allowed
	 */
	@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
	public ResponseEntity<ErrorResponse> notSupported(HttpServletRequest req){
		return new ResponseEntity<ErrorResponse>(
				new ErrorResponse(null,null,"Method Not Allowed")
				,HttpStatus.METHOD_NOT_ALLOWED);
	}

	@ExceptionHandler(Exception.class)
	public ResponseEntity<ErrorResponse> anotherException(Exception e, HttpServletRequest req){
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");
		
		// ログは、大事
		System.err.println("ERROR(500) " + (sdf.format((Calendar.getInstance()).getTime()))
				+ ": remoteAddr:" + req.getRemoteAddr()
				+ ", remoteHost:" + req.getRemoteHost()
				+ ", requestURL:" + req.getRequestURL()
				);
		e.printStackTrace();
		
		return new ResponseEntity<ErrorResponse>(
				new ErrorResponse(null,null,"unknown error")
				,HttpStatus.INTERNAL_SERVER_ERROR);
	}
}
```

以上！

#参考

Exception Handling in Spring REST Web Service 
https://dzone.com/articles/exception-handling-spring-rest