일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- javascript
- Gradle
- Spring Boot
- AWS
- IntelliJ
- Design Patterns
- Git
- jsp
- springboot
- Spring
- redis
- tool
- linux
- elasticsearch
- 맛집
- java
- Spring Batch
- Web Server
- ReactJS
- db
- it
- Oracle
- php
- ubuntu
- 요리
- MySQL
- JVM
- laravel
- devops
- jenkins
Archives
- Today
- Total
아무거나
@ControllerAdvice 를 통한 Global Exception Handler 처리 본문
반응형
Exception 을 개선하다가 예전에 포스팅을 작성해야지 하면서 깜박했던 부분이 있어 다시 작성하게되었다.
- @ControllerAdvice 를 통한 Global Exception Handler 처리
- 에러 객체 생성
[ErrorResponse.java]import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import org.springframework.validation.BindingResult; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import com.example.bkjeon.enums.exception.ErrorCode; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; /** * message: 에러 메세지 * status: http status code * errors: 요청 값에 대한 field, value, reason 작성, 일반적으로 @Valid 로 검증을 진행 * code: 에러 코드 값 */ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class ErrorResponse { private String message; private int status; private List<FieldError> errors; private String code; private ErrorResponse(final ErrorCode code, final List<FieldError> errors) { this.message = code.getMessage(); this.status = code.getStatus(); this.errors = errors; this.code = code.getCode(); } private ErrorResponse(final ErrorCode code) { this.message = code.getMessage(); this.status = code.getStatus(); this.code = code.getCode(); this.errors = new ArrayList<>(); } public static ErrorResponse of(final ErrorCode code, final BindingResult bindingResult) { return new ErrorResponse(code, FieldError.of(bindingResult)); } public static ErrorResponse of(final ErrorCode code) { return new ErrorResponse(code); } public static ErrorResponse of(final ErrorCode code, final List<FieldError> errors) { return new ErrorResponse(code, errors); } public static ErrorResponse of(MethodArgumentTypeMismatchException e) { final String value = e.getValue() == null ? "" : e.getValue().toString(); final List<ErrorResponse.FieldError> errors = ErrorResponse.FieldError.of(e.getName(), value, e.getErrorCode()); return new ErrorResponse(ErrorCode.INVALID_INPUT_VALUE_BINDING_ERROR, errors); } @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public static class FieldError { private String field; private String value; private String reason; private FieldError(final String field, final String value, final String reason) { this.field = field; this.value = value; this.reason = reason; } public static List<FieldError> of(final String field, final String value, final String reason) { List<FieldError> fieldErrors = new ArrayList<>(); fieldErrors.add(new FieldError(field, value, reason)); return fieldErrors; } private static List<FieldError> of(final BindingResult bindingResult) { final List<org.springframework.validation.FieldError> fieldErrors = bindingResult.getFieldErrors(); return fieldErrors.stream() .map(error -> new FieldError( error.getField(), error.getRejectedValue() == null ? "" : error.getRejectedValue().toString(), error.getDefaultMessage())) .collect(Collectors.toList()); } } }
- 에러 코드 정의
[ErrorCode.java]import org.springframework.http.HttpStatus; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Getter; /** * 에러 코드 정의 * > 코드값은 _ 앞의 첫자리 대문자를 따서 코드값 + Http Status Code 로 합쳐서 만듬 * > 에러 메세지는 Common 과 각 도메인별로 관리 */ @Getter @JsonFormat(shape = JsonFormat.Shape.OBJECT) public enum ErrorCode { // Common BAD_REQUEST(HttpStatus.BAD_REQUEST.value(), "ECBR400", "Bad Request"), METHOD_ARGUMENT_TYPE_ENUM_BINDING_MISMATCH(HttpStatus.BAD_REQUEST.value(), "ECEBM400", "Bad Request (Method Argument Type Mismatch Error)"), UNAUTHORIZED(HttpStatus.UNAUTHORIZED.value(), "ECU401", "Unauthorized"), FORBIDDEN(HttpStatus.FORBIDDEN.value(), "ECF403", "Forbidden"), NOT_FOUND(HttpStatus.NOT_FOUND.value(), "ECN404", "Not Found"), METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED.value(), "ECMNA405", "Method Not Allowed"), CONFLICT(HttpStatus.CONFLICT.value(), "ECC409", "Conflict"), PRECONDITION_FAILED(HttpStatus.PRECONDITION_FAILED.value(), "ECPF412", "Precondition Failed"), TOO_MANY_REQUESTS(HttpStatus.TOO_MANY_REQUESTS.value(), "ECTMR429", "Too Many Requests"), INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR.value(), "ECISE500", "Internal Server Error"), BAD_GATEWAY(HttpStatus.BAD_GATEWAY.value(), "ECBG502", "Bad Gateway"), SERVICE_UNAVAILABLE(HttpStatus.SERVICE_UNAVAILABLE.value(), "ECSU503", "Service Unavailable"), INVALID_INPUT_VALUE_BINDING_ERROR(HttpStatus.BAD_REQUEST.value(), "ECIIVBE400", "Invalid Input Value Binding"), // Board BOARD_INSERT_FAILED(HttpStatus.BAD_REQUEST.value(), "EBBIF001", "Board Data Insert Error"), BOARD_UPDATE_FAILED(HttpStatus.BAD_REQUEST.value(), "EBBUF002", "Board Data Update Error"), BOARD_EMPTY(HttpStatus.BAD_REQUEST.value(), "EBBE003", "Board Data Empty"); private final String code; private final String message; private int status; ErrorCode(final int status, final String code, final String message) { this.status = status; this.message = message; this.code = code; } }
- 예외 핸들러에 사용할 결과 모델 클래스 생성
[ApiResponse.java]import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @Getter @AllArgsConstructor @Builder public class ApiResponse<T> { private int statusCode; private String responseMessage; private T param; private T data; public static<T> ApiResponse<T> res( final int statusCode, final String responseMessage, final T param, final T responseData ) { return ApiResponse.<T>builder() .statusCode(statusCode) .responseMessage(responseMessage) .param(param) .data(responseData) .build(); } }
- Custom Exception 관련 클래스 생성 (비즈니스 로직 예외 처리 용도)
[BoardException.java]import com.example.bkjeon.enums.exception.ErrorCode; import lombok.Getter; @Getter public class BoardException extends RuntimeException { private ErrorCode errorCode; public BoardException(String message, ErrorCode errorCode) { super(message); this.errorCode = errorCode; } public BoardException(ErrorCode errorCode) { super(errorCode.getMessage()); this.errorCode = errorCode; } }
- @ControllerAdvice로 모든 예외 핸들링 정의
[ApiExceptionHandler.java]import java.util.HashMap; import java.util.Map; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import com.example.bkjeon.enums.exception.ErrorCode; import com.example.bkjeon.model.response.ApiResponse; import lombok.extern.slf4j.Slf4j; @ControllerAdvice @Slf4j public class ApiExceptionHandler { /** * javax.validation.Valid or @Validated 으로 binding error 발생시 발생한다. * HttpMessageConverter 에서 등록한 HttpMessageConverter binding 못할경우 발생 * 주로 @RequestBody, @RequestPart 어노테이션에서 발생 */ @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseBody private ApiResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { Map<String, String> errorMap = new HashMap<>(); e.getBindingResult().getAllErrors() .forEach(c -> errorMap.put(((FieldError) c).getField(), c.getDefaultMessage())); StringBuilder sb = new StringBuilder(); sb.append(ErrorCode.INVALID_INPUT_VALUE_BINDING_ERROR.getMessage()); sb.append(" ("); for (Object o: errorMap.entrySet()) { sb.append(o); break; } sb.append(")"); log.error("=================== MethodArgumentNotValidException Error !!: {}", e); return ApiResponse.builder() .statusCode(ErrorCode.INVALID_INPUT_VALUE_BINDING_ERROR.getStatus()) .responseMessage(sb.toString()) .build(); } /** * @ModelAttribute 으로 binding error 발생시 BindException 발생한다. * ref https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-modelattrib-method-args */ @ExceptionHandler(BindException.class) private ApiResponse handleBindException(BindException e) { log.error("=================== BindException Error !!", e); final ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE_BINDING_ERROR, e.getBindingResult()); return ApiResponse.builder() .statusCode(response.getStatus()) .responseMessage(response.getMessage()) .build(); } /** * enum type 일치하지 않아 binding 못할 경우 발생 * 주로 @RequestParam Enum binding 못했을 경우 발생 */ @ExceptionHandler(MethodArgumentTypeMismatchException.class) @ResponseBody private ApiResponse handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) { log.error("=================== MethodArgumentTypeMismatchException Error !!", e); return ApiResponse.builder() .statusCode(ErrorCode.METHOD_ARGUMENT_TYPE_ENUM_BINDING_MISMATCH.getStatus()) .responseMessage(ErrorCode.METHOD_ARGUMENT_TYPE_ENUM_BINDING_MISMATCH.getMessage()) .build(); } /** * 지원하지 않은 HTTP method 호출 할 경우 발생 */ @ExceptionHandler(HttpRequestMethodNotSupportedException.class) @ResponseBody private ApiResponse handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { log.error("=================== HttpRequestMethodNotSupportedException Error !!", e); return ApiResponse.builder() .statusCode(ErrorCode.METHOD_NOT_ALLOWED.getStatus()) .responseMessage(ErrorCode.METHOD_NOT_ALLOWED.getMessage()) .build(); } /** * 게시물 관련 Custom Exception * @param e * @return */ @ExceptionHandler(BoardException.class) @ResponseBody private ApiResponse handleBoardException(BoardException e) { log.error("=================== BoardException Error !!", e); final ErrorCode errorCode = e.getErrorCode(); final ErrorResponse response = ErrorResponse.of(errorCode); return ApiResponse.builder() .statusCode(response.getStatus()) .responseMessage(response.getMessage()) .build(); } @ExceptionHandler(Exception.class) @ResponseBody private ApiResponse handleException(Exception e) { log.error("=================== Exception Error !!", e); return ApiResponse.builder() .statusCode(ErrorCode.INTERNAL_SERVER_ERROR.getStatus()) .responseMessage(ErrorCode.INTERNAL_SERVER_ERROR.getMessage()) .build(); } }
- Exception Test용 컨트롤러 및 파라미터 관련 클래스 생성
[ApiExceptionController.java]
[ExceptionDTO.java]import javax.servlet.http.HttpServletRequest; import org.springframework.http.HttpMethod; import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import com.example.bkjeon.base.exception.error.BoardException; import com.example.bkjeon.enums.exception.ErrorCode; import com.example.bkjeon.dto.exception.ExceptionDTO; import io.swagger.annotations.ApiOperation; @RestController @RequestMapping("v1/apiException") public class ApiExceptionController { @ApiOperation("Exception 처리") @GetMapping("returnException") public void returnException() throws Exception { throw new Exception("Exception Error ~!"); } @ApiOperation("사용자 에러 처리") @GetMapping("customException") public void returnCustomException() throws BoardException { // Board 관련 예시 throw new BoardException("setBoardReply Error!", ErrorCode.BOARD_INSERT_FAILED); } @ApiOperation("Method 관련 테스트 이므로 Get 메소드만 성공") @RequestMapping("returnHttpRequestMethodNotSupportedException") public void returnHttpRequestMethodNotSupportedException( HttpServletRequest req) throws HttpRequestMethodNotSupportedException { if (!HttpMethod.GET.name().equals(req.getMethod())) { throw new HttpRequestMethodNotSupportedException("HttpRequestMethodNotSupportedException Error ~!"); } } @ApiOperation("Enum Type Exception 처리 (ErrorCode ENUM 에 정의되어있는값을 넘겨야 성공 ex: BAD_REQUEST)") @GetMapping("handleMethodArgumentTypeMismatchException") public void handleMethodArgumentTypeMismatchException( @RequestParam("errorCode") ErrorCode errorCode) throws MethodArgumentTypeMismatchException {} @ApiOperation("BindException 처리") @PostMapping("returnBindException") public void returnBindException( @ModelAttribute ExceptionDTO exceptionDTO, BindingResult bindingResult ) throws BindException { throw new BindException(bindingResult); } }
import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor public class ExceptionDTO { private String title; private Integer sort; }
- 에러 객체 생성
반응형
'Java & Kotlin > Spring' 카테고리의 다른 글
POJO 란 (0) | 2022.09.06 |
---|---|
Spring Boot Graceful Shutdown 사용한 애플리케이션 정상 종료 (0) | 2022.08.08 |
[Spring Boot] @ControllerAdvice 를 사용한 Global Exception Handler 처리 (0) | 2022.07.26 |
서킷브레이커(=Circuitbreaker) Resilience4j 적용 (Java + Spring Boot) 3편 (1) | 2022.07.13 |
서킷브레이커(=Circuitbreaker) Resilience4j 적용 (Java + Spring Boot) 2편 (3) | 2022.07.08 |
Comments