🧸
SpringBoot Exceptionハンドリング
前回の続き
エラーが発生した際に詳細な内容をJSONで返すようにする
レスポンス例1
{
"handlerName": "handleDuplicateKeyException",
"localDatetime": "2022-01-04T19:38:05.955901",
"exceptionCauseMessage": "Duplicate entry '1' for key 'user_idx'",
"exceptionClassName": "org.springframework.dao.DuplicateKeyException",
"requestUri": "/insert_user/1",
"requestParams": "{name=name2}",
"headers": "{content-length=10, host=localhost:8080, content-type=application/x-www-form-urlencoded}",
"requestSessionId": "89C7B5111202C2A20C8DD2759CBED1FE"
}
レスポンス例2
{
"handlerName": "handleAppException",
"localDatetime": "2022-01-04T19:36:03.087691",
"exceptionCauseMessage": "Data truncation: Data too long for column 'name' at row 1",
"exceptionClassName": "org.springframework.dao.DataIntegrityViolationException",
"requestUri": "/update_user/1",
"requestParams": "{name=too long name}",
"headers": "{content-length=18, host=localhost:8080, content-type=application/x-www-form-urlencoded}",
"requestSessionId": "51C936B64DCBD05B4F2059A151472A16"
}
既存のソースは変更せず、下記の2ファイルを追加することで、エラーハンドリングできるようになる
AppExceptionBean.java
package com.example.demo.component.error;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class AppExceptionBean {
final String handlerName;
final LocalDateTime localDatetime;
final String exceptionCauseMessage;
final String exceptionClassName;
final String requestUri;
final String requestParams;
final String headers;
final String requestSessionId;
}
Exception発生時にJSONで返却する項目
AppExceptionHandler.java
package com.example.demo.component.error;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import org.springframework.web.context.request.ServletWebRequest;
@RestControllerAdvice
public class AppExceptionHandler extends ResponseEntityExceptionHandler {
private static final Logger log =
LoggerFactory.getLogger(AppExceptionHandler.class);
@ExceptionHandler(DuplicateKeyException.class)
public ResponseEntity<Object> handleDuplicateKeyException(DuplicateKeyException ex, WebRequest request) {
Map<String,String> requestParams = new HashMap<>();
request.getParameterNames().forEachRemaining(
name -> requestParams.put(name, request.getParameter(name))
);
Map<String,String> headers = new HashMap<>();
request.getHeaderNames().forEachRemaining(
name -> headers.put(name, request.getHeader(name))
);
AppExceptionBean appExceptionBean = new AppExceptionBean(
"handleDuplicateKeyException",
LocalDateTime.now(),
ex.getCause().getMessage(),
ex.getClass().getName(),
((ServletWebRequest)request).getRequest().getRequestURI().toString(),
requestParams.toString(),
headers.toString(),
request.getSessionId()
);
log.error("handleDuplicateKeyException",ex);
return super.handleExceptionInternal(
ex, appExceptionBean, null, HttpStatus.INTERNAL_SERVER_ERROR, request);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleAppException(Exception ex, WebRequest request) {
Map<String,String> requestParams = new HashMap<>();
request.getParameterNames().forEachRemaining(
name -> requestParams.put(name, request.getParameter(name))
);
Map<String,String> headers = new HashMap<>();
request.getHeaderNames().forEachRemaining(
name -> headers.put(name, request.getHeader(name))
);
AppExceptionBean appExceptionBean = new AppExceptionBean(
"handleAppException",
LocalDateTime.now(),
ex.getCause().getMessage(),
ex.getClass().getName(),
((ServletWebRequest)request).getRequest().getRequestURI().toString(),
requestParams.toString(),
headers.toString(),
request.getSessionId()
);
log.error("handleAppException",ex);
return super.handleExceptionInternal(
ex, appExceptionBean, null, HttpStatus.INTERNAL_SERVER_ERROR, request);
}
}
親クラスResponseEntityExceptionHandlerのhandleExceptionInternalをreturnするメソッドを作成する
@RestControllerAdviceのアノテーションをつけないと、このクラスが反応しない
@ExceptionHandlerでハンドリングするExceptionを指定している
Exception.classを指定すると、全てのExceptionが対象になる
メソッドの書く順番は関係なく、一番該当するものが1つ選ばれてエラーハンドリングされる
DuplicateKeyExceptionの場合、handleDuplicateKeyExceptionのみに反応し、
DataIntegrityViolationExceptionの場合、handleAppExceptionのみに反応する
Discussion