프로젝트

[SpringBoot 예외처리] @ExceptionHandler 설정

nineDeveloper 2020. 6. 20.
728x90

@ExceptionHandler 설정

에러 처리는 @ExceptionHandler를 통해 처리하도록 구성

크리티컬한 에러는 Dooray 메신저로 에러 전송을 하도록 구성함

자세한 내용은 Dooray 메신저 에러 전송 설정 가이드 참조
https://parkingcloud.dooray.com/project/2525192394467198586?contentsType=wiki&pageId=2608604964277753769

exception 패키지 구조

Imis V2 APIexception 패키지 구조를 참조

  • common: 공통 Exception 처리 패키지
  • imisFile: ImisFileControllerException 처리를 위한 패키지
  • excel: excel component Exception 처리를 위한 패키지
  • jooq: jooq Exception 처리를 위한 패키지

constructer.png

ErrorUtil

@ExceptionHandler에 출력할 에러 메세지를 가공하는 Util

getErrorMap

@ExceptionHandler 를 통해 전달 받은 HttpServletRequest 에서 요청정보 데이터를 추출하여 ErrorMap에 담아서 리턴 해준다

ErrorMap에 담긴 요청 정보는 에러 메시지를 로그로 남길 때 표출 해준다

/**
 * Error Map 생성
 * 에러 메세지 맵을 생성하여 메신저에 전송한다
 * @param errorMap
 * @param request
 * @return
 */
public static Map<String, Object> getErrorMap(Map<String, Object> errorMap, HttpServletRequest request) {

    if(errorMap == null) errorMap = new LinkedHashMap<>();

    errorMap.put("Request URI",request.getRequestURI());
    errorMap.put("HttpMethod",request.getMethod());
    errorMap.put("Servlet Path",request.getServletPath());
    errorMap.put("Client IP",request.getLocalAddr());
    errorMap.put("QueryString",request.getQueryString());
    errorMap.put("Parameters",request.getParameterMap());
    return errorMap;
}

errorWriter

log.error 메세지 가공 및 출력 Util

errorWriter를 통해 출력된 에러 메세지는 Dooray 메신저로 전송된다

크리티컬한 에러에 대해서만 처리되도록 한다

/**
     * 에러메세지 출력
     * @param CLASS_NAME
     * @param ERROR_MSG
     * @param errorMap
     * @param e
     */
    public static void errorWriter(String CLASS_NAME, String ERROR_MSG, Map<String, Object> errorMap, Exception e){
        log.error("[{}] ERROR 메세지 :: {}\n\n요청 정보 ::\n{}\n\nException ::\n{}",CLASS_NAME,ERROR_MSG,JsonUtils.toJson(errorMap),e.getMessage(),e);
    }

warnWriter

log.warn 메세지 가공 및 출력 Util

warnWriter를 통해 출력된 에러 메세지는 Dooray 메신저로 전송되지 않으며
로그에만 기록이 된다

일반적으로 크리티컬하지 않으며 커스텀한 @ExceptionHandler를 통해 예외처리 한 WARN로그를 기록하는 용도로 사용한다

    /**
     * WARN 에러메세지 출력
     * @param CLASS_NAME
     * @param ERROR_MSG
     * @param errorMap
     * @param errorMsg
     */
    public static void warnWriter(String CLASS_NAME, String ERROR_MSG, Map<String, Object> errorMap, String errorMsg){
        log.warn("[{}] WARN 메세지 :: {}\n\n요청 정보 ::\n{}\n\nException ::\n{}",CLASS_NAME,ERROR_MSG,JsonUtils.toJson(errorMap),errorMsg);
    }

infoWriter

log.info 메세지 가공 및 출력 Util

infoWriter를 통해 출력된 에러 메세지는 Dooray 메신저로 전송되지 않으며
로그에만 기록이 된다

일반적으로 크리티컬하지 않으며 커스텀한 @ExceptionHandler를 통해 예외처리 한 INFO 로그를 기록하는 용도로 사용한다

    /**
     * INFO 에러메세지 출력
     * @param CLASS_NAME
     * @param ERROR_MSG
     * @param errorMap
     * @param errorMsg
     */
    public static void infoWriter(String CLASS_NAME, String ERROR_MSG, Map<String, Object> errorMap, String errorMsg){
        log.info("[{}] INFO 메세지 :: {}\n\n요청 정보 ::\n{}\n\nException ::\n{}",CLASS_NAME,ERROR_MSG,JsonUtils.toJson(errorMap),errorMsg);
    }

common

common 패키지에는 공통 Exception Handler 와 공통 Exception 클래스가 위치함

서버에서 발생한 예외처리 하지 못한 크리티컬한 Exception에 대해 모두 처리한다

/**
 * Handler 에서 예외처리 되지 않은 Exception 처리
 * @param e
 * @return
 */
@ExceptionHandler(Exception.class)
public CommonResult allException(Exception e, HttpServletRequest request, WebRequest webRequest) throws IOException {
    String ERROR_MSG = e.getMessage();

    setErrorMap(request, webRequest); // ErrorMap 기본 셋팅

    ErrorUtils.errorWriter("",ERROR_MSG,errorMap,e); //에러메세지 출력
    return new CommonResult<>(ERROR, ERROR_MSG, null);
}

setErrorMap

Handler 에서 추가로 ErrorMap에 추가할 데이터를 셋팅 해준다

/**
 * Exception Handler ErrorMap 기본 셋팅
 * @param request
 * @throws IOException
 */
private void setErrorMap(HttpServletRequest request, WebRequest webRequest) throws IOException {
    errorMap = ErrorUtils.getErrorMap(errorMap, request); //에러 맵 생성
    errorMap.put("Request USER ID",AuthUtils.getUserId(request.getHeader("jwt"), secretKey));
    errorMap.put("Request Body",webRequest.getAttribute("body", RequestAttributes.SCOPE_REQUEST));
}

custom

custom 패키지에는 공통 이외에 각 업무별 컨트롤러의 Handler를 아래와 같은 형식으로 작성하면 된다

기본적으로 예외처리 하지 못한 Exception에 대해 처리하는 @ExceptionHandler와 각 Exception@ExceptionHandler 처리를 하면 된다

작성 요령은 아래의 작성 내용을 참조 하면 된다

커스텀 에러 핸들러 작성시 주의 할 점

@Order(Ordered.HIGHEST_PRECEDENCE) 어노테이션으로 실행순서를 commonErrorHandler 보다 먼저 실행 되도록 설정한다

/**
 * Created by KMS on 05/11/2019.
 * ImisFileController 에러 핸들러
 */
@Slf4j
@RestControllerAdvice(assignableTypes = {ImisFileController.class})
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ImisFileErrorHandler {

    private String CLASS_NAME = "ImisFileController";

    private Map<String, Object> errorMap = new LinkedHashMap<>();

    @Value("${jwt.secret-key}")
    String secretKey;

    private RequestContext requestContext;

    /**
     * 임시 파일 적용 시 BODY가 없을 때
     * @param e
     * @param request
     * @return
     * @throws IOException
     */
    @ExceptionHandler(value = HttpMessageNotReadableException.class)
    public CommonResult requestBodyError(HttpMessageNotReadableException e, HttpServletRequest request, WebRequest webRequest) throws IOException {
        String ERROR_MSG = "TEMP 파일을 적용할 RequestBody가 없습니다";

        setImisFileErrorMap(request, webRequest); // ImisFile ErrorMap 기본 셋팅

        ErrorUtils.warnWriter(CLASS_NAME,ERROR_MSG,errorMap,e.getMessage()); //에러메세지 출력
        return new CommonResult<>(FILE_TEMP_APPLY_FAILED, ERROR_MSG, null);
    }

    /**
     * 필수 파라메터 체크
     * @param e
     * @return
     */
    @ExceptionHandler(ImisFileRequestParamRequiredException.class)
    public CommonResult ImisFileRequestParamRequiredException(ImisFileRequestParamRequiredException e, HttpServletRequest request, WebRequest webRequest) throws IOException {
        String ERROR_MSG = e.getMessage();

        setImisFileErrorMap(request, webRequest); // ImisFile ErrorMap 기본 셋팅

        ErrorUtils.warnWriter(CLASS_NAME,ERROR_MSG,errorMap,e.getMessage()); //에러메세지 출력
        return new CommonResult<>(FILE_DUPLACATE, ERROR_MSG, null);
    }


    /**
     * 지정 되지 않은 에러 발생 시 처리
     * @param e
     * @param request
     * @return
     * @throws IOException
     */
    @ExceptionHandler(Exception.class)
    public CommonResult imisFileException(Exception e, HttpServletRequest request, WebRequest webRequest) throws IOException {
        String ERROR_MSG = e.getMessage();

        setImisFileErrorMap(request, webRequest); // ImisFile ErrorMap 기본 셋팅

        ErrorUtils.errorWriter(CLASS_NAME,ERROR_MSG,errorMap,e); //에러메세지 출력
        return new CommonResult<>(ERROR, ERROR_MSG, null);
    }

    /**
     * ImisFile Exception Handler ErrorMap 기본 셋팅
     * @param request
     * @param webRequest
     * @throws IOException
     */
    private void setImisFileErrorMap(HttpServletRequest request, WebRequest webRequest) throws IOException {
        errorMap = ErrorUtils.getErrorMap(errorMap, request); //에러 맵 생성
        errorMap.put("Request USER ID",AuthUtils.getUserId(request.getHeader("jwt"), secretKey));
        errorMap.put("Request Body",webRequest.getAttribute("reqImisFileTempApply", RequestAttributes.SCOPE_REQUEST));
    }
}

특정 컨트롤러에 대한 @ExceptionHandler

특정 컨트롤러에서 발생한 Exception을 처리하고 싶으면

아래와 같이 assignableTypes에 해당 컨트롤러 클래스를 지정해주면 된다

@RestControllerAdvice(assignableTypes = {ImisFileController.class})

@ExceptionHandler@RequestBody 요청 값 전달하기

@RequestBody 요청 값에 대해서는 일반적인 방법으로는 @ExceptionHandler 에서 값을 받아오기가 번거롭다
하지만 WebRequest를 이용해서 attribute에 포함시키면 @ExceptionHandler@RequestBody 요청 값을 쉽게 받아올 수 있다

1. ControllerWebRequest를 파라메터로 추가

  1. 컨트롤러에 아래와 같이 WebRequest를 파라메터로 추가 한다

  2. WebRequest에 전달받은 Body 객체를 RequestAttributes.SCOPE_REQUESTbody라는 키 명으로 setAttribute 한다

    @PostMapping("/temp")
    public CommonResult<List<ResImisFile>> postTempFiles(
                                                      @RequestHeader(value = "jwt", required = true) String jwt,
                                                      @RequestBody(required = true) ReqImisFileTempApply reqImisFileTempApply, WebRequest webRequest ) throws Exception {
    
     // requestBody가 있는 경우 SCOPE_REQUEST에 attribute를 등록하면 exceptionHandler에서 값을 받아 올 수 있다
     webRequest.setAttribute("body", reqImisFileTempApply, RequestAttributes.SCOPE_REQUEST);
     // TEMP 파일 적용 서비스 호출
     return service.applyTempFile(jwt, reqImisFileTempApply);
    }

WebRequest를 파라메터로 추가하면 Swagger에서 WebRequest 파라메터 항목이 표출되는데 Swagger에서
해당 파라메터 항목을 제거 하고 싶으면 아래와 같이 Swagger 설정하면된다

return new Docket(DocumentationType.SWAGGER_2)
    .ignoredParameterTypes(WebRequest.class) //특정 파라메터 무시하기

2. @ExceptionHandler에서 @RequestBody 요청 값 받아오기

  1. @ExceptionHandlerWebRequest를 파라메터로 추가한다
  2. Controller에서 셋팅한 WebRequestRequestAttributes.SCOPE_REQUEST
    set 했던 @RequestBody attribute 값을 get하면 쉽게 받아올 수 있다
    webRequest.getAttribute("body", RequestAttributes.SCOPE_REQUEST)
  3. 별도로 커스텀 @ExceptionHandler 설정을 하지 않는다면 공통 @ExceptionHandler에는 body라는 키명으로 받아 올 수 있도록 이미 설정되어있다
/**
 * 임시 파일 적용 시 BODY가 없을 때
 * @param e
 * @param request
 * @return
 * @throws IOException
 */
@ExceptionHandler(value = HttpMessageNotReadableException.class)
public CommonResult requestBodyError(HttpMessageNotReadableException e, HttpServletRequest request, WebRequest webRequest) throws IOException {
    String ERROR_MSG = "TEMP 파일을 적용할 RequestBody가 없습니다";

    setImisFileErrorMap(request, webRequest); // ImisFile ErrorMap 기본 셋팅

    ErrorUtils.warnWriter(CLASS_NAME,ERROR_MSG,errorMap,e.getMessage()); //에러메세지 출력
    return new CommonResult<>(FILE_TEMP_APPLY_FAILED, ERROR_MSG, null);
}

setErrorMap

Handler 에서 추가로 ErrorMap에 추가할 데이터를 셋팅 해준다

/**
 * ImisFile Exception Handler ErrorMap 기본 셋팅
 * @param request
 * @param webRequest
 * @throws IOException
 */
private void setErrorMap(HttpServletRequest request, WebRequest webRequest) throws IOException {
    errorMap = ErrorUtils.getErrorMap(errorMap, request); //에러 맵 생성
    errorMap.put("Request USER ID",AuthUtils.getUserId(request.getHeader("jwt"), secretKey));
    errorMap.put("Request Body",webRequest.getAttribute("body", RequestAttributes.SCOPE_REQUEST));
}
728x90

댓글

💲 추천 글