[TIL] 2025-01-27_@ExceptionHandler

YuriΒ·2025λ…„ 1μ›” 27일

TIL

λͺ©λ‘ 보기
38/59

πŸ”« 일정관리 API λ₯Ό κ΅¬ν˜„ν•˜λ©° κ²ͺ은 문제점과 해결방법, μƒˆλ‘œ μ•Œκ²Œλœ 점을 κΈ°λ‘ν•©λ‹ˆλ‹€.

@ExceptionHandler

μ–΄λ…Έν…Œμ΄μ…˜ ν•˜μœ„ 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 을 μ„€μ •ν•˜μ—¬ μ²˜λ¦¬ν•  수 μžˆλ‹€.

@ControllerAdvice

@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 {}
  1. μ–΄λ…Έν…Œμ΄μ…˜: νŠΉμ • μ–΄λ…Έν…Œμ΄μ…˜ μ§€μ •
  2. Base Package (Base Classes): 탐색 νŒ¨ν‚€μ§€ μ§€μ •
  3. assignableTypes: νŠΉμ • 클래슀 μ§€μ •

@ControllerAdvice 와 @ExceptionHandler λ₯Ό ν•¨κ»˜ μ‚¬μš©ν•˜λ©΄ 전체 Controller μ—μ„œ λ°œμƒν•˜λŠ” μ—λŸ¬λ₯Ό μž‘μ•„ λ©”μ„œλ“œλ‘œ μ²˜λ¦¬ν•  수 μžˆλ‹€.

@RestControllerAdvice

@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 κ°€ 적용된 클래슀λ₯Ό μƒμ„±ν•˜μ—¬ μ²˜λ¦¬ν•  수 μžˆλ‹€.

API μ‹€ν–‰ 및 κ²°κ³Ό 확인 (μ‚¬μš©μž λ©”μ‹œμ§€ 전달 확인)

μ˜ˆμ‹œ) λ°μ΄ν„°μ˜ μˆ˜μ •, μ‚­μ œ μ‹œ μž…λ ₯ν•œ λΉ„λ°€λ²ˆν˜Έμ™€ κΈ°μ‘΄ λΉ„λ°€λ²ˆν˜Έκ°€ μΌμΉ˜ν•˜μ§€ μ•ŠλŠ” 경우

  • Status: 400 Bad Request
  • Response Body: ErrorResponseDto에 μ •μ˜λœ μ—λŸ¬μ½”λ“œ, μ—λŸ¬λ©”μ„Έμ§€λ₯Ό JSON ν˜•μ‹μœΌλ‘œ λ°˜ν™˜

πŸ”— 참고자료
[μŠ€ν”„λ§λΆ€νŠΈ] @ExceptionHandlerλ₯Ό ν†΅ν•œ μ˜ˆμ™Έμ²˜λ¦¬
[Springboot] Custom exception μ„€μ •ν•˜κΈ°

βž•) Swagger 적용 μ‹œ @RestControllerAdvice NoSuchMethodError


μ„œλ²„ λ‘œκ·Έμ— λ‹€μŒκ³Ό 같은 μ˜ˆμ™Έκ°€ ν™•μΈλœλ‹€.

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 λ₯Ό λ°©μ§€ν•˜μ˜€λ‹€.

@Hidden

Swagger λΌμ΄λΈŒλŸ¬λ¦¬μ—μ„œ API λ¬Έμ„œν™” μ‹œ νŠΉμ • API μ—”λ“œν¬μΈνŠΈλ‚˜ λͺ¨λΈμ„ λ¬Έμ„œν™”μ—μ„œ μ œμ™Έν•˜κ³  싢을 λ•Œ μ‚¬μš©ν•œλ‹€. 이 μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜λ©΄ ν•΄λ‹Ή API λ©”μ„œλ“œλ‚˜ λͺ¨λΈμ΄ Swagger UIλ‚˜ μžλ™ μƒμ„±λœ API λ¬Έμ„œμ—μ„œ λ‚˜νƒ€λ‚˜μ§€ μ•Šκ²Œ λœλ‹€.


πŸ“ TODO

  1. 도전 과제 Lv6 κ΅¬ν˜„
  2. ν”„λ‘œμ νŠΈ ν†΅ν•©ν…ŒμŠ€νŠΈ
  3. Swagger μ μš©ν•˜κΈ° 🌳

πŸ’­ Diary

아직 μš”κ΅¬μ‚¬ν•­μ„ 읽고 μ •ν™•ν•œ 사양을 λ½‘μ•„λ‚΄λŠ”κ²Œ μ–΄λ €μ›Œ μš”κ΅¬μ‚¬ν•­ 해석을 잘λͺ»ν•˜μ˜€λ‹€. λ‹€ν–‰νžˆ μ½”λ“œλ₯Ό 많이 μˆ˜μ •ν•˜μ§€λŠ” μ•Šμ•˜μ§€λ§Œ μ‹€λ¬΄μ—μ„œ 이런 상황이 생겼닀고 μƒμƒν•˜λ‹ˆ μ•„μ°”ν–ˆλ‹€.😨 μ‹œκ°„μ„ 효율적으둜 μ“°λ €λ©΄ μ—­μ‹œ 기초 섀계뢀터 νƒ„νƒ„ν•΄μ•Όν•œλ‹€λŠ” 것을 λ‹€μ‹œ κΉ¨λ‹¬μ•˜λ‹€. μ½”λ“œ μž‘μ„±μ„ 마친 λ’€ 꼼꼼히 λΆ„μ„ν•˜κ³  μ—¬λŸ¬ 번 κ²€ν† ν•˜μ—¬ μ΅œλŒ€ν•œ 완성도λ₯Ό λ†’μ—¬μ•Όκ² λ‹€.

profile
μ•ˆλ…•ν•˜μ„Έμš” :)

0개의 λŒ“κΈ€