Skip to content

Commit

Permalink
Merge pull request #9 from mju-likelion/feature/exception-#8
Browse files Browse the repository at this point in the history
Feature/#8 전역 예외처리 적용
  • Loading branch information
Dh3356 authored Feb 22, 2024
2 parents 8b6c170 + 729c82d commit c926445
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 0 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ dependencies {
runtimeOnly 'com.mysql:mysql-connector-j'//MySQL
compileOnly 'org.projectlombok:lombok'//Lombok
annotationProcessor 'org.projectlombok:lombok'//Lombok
implementation 'org.springframework.boot:spring-boot-starter-validation'//Validation(Dto)
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package org.mjulikelion.baker.controller;

import lombok.extern.slf4j.Slf4j;
import org.mjulikelion.baker.dto.response.ResponseDto;
import org.mjulikelion.baker.errorcode.ErrorCode;
import org.mjulikelion.baker.errorcode.ValidationErrorCode;
import org.mjulikelion.baker.exception.InvalidDataException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.FieldError;
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.HandlerMethodValidationException;
import org.springframework.web.servlet.resource.NoResourceFoundException;

@Slf4j
@RestControllerAdvice
public class ExceptionController {
//MethodArgumentNotValidException 예외를 처리하는 핸들러(Body(dto)의 Validation에 실패한 경우)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ResponseDto<Void>> handleMethodArgumentNotValidException(
MethodArgumentNotValidException methodArgumentNotValidException) {
log.error("MethodArgumentNotValidException: {}", methodArgumentNotValidException.getMessage());
FieldError fieldError = methodArgumentNotValidException.getBindingResult().getFieldError();
if (fieldError == null) {
return new ResponseEntity<>(
ResponseDto.res(HttpStatus.BAD_REQUEST.toString(), methodArgumentNotValidException.getMessage()),
HttpStatus.BAD_REQUEST);
}
ValidationErrorCode validationErrorCode = ValidationErrorCode.resolveAnnotation(fieldError.getCode());
String code = validationErrorCode.getCode();
String message = validationErrorCode.getMessage() + " : " + fieldError.getDefaultMessage();
return new ResponseEntity<>(ResponseDto.res(code, message), HttpStatus.BAD_REQUEST);
}

//HandlerMethodValidationException 예외를 처리하는 핸들러(HandlerMethod의 Validation에 실패한 경우(controller의 메소드의 파라미터에 대한 Validation))
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(HandlerMethodValidationException.class)
public ResponseEntity<ResponseDto<Void>> handleHandlerMethodValidationException(
HandlerMethodValidationException handlerMethodValidationException) {
log.error("HandlerMethodValidationException: {}", handlerMethodValidationException.getMessage());
ValidationErrorCode errorCode = ValidationErrorCode.VALIDATION;
String code = errorCode.getCode();
String message = errorCode.getMessage() + " : "
+ handlerMethodValidationException.getDetailMessageArguments()[0].toString();
return new ResponseEntity<>(ResponseDto.res(code, message), HttpStatus.BAD_REQUEST);
}

//HttpMessageNotReadableException 예외를 처리하는 핸들러(Body가 잘못된 경우(json 형식이 잘못된 경우))
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ResponseDto<Void>> handleHttpMessageNotReadableException(
HttpMessageNotReadableException httpMessageNotReadableException) {
log.error("HttpMessageNotReadableException: {}", httpMessageNotReadableException.getMessage());
ErrorCode errorCode = ErrorCode.INVALID_REQUEST_FORMAT_ERROR;
String code = errorCode.getCode();
String message = errorCode.getMessage() + " : " + httpMessageNotReadableException.getMessage();
return new ResponseEntity<>(ResponseDto.res(code, message), HttpStatus.BAD_REQUEST);
}

//HttpRequestMethodNotSupportedException 예외를 처리하는 핸들러(요청의 메소드가 잘못된 경우)
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<ResponseDto<Void>> handleHttpRequestMethodNotSupportedException(
HttpRequestMethodNotSupportedException httpRequestMethodNotSupportedException) {
log.error("HttpRequestMethodNotSupportedException: {}", httpRequestMethodNotSupportedException.getMessage());
ErrorCode errorCode = ErrorCode.METHOD_NOT_ALLOWED_ERROR;
String code = errorCode.getCode();
String message = errorCode.getMessage();
return new ResponseEntity<>(ResponseDto.res(code, message), HttpStatus.METHOD_NOT_ALLOWED);
}

//NoResourceFoundException 예외를 처리하는 핸들러(리소스를 찾을 수 없는 경우(URI가 잘못된 경우))
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(NoResourceFoundException.class)
public ResponseEntity<ResponseDto<Void>> handleNoResourceFoundException(
NoResourceFoundException noResourceFoundException) {
log.error("NoResourceFoundException: {}", noResourceFoundException.getMessage());
ErrorCode errorCode = ErrorCode.NO_RESOURCE_ERROR;
String code = errorCode.getCode();
String message = errorCode.getMessage();
return new ResponseEntity<>(ResponseDto.res(code, message), HttpStatus.NOT_FOUND);
}

//Exception 예외를 처리하는 핸들러(해당 클래스에 정의되지 않은 예외를 처리하는 핸들러)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
public ResponseEntity<ResponseDto<Void>> handleException(Exception exception) {
log.error("InternalServerException: {}", exception.getMessage());
ErrorCode errorCode = ErrorCode.INTERNAL_SERVER_ERROR;
String code = errorCode.getCode();
String message = errorCode.getMessage() + " : " + exception.getClass();
return new ResponseEntity<>(ResponseDto.res(code, message), HttpStatus.INTERNAL_SERVER_ERROR);
}

// InvalidDataException 예외를 처리하는 핸들러(유효하지 않은 데이터가 요청된 경우)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(InvalidDataException.class)
public ResponseEntity<ResponseDto<Void>> handleInvalidDataException(InvalidDataException invalidDataException) {
log.error("InvalidDataException: {}", invalidDataException.getMessage());
ErrorCode errorCode = invalidDataException.getErrorCode();
String code = errorCode.getCode();
String message = errorCode.getMessage();
return new ResponseEntity<>(ResponseDto.res(code, message), HttpStatus.BAD_REQUEST);
}
}
49 changes: 49 additions & 0 deletions src/main/java/org/mjulikelion/baker/dto/response/ResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.mjulikelion.baker.dto.response;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.springframework.http.HttpStatusCode;

@Data
@Builder
@AllArgsConstructor
public class ResponseDto<T> {
@JsonProperty
private final String statusCode;
@JsonProperty
private final String message;
@JsonProperty
private final T data;

public ResponseDto(final String statusCode, final String resultMsg) {
this.statusCode = statusCode;
this.message = resultMsg;
this.data = null;
}

public static <T> ResponseDto<T> res(final String statusCode, final String resultMsg) {
return res(statusCode, resultMsg, null);
}

public static <T> ResponseDto<T> res(final String statusCode, final String resultMsg, final T data) {
return ResponseDto.<T>builder()
.data(data)
.statusCode(statusCode)
.message(resultMsg)
.build();
}

public static <T> ResponseDto<T> res(final HttpStatusCode statusCode, final String resultMsg) {
return res(statusCode, resultMsg, null);
}

public static <T> ResponseDto<T> res(final HttpStatusCode statusCode, final String resultMsg, final T data) {
return ResponseDto.<T>builder()
.data(data)
.statusCode(String.valueOf(statusCode.value()))
.message(resultMsg)
.build();
}
}
22 changes: 22 additions & 0 deletions src/main/java/org/mjulikelion/baker/errorcode/ErrorCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.mjulikelion.baker.errorcode;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum ErrorCode {
//잘못된 요청 오류들
PAGE_OUT_OF_RANGE("4000", "페이지가 범위를 벗어났습니다."),
//서버 내부 오류들
INTERNAL_SERVER_ERROR("5000", "알 수 없는 서버 내부 오류."),//서버 내부 오류
//잘못된 경로, 메소드 오류들
METHOD_NOT_ALLOWED_ERROR("4050", "허용되지 않은 메소드 입니다."),//허용되지 않은 메소드
NO_RESOURCE_ERROR("4040", "해당 리소스를 찾을 수 없습니다."),//리소스를 찾을 수 없음(잘못된 URI)
//잘못된 데이터 오류들
INVALID_REQUEST_FORMAT_ERROR("4000", "유효하지 않은 Body 형식입니다.");//요청 형식이 잘못됨


private final String code;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.mjulikelion.baker.errorcode;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum ValidationErrorCode {
VALIDATION("9000", "유효성 검사 실패."),
NOT_NULL("9001", "필수값이 누락되었습니다."),
NOT_BLANK("9002", "필수값이 빈 값이거나 공백으로 되어있습니다."),
REGEX("9003", "형식에 맞지 않습니다."),
ENUM_CONSTRAINT("9004", "유효하지 않은 Enum 값입니다."),
GRADE_CONSTRAINT("9005", "학년이 형식에 맞지 않습니다.");


private final String code;
private final String message;

//Dto의 어노테이션을 통해 발생한 에러코드를 반환
public static ValidationErrorCode resolveAnnotation(String code) {
return switch (code) {
case "NotNull" -> NOT_NULL;
case "NotBlank" -> NOT_BLANK;
case "Pattern" -> REGEX;
case "EnumConstraint" -> ENUM_CONSTRAINT;
case "GradeConstraint" -> GRADE_CONSTRAINT;
default -> throw new IllegalArgumentException("Unexpected value: " + code);
};
}
}

18 changes: 18 additions & 0 deletions src/main/java/org/mjulikelion/baker/exception/CustomException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.mjulikelion.baker.exception;

import lombok.Getter;
import org.mjulikelion.baker.errorcode.ErrorCode;

@Getter
public class CustomException extends RuntimeException {
protected ErrorCode errorCode;

public CustomException(ErrorCode errorCode) {
this.errorCode = errorCode;
}

public CustomException(ErrorCode errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.mjulikelion.baker.exception;

import lombok.Getter;
import org.mjulikelion.baker.errorcode.ErrorCode;

@Getter
public class InvalidDataException extends CustomException {
public InvalidDataException(ErrorCode errorCode) {
super(errorCode);
}
}

0 comments on commit c926445

Please sign in to comment.