티스토리 뷰

0. BasicErrorController

:: Spring 기본 에러 처리 (페이지 기반)
:: 적절한 ExceptionResolver가 없으면 해당 컨트롤러를 통해 기본적인 에러 페이지를 제공

:: 에러 발생 시, 기본적으로 /error로 에러 요청을 다시 전달 - WebMvcAutoConfiguration를 통한 기본 설정

WAS(톰캣) -> 필터 -> 서블릿(디스패처 서블릿) -> 인터셉터 -> 컨트롤러
컨트롤러(예외발생) -> 인터셉터 -> 서블릿(디스패처 서블릿) -> 필터 -> WAS(톰캣)
WAS(톰캣) -> 필터 -> 서블릿(디스패처 서블릿) -> 인터셉터 -> 컨트롤러(BasicErrorController)
  •  예외 처리는 위 설명한 순서대로 ExceptionResolver 들을 순회하며 처리하고
    • 적한 ExceptionResolver 가 없으면 마지막으로는 예외가 서블릿까지 전달
    • 서블릿은 SpringBoot 가 진행한 자동 설정에 맞게 BasicErrorController 로 요청을 다시 전달

 

1. 스프링이 제공하는 예외 처리 방법들

:: try-catch를  모든 코드에 붙이는 것은 비효율적

:: 예외 처리를 위한 try-catch 사용하지 않고, 공통 관심사(cross-cutting concerns)를 메인 로직에서 분리

:: 이를 적용한 HandlerExceptionResolver를 만듦

 

1.0 HandlerExceptionResolver의 개념

:: 예외 처리 전략을 추상화한 인터페이스 (전략 패턴 Strategy)
:: 대부분의 경우 Exception을 catch하고 HTTP 상태나 응답 메세지 등을 설정
:: WAS 입장에서는 요청에 대한 정상적인 응답으로 인식

 

HandlerExceptionResolver 구현체 종류 역할
1) DefaultErrorAttributes - 예외 속성(statuts, message)등 저장
- 직접 예외를 처리는 X
2) ExceptionHandlerExceptionResolver - @ ExceptionHandler를 사용해 예외처리 전략 제공
- 특정 예외를 처리하기 위한 메서드를 지정하여 해당 예외가 발생했을 때, 이 메서드로 예외를 전달
3) ResponseStatusExceptionResolver - @ResponseStatus 또는 ResponseStatusException을 활용해 특정 HTTP 상태 코드와 함께 예외를 처리
- 예외 클래스에 @ResponseStatus 어노테이션을 사용하면, 해당 예외 발생 시 자동으로 특정 HTTP 상태 코드와 메시지를 반환
4) DefaultHandlerExceptionResolver - Spring 내부에서 발생하는 예외들, 예를 들어 NullPointerException, TypeMismatchException 등의 기본적인 예외들을 처리
- Spring에서 미리 정의된 예외에 대해 자동으로 처리해주는 기본 처리기
  • HandlerExceptionResolverComposite
    :: 직접 예외를 처리하지 않는 DefaultErrorAttributes를 제외한 2,3,4들을 모아서 관리

2. ExceptionResolver의 예외 처리 방법

2. 1 @ResponseStatus

:: Spring에서 예외 발생 시, 해당 예외에 대한 HTTP Status Code 설정 가능

:: 예외 발생 시, 자동으로 HTTP Response Code와 메세지 설정

단점 1. 응답 내용(Payload) 수정 불가능
2. 예외 클래스 단위로 상태를 정의하여, 예외 클래스 내 세부적으로 HTTP 상태 분기 불가능
3. 같은 예외 클래스 내 별도의 응답 상태가 필요하다면 예외 클래스를 그 수에 맞춰 추가 
  • 적용가능한 경우
    - Exception 클래스 자체
    - 메서드에 @ExceptionHandler와 함께
    - 클래스에 @RestControllerAdvice와 함께

- EX) value를 통해 해당 응답 코드를 지정 가능

@ResponseStatus(value = HttpStatus.NOT_FOUND) // ResourceNotFoundException 발생 시, HTTP 404 반환
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

2. 2 @ResponseStatusException

::외부 라이브러리에서 정의한 코드에 수정 X이기 때문 @ResponseStatus 적용 X

:: @ResponseStatus의 대체로 spring 5부터 사용가능

:: 예외 발생과 동시에 예외에 해당하는 에러 HTTP 상태를 동시에 정의 

 

  • ResponseStatusException은 HttpStatus와 메시지를 생성자에 전달하여 예외를 던짐
  • @ResponseStatus와는 달리 동적으로 예외 메시지나 상태 코드를 설정

- EX)

 throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Item Not Found");
@GetMapping("/product/{id}")
public ResponseEntity<Product> getProduct(@PathVariable String id) {
    try {
        return ResponseEntity.ok(productService.getProduct(id));
    } catch (NoSuchElementFoundException e) {
        throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Item Not Found");
    }
}
// 출처: https://mangkyu.tistory.com/204 [MangKyu's Diary:티스토리]

2. 3 @ExceptionHandler

:: 전체 방법론 중 가장 유연한 예외처리 방식

  • 적용가능한 경우
    - 컨트롤러의 메소드
    - @ControllerAdvice나 @RestControllerAdvice가 있는 클래스의 메소드

- EX)

@Controller
public class MyController {

    @ExceptionHandler(ResourceNotFoundException.class) // 해당 예외 발생 시
    public String handleResourceNotFound(ResourceNotFoundException ex) {
        // 예외 처리 로직
        return "errorPage";
    }
}
@RestController
@RequiredArgsConstructor
public class ProductController {

  private final ProductService productService;
  
  @GetMapping("/product/{id}")
  public Response getProduct(@PathVariable String id){
    return productService.getProduct(id);
  }

  @ExceptionHandler(NoSuchElementFoundException.class)
  public ResponseEntity<String> handleNoSuchElementFoundException(NoSuchElementFoundException exception) {
    return ResponseEntity.status(HttpStatus.NOT_FOUND).body(exception.getMessage());
  }
}
// 출처: https://mangkyu.tistory.com/204 [MangKyu's Diary:티스토리]

2. 4 @ControllerAdvice, @RestControllerAdvice

:: 전역 예외처리를 담당하는 클래스에 사용하는 어노테이션

:: 애플리케이션 내 모든 컨트롤러에서 발생하는 예외를 하나의 클래스로 처리 가능

- @RestControllerAdvice == RESTful API에서 주로 사용(JSON 등의 값)

 

- EX)

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<String> handleResourceNotFound(ResourceNotFoundException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleGeneralException(Exception ex) {
        return new ResponseEntity<>("An unexpected error occurred", HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
@RestControllerAdvice
public class GlobalRestExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<Map<String, String>> handleResourceNotFound(ResourceNotFoundException ex) {
        Map<String, String> response = new HashMap<>();
        response.put("error", ex.getMessage());
        return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<Map<String, String>> handleGeneralException(Exception ex) {
        Map<String, String> response = new HashMap<>();
        response.put("error", "An unexpected error occurred");
        return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

3. Custom Exception

:: @Controller 에서 Exception 처리 시, 다양한 Exception 에 대해 대응하기 위해 Exception 다중화

  1. 클래스 단위의 Exception 다중화 = RuntimeException 상속
  2. 필드 단위의 Exception 다중화 = Enum 을 통한 자체 예외 케이스 관리

3.1 클래스 단위 Exception 다중화

:: RuntimeException 상속

- @Controller에서 Exception 처리 X시, 500 표준 오류 반환

- @Controller에서 Exception 처리 O시(try-catch), 반환 타입은 ResponeEntity

// 사용자 정의 예외 클래스 1
public class InvalidInputException extends RuntimeException {
    public InvalidInputException(String message) {
        super(message);
    }
}

// 사용자 정의 예외 클래스 2
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

// ...

:: But, 상황에 맞게 예외를 나타내야 함 -> 무지성적인 RuntimeException은 XX

:: try-catch 적용 시, 미쳐 생각 못한 예외를 잡기 위해

} catch(Exception e){
	// ...
}

를 해줘야 함


3.2 필드 단위의 Exception 다중화

:: Enum을 통한 자체 예외 케이스 관리

:: RuntimeException 예외에 타입 필드를 추가하여 예외 객체로 다양한 예외를 표현

 

- RuntimeException 예외 내 타입 필드발생지에서 에러 유형에 따라 타입 필드를 명시하고

throw new CustomException("INVALID_REQUEST", "아이디는 10을 넘을 수 없습니다 - id : " + id);

 

- 처리지에서 타입 필드를 읽어 에러 유형에 따른 처리 수행

try {
    UserResponseDto response = userService.retrieve(id);
    return ResponseEntity.ok(response);
} catch (CustomException e) {
    if (e.getType().equals("INVALID_REQUEST")) {
        log.warn(e.getMessage());
        e.printStackTrace();
        return ResponseEntity.badRequest().build();
    } else if (e.getType().equals("NOT_EXIST")) {
        log.warn(e.getMessage());
        e.printStackTrace();
        return ResponseEntity.notFound().build();
    } else {
        log.error(e.getMessage());
        e.printStackTrace();
        return ResponseEntity.internalServerError().build();
    }
}

4. Loggin

- Exception 처리에서 중요한것은 해당 예외상황(문제, 이슈) 해결을 위해 힌트를 제대로 남겨야함

  • Where? 어디서 발생했는지 :: e.**printStackTrace**();
  • Why? 왜 발생했는지(오류 메세지) :: log.warn(e.**getMessage**());

 

4.1 SLF4J (Simple Logger Facade for Java)

:: Java에서 로깅을 위한 추상화된 인터페이스를 제공하는 라이브러리

:: Logback, Log4j 와 같은 Logging Framework 들에 대한 Facade(추상체) 제공

:: Logging Framework 변경에도 아무런 문제없이 로깅 수행

 

-> lombok의 내장 slf4j를 사용하기 때문에 lombok의존성만 있으면 ㅇㅋ

 

Ex) @Slf4j 애너테이션 사용

import lombok.extern.slf4j.Slf4j;

@Slf4j // @Slf4j 애너테이션을 사용하여 Logger 자동 생성
public class MyService {

    public void performTask(String input) {
        log.trace("Trace level log: {}", input);
        log.debug("Debug level log: {}", input);
        log.info("Informational log: {}", input);
        log.warn("Warning log: {}", input);
        log.error("Error log: {}", input);
    }
}

 

 

Ex) @Slf4j 로깅 사용 예시

@Slf4j
public class MyService {

    public void processOrder(String orderId) {
        log.info("Processing order with ID: {}", orderId);
        
        try {
            // 일부 비즈니스 로직 수행
            int result = 10 / 0; // 예외 발생
        } catch (Exception e) {
            log.error("An error occurred while processing the order", e);
        }
    }
}

 

 


참고

ASAC 수업자료

 

https://mangkyu.tistory.com/204

 

[Spring] 스프링의 다양한 예외 처리 방법(ExceptionHandler, ControllerAdvice 등) 완벽하게 이해하기 - (1/2)

예외 처리는 애플리케이션을 만드는데 매우 중요한 부분을 차지한다. Spring 프레임워크는 매우 다양한 에러 처리 방법을 제공하는데, 어떠한 방법들이 있고 가장 좋은 방법(Best Practice)은 무엇인

mangkyu.tistory.com

 

https://thkim610.tistory.com/133#%40ExceptionHandler-1

 

ExceptionResolver

스프링에서는 어노테이션을 기반으로 ExceptionResolver를 활용하여 API 예외처리를 한다. 스프링 부트가 제공하는 ExceptionResolver는 다음과 같다. ( 위에서부터 우선순위가 높은 ExceptionResolver이다.) Exce

thkim610.tistory.com

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
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
글 보관함