Today I Learned
오늘은... 멋지고 품격있는 글쓰기가 아니라 그냥..
기록....
Exception... 진짜 날 괴롭게해
결국 치즈윤님 github에서 도움을 얻었고
security에 맞춰 열심히 개조해보았지만 ㅎㅎ 음...
여전히 잘 모르겠다 아무튼 기록!!
일단 custom한 exception 잡아줄 BusinessException을 Runtimeexeption extends해서 만들어준다
package com.sparta.shopapi.global.handler.exception;
import lombok.Getter;
@Getter
public class BusinessException extends RuntimeException {
private ErrorCode errorCode;
public BusinessException(String message, ErrorCode errorCode) {
super(message);
this.errorCode = errorCode;
}
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
}
그리고 에러코드 설정..
package com.sparta.shopapi.global.handler.exception;
import com.fasterxml.jackson.annotation.JsonFormat;
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum ErrorCode {
// Common
INVALID_INPUT_VALUE(400, "C001", " Invalid Input Value"),
METHOD_NOT_ALLOWED(405, "C002", " Invalid Input Value"),
ENTITY_NOT_FOUND(400, "C003", " Entity Not Found"),
INTERNAL_SERVER_ERROR(500, "C004", "Server Error"),
INVALID_TYPE_VALUE(400, "C005", " Invalid Type Value"),
HANDLE_ACCESS_DENIED(403, "C006", "Access is Denied"),
// Member
ALREADY_EXIST_EMAIL(400, "M001", "이미 가입된 이메일입니다."),
ACCESS_DENIED_ADMIN(400, "M002", "관리자 암호가 틀려 가입할 수 없습니다."),
// Product
NOT_FOUND_PRODUCT(400, "P001", "상품을 찾을 수 없습니다."),
// Cart
NOT_FOUND_CART(400, "C001","장바구니가 비어있습니다."),
ACCESS_DENIED_MEMBER(400,"C002", "접근할 수 없는 장바구니 입니다."),
;
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;
}
public String getMessage() {
return this.message;
}
public String getCode() {
return code;
}
public int getStatus() {
return status;
}
}
이거 받아서 넘겨줄 Error response 설정..
package com.sparta.shopapi.global.handler;
import com.sparta.shopapi.global.handler.exception.ErrorCode;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.validation.BindingResult;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ErrorResponse {
private String message;
private int status;
private List<FieldError> errors;
private String code;
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 ErrorResponse(final ErrorCode code, String message, int value) {
this.message = message;
this.status = value;
this.code = code.getCode();
}
public static ErrorResponse of(final ErrorCode code, String message, int value) {
return new ErrorResponse(code, message, value);
}
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<FieldError> errors = FieldError.of(e.getName(), value, e.getErrorCode());
return new ErrorResponse(ErrorCode.INVALID_TYPE_VALUE, 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());
}
}
}
그리고 이거 전부 처리해주고 다른 exception들도 예쁘게 같은 형식으로 담아줄
대망의 globalexceptionhandler...
package com.sparta.shopapi.global.handler;
import com.sparta.shopapi.global.handler.exception.BusinessException;
import com.sparta.shopapi.global.handler.exception.ErrorCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import java.nio.file.AccessDeniedException;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// Valid
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error("handleMethodArgumentNotValidException", e);
final ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
// enum type error
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
log.error("handleMethodArgumentTypeMismatchException", e);
final ErrorResponse response = ErrorResponse.of(e);
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
protected ResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
log.error("handleHttpRequestMethodNotSupportedException", e);
final ErrorResponse response = ErrorResponse.of(ErrorCode.METHOD_NOT_ALLOWED);
return new ResponseEntity<>(response, HttpStatus.valueOf(ErrorCode.METHOD_NOT_ALLOWED.getStatus()));
}
@ExceptionHandler(AccessDeniedException.class)
protected ResponseEntity<ErrorResponse> handleAccessDeniedException(AccessDeniedException e) {
log.error("handleAccessDeniedException", e);
final ErrorResponse response = ErrorResponse.of(ErrorCode.HANDLE_ACCESS_DENIED);
return new ResponseEntity<>(response, HttpStatus.valueOf(ErrorCode.HANDLE_ACCESS_DENIED.getStatus()));
}
@ExceptionHandler(BusinessException.class)
protected ResponseEntity<ErrorResponse> handleBusinessException(final BusinessException e) {
log.error("handleBusinessException", e);
final ErrorCode errorCode = e.getErrorCode();
final ErrorResponse response = ErrorResponse.of(errorCode);
return new ResponseEntity<>(response, HttpStatus.valueOf(errorCode.getStatus()));
}
@ExceptionHandler(Exception.class)
protected ResponseEntity<ErrorResponse> handleException(Exception e) {
log.error("handleEntityNotFoundException", e);
final ErrorResponse response = ErrorResponse.of(ErrorCode.INTERNAL_SERVER_ERROR);
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
짜잔...~!
이런 예쁜 에러코드 완성~~..!
이건 왜 형식이 다르냐면~~?
403은 Spring Security 문제라서 따로 만들어 주었기 때문인데요.. 그.. 음... 같은 형식으로 담아서 보낼 걸 그랬습니다?
Valid도 잘 잡아주네..!
문제 없다!!