π«
μΌμ κ΄λ¦¬ APIλ₯Ό ꡬννλ©° κ²ͺμ λ¬Έμ μ κ³Ό ν΄κ²°λ°©λ², μλ‘ μκ²λ μ μ κΈ°λ‘ν©λλ€.
μ΄λ Έν μ΄μ νμ Controller κ³μΈ΅μμ λ°μνλ μλ¬λ₯Ό μ‘μμ λ©μλλ‘ μ²λ¦¬ν΄μ£Όλ κΈ°λ₯μ΄λ€.
import java.io.IOException;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
@Controller
public class SimpleController {
@ExceptionHandler(IOException.class)
public ResponseEntity<String> handle() {
return ResponseEntity.internalServerError().body("Could not read file storage");
}
}
λ©μλμ μΈμλ‘ Exception μ λ°κ³ νΉμ Exception μ μ€μ νμ¬ μ²λ¦¬ν μ μλ€.
@Controller μ μ¬μ©νλ λͺ¨λ ν΄λμ€μμ λ°μνλ μλ¬λ€μ μ‘μμ€λ€.
μΌλΆ μλ¬λ§ μ²λ¦¬νκ³ μΆμ κ²½μ° λ°λ‘ μ€μ νλ€.
// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}
@ControllerAdvice μ @ExceptionHandler λ₯Ό ν¨κ» μ¬μ©νλ©΄ μ 체 Controller μμ λ°μνλ μλ¬λ₯Ό μ‘μ λ©μλλ‘ μ²λ¦¬ν μ μλ€.
@ControllerAdvice + @ResponseBody μ κ°μ§κ³ μλ€.
@RestControllerAdvice μ λ©μλλ‘ HTTP Message Bodyμ λ¬Έμμ΄μ 리ν΄ν μ μλ€.
μλ²μμ λ°μ κ°λ₯ν μμΈμ‘°κ±΄μ μ 리νμ¬ Enum ν΄λμ€λ‘ μ μ
@AllArgsConstructor
@Getter
public enum ErrorCode {
INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "001_INVALID_PARAMETER", "νλΌλ―Έν° κ°μ νμΈν΄μ£ΌμΈμ."),
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "002_USER_NOT_FOUND", "μ‘΄μ¬νμ§ μλ μ μ ID μ
λλ€."),
SCHEDULE_NOT_FOUND(HttpStatus.NOT_FOUND, "003_SCHEDULE_NOT_FOUND", "μ‘΄μ¬νμ§ μλ μΌμ ID μ
λλ€."),
PASSWORD_INCORRECT(HttpStatus.BAD_REQUEST, "004_PASSWORD_INCORRECT", "λΉλ°λ²νΈκ° μΌμΉνμ§ μμ΅λλ€."),
ENTITY_DELETED(HttpStatus.BAD_REQUEST, "005_ENTITY_DELETED", "μ΄λ―Έ μμ λ μ 보μ
λλ€."),
UNKNOWN(HttpStatus.INTERNAL_SERVER_ERROR, "999_UNKNOWN", "μ μ μλ μ€λ₯κ° λ°μνμ΅λλ€.");
private final HttpStatus status;
private final String code;
private final String message;
}
status: APIλ‘ λ°νλ HTTP μν μ½λcode: ν΄λΌμ΄μΈνΈ κ°λ°μκ° μλ¬ μν©μ μ°Έμ‘°ν μ μλ μλ² μλ¬ μ½λmessage: μ λ¬λ°μ μλ¬ μ½λμ λν μ€λͺ
μμΈλ₯Ό μ λ¬ν λ, μ μν ErrorCodeλ₯Ό μ΄μ©ν΄ μ 보λ₯Ό μ λ¬νκΈ° μν μ¬μ©μ μμΈ ν΄λμ€λ₯Ό μμ±
@Getter
@Slf4j
public class CustomException extends RuntimeException {
private final HttpStatus status;
private final ErrorCode errorCode;
private final String detail;
public CustomException(ErrorCode errorCode) {
this.status = errorCode.getStatus();
this.errorCode = errorCode;
this.detail = "";
}
public CustomException(ErrorCode errorCode, String detail) {
this.status = errorCode.getStatus();
this.errorCode = errorCode;
this.detail = detail;
}
public CustomException(Exception exception) {
if (exception.getClass() == CustomException.class) {
CustomException customException = (CustomException) exception;
this.status = customException.getStatus();
this.errorCode = customException.getErrorCode();
this.detail = customException.getDetail();
} else {
this.status = HttpStatus.INTERNAL_SERVER_ERROR;
this.errorCode = ErrorCode.UNKNOWN;
this.detail = "μ μ μλ μ€λ₯ λ°μ, κ°λ°νμ λ¬Έμ λ°λλλ€.";
log.error(exception.getMessage(), exception);
}
}
}
μ¬μ©μ μμΈ ν΄λμ€μμ μ μλμ§ μμ μμΈλ κ°λ°μκ° μμνμ§ λͺ»ν μ€λ₯λ‘ μμΈ μμΈμ μΆμ νκ³ ν΄κ²°ν μ μλλ‘ λ‘κ·Έλ₯Ό λ¨κΈ΄λ€. API μμ μΈ‘μμλ ꡬ체μ μΈ Exception μ μ μ μλλ‘ μλ¬λ©μμ§λ₯Ό λ³ννμ¬ μλ΅ λ©μμ§μ 보λΈλ€.
@ExceptionHandler κ³΅ν΅ μμΈ μ²λ¦¬ ꡬνResponse(μλ΅) λ°μ΄ν°λ‘ μμΈ λ©μΈμ§λ₯Ό λ³΄λΌ μ μλλ‘ ResponseEntityλ₯Ό λ°ννλ DTO μμ±
@Data
@Builder
public class ErrorResponseDto {
private String code;
private String message;
private String detail;
public static ResponseEntity<ErrorResponseDto> errResponseEntity(CustomException e) {
ErrorCode errorCode = e.getErrorCode();
String detail = e.getDetail();
return ResponseEntity.status(e.getStatus())
.body(ErrorResponseDto.builder()
.code(errorCode.getCode())
.message(errorCode.getMessage())
.detail(detail)
.build());
}
}
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
private ResponseEntity<ErrorResponseDto> handleException(Exception e) {
return ErrorResponseDto.errResponseEntity(new CustomException(e));
}
}
μ΄λ, λͺ¨λ ν΄λμ€μμ κ³΅ν΅ μμΈ μ²λ¦¬κ° κ°λ₯νλλ‘ @RestControllerAdvice κ° μ μ©λ ν΄λμ€λ₯Ό μμ±νμ¬ μ²λ¦¬ν μ μλ€.
μμ) λ°μ΄ν°μ μμ , μμ μ μ
λ ₯ν λΉλ°λ²νΈμ κΈ°μ‘΄ λΉλ°λ²νΈκ° μΌμΉνμ§ μλ κ²½μ°

Status: 400 Bad RequestResponse Body: ErrorResponseDtoμ μ μλ μλ¬μ½λ, μλ¬λ©μΈμ§λ₯Ό JSON νμμΌλ‘ λ°νπ μ°Έκ³ μλ£
[μ€νλ§λΆνΈ] @ExceptionHandlerλ₯Ό ν΅ν μμΈμ²λ¦¬
[Springboot] Custom exception μ€μ νκΈ°

μλ² λ‘κ·Έμ λ€μκ³Ό κ°μ μμΈκ° νμΈλλ€.
Caused by: java.lang.NoSuchMethodError: 'void org.springframework.web.method.ControllerAdviceBean.<init>(java.lang.Object)'
How to use both @RestControllerAdvice and Swagger UI in Spring boot
μμ λ΅λ³μ μ°Έκ³ νμ¬ @RestControllerAdvice μ΄λ
Έν
μ΄μ
μ μ¬μ©ν 컨νΈλ‘€λ¬μ @Hidden(io.swagger.v3.oas.annotations.Hidden) μ μΆκ°νμ¬ Swagger API λ¬Έμ μμ± μ Fetch error λ₯Ό λ°©μ§νμλ€.
Swagger λΌμ΄λΈλ¬λ¦¬μμ API λ¬Έμν μ νΉμ API μλν¬μΈνΈλ λͺ¨λΈμ λ¬Έμνμμ μ μΈνκ³ μΆμ λ μ¬μ©νλ€. μ΄ μ΄λ Έν μ΄μ μ μ¬μ©νλ©΄ ν΄λΉ API λ©μλλ λͺ¨λΈμ΄ Swagger UIλ μλ μμ±λ API λ¬Έμμμ λνλμ§ μκ² λλ€.
μμ§ μꡬμ¬νμ μ½κ³ μ νν μ¬μμ λ½μλ΄λκ² μ΄λ €μ μꡬμ¬ν ν΄μμ μλͺ»νμλ€. λ€νν μ½λλ₯Ό λ§μ΄ μμ νμ§λ μμμ§λ§ μ€λ¬΄μμ μ΄λ° μν©μ΄ μκ²Όλ€κ³ μμνλ μμ°νλ€.π¨ μκ°μ ν¨μ¨μ μΌλ‘ μ°λ €λ©΄ μμ κΈ°μ΄ μ€κ³λΆν° ννν΄μΌνλ€λ κ²μ λ€μ κΉ¨λ¬μλ€. μ½λ μμ±μ λ§μΉ λ€ κΌΌκΌΌν λΆμνκ³ μ¬λ¬ λ² κ²ν νμ¬ μ΅λν μμ±λλ₯Ό λμ¬μΌκ² λ€.