Spring 입문 5-5 (예외처리)

SJ.CHO·2024년 10월 29일
post-thumbnail

WebApp 에서의 예외처리란?

  1. Client와 Server 모두가 예외에 대해 공통적으로 알고 있지 못하다면, 서비스환경에서의 예외를 제대로 대응할 수 가 없음.
  2. 예외처리 역시 각각의 관심사 분리를 통해 효율적으로 처리가 가능.

  • API 는 응답 헤더에 HTTPStatus Code 를 함께 보냄.
  • Spring 내부에서 Enum 을 통해 상태에 따른 열거상수를 지원.

for (String folderName : folderNames) {
      // 이미 생성한 폴더가 아닌 경우만 폴더 생성
      if (!isExistFolderName(folderName, existFolderList)) {
        Folder folder = new Folder(folderName, user);
        folderList.add(folder);
      } else {
        throw new IllegalArgumentException("폴더명이 중복되었습니다.");
      }
  • 현재 코드에서는 IllegalArgumentException 을 통해서 서버측은 로그를 통해 확인이 가능하지만 클라이언트는 확실한 에러정보를 얻을수 없다.
  • 해당 코드를 중복시 statusCode : 400 , errorMessage : 중복된 폴더명을 제거해 주세요! 폴더명: {중복 폴더명} 형태로 전송해줄 필요성이 있음.
@PostMapping("/folders")
public ResponseEntity<RestApiException> addFolders(@RequestBody FolderRequestDto folderRequestDto,
                                 @AuthenticationPrincipal UserDetailsImpl userDetails) {
    try {
        List<String> folderNames = folderRequestDto.getFolderNames();

        folderService.addFolders(folderNames, userDetails.getUser());
        return new ResponseEntity<>(HttpStatus.OK);
    } catch(IllegalArgumentException ex) {
        RestApiException restApiException = new RestApiException(ex.getMessage(), HttpStatus.BAD_REQUEST.value());
        return new ResponseEntity<>(
                // HTTP body
                restApiException,
                // HTTP status code
                HttpStatus.BAD_REQUEST);
    }
}
  • 해당 코드방식을 통해 하나씩 요구사항에 맞출순 있지만 AOP의 문제와 동일한 이슈가 발생하게된다.
  • 하나의 관심사로써 처리할수 있는 방안이 있을까?

@ExceptionHandler

@ExceptionHandler({IllegalArgumentException.class})
public ResponseEntity<RestApiException> handleException(IllegalArgumentException ex) {
    RestApiException restApiException = new RestApiException(ex.getMessage(), HttpStatus.BAD_REQUEST.value());
    return new ResponseEntity<>(
            // HTTP body
            restApiException,
            // HTTP status code
            HttpStatus.BAD_REQUEST
    );
}
  • Spring에서 예외를 처리하기위한 어노테이션.
  • 특정 Controller 에서 발생한 예외를 처리해주기위해 사용한다.
  • 해당어노테이션 메소드는 @ExceptionHandler({IllegalArgumentException.class}) 방식으로 해당 예외 발생시 처리하는 로직을 담고있음.

Global 예외처리

  • 예외처리는 하나의 컨트롤러가 아닌 여러 컨트롤러의 예외처리를 한번에 모아서 하는것이 효율적이다.
@RestControllerAdvice
public class GlobalExceptionHandler {

  @ExceptionHandler({IllegalArgumentException.class})
  public ResponseEntity<RestApiException> handleException(IllegalArgumentException ex) {
    RestApiException restApiException =
        new RestApiException(ex.getMessage(), HttpStatus.BAD_REQUEST.value());
    return new ResponseEntity<>(
        // HTTP body
        restApiException,
        // HTTP status code
        HttpStatus.BAD_REQUEST);
  }
}
  • @ControllerAdvice
    • Spring에서 예외처리를 위한 클래스 레벨 어노테이션.
    • 모든 컨트롤러 내에서 발생하는 예외를 처리해줌.
    • 예외처리를 중앙집중화하여 Controller에서 예외처리 로직을 반복하지 않아도 됨으로 코드의 중복을 방지하고 유지보수성 상승.
    • 예외처리로직을 모듈화하여 로직 공유 및 참고가 가능.
    • @RestControllerAdvice
      • @ControllerAdvice + @ResponseBody

에러메세지 관리

  • Spring 에서는 properties 파일을 통해서 에러메시지들을 관리할 수 있음.
below.min.my.price=최저 희망가는 최소 {0}원 이상으로 설정해 주세요.
not.found.product=해당 상품이 존재하지 않습니다.
  • K:V 의 형태로 저장되어 관리되며 messageSource 를 Bean 으로 주입받아 사용한다.
private final MessageSource messageSource;

...

@Transactional
public ProductResponseDto updateProduct(Long id, ProductMypriceRequestDto requestDto) {
    int myprice = requestDto.getMyprice();
    if (myprice < MIN_MY_PRICE) {
        throw new IllegalArgumentException(messageSource.getMessage(
                "below.min.my.price",
                new Integer[]{MIN_MY_PRICE},
                "Wrong Price",
                Locale.getDefault()
        ));
    }

    Product product = productRepository.findById(id).orElseThrow(() ->
            new ProductNotFoundException(messageSource.getMessage(
                    "not.found.product",
                    null,
                    "Not Found Product",
                    Locale.getDefault()
            ))
    );

    product.update(requestDto);

    return new ProductResponseDto(product);
}
  • messageSource.getMessage()메서드

    • 첫번째 파라미터는 messages.properties 파일에서 가져올 메시지의 키 값을 전달.
    • 두번째 파라미터는 메시지 내에서 매개변수를 사용할 경우 전달하는 값.
    • 세번째 파라미터는 언어 설정을 전달.
    • Locale.getDefault()메서드는 기본 언어 설정을 가져오는 메서드.
      Ex) 영어, 불어 한국어 등
  • properties 파일로 메세지를 관리할때의 장점은 동일한 에러에 대한 여러메시지를 줄수있는것이라 생각함.
    Ex) 동일한 Nullpoint Exception 의 경우에도 메세지만들 다르게 줄 수 있음.

  • 현재 내가 사용하는 예외처리용 Enum 들에서는 동일예외라도 새로운 열거상수가 만들어져야하기 때문에 생산성측에서는 낮은거같다고 느낌. 또한 기초예외를 사용하지 못함.
  • 장점으로는 열거상수명 만으로도 어느 예외인지 명시적이게 전달이 가능하고 메세지가 일관적이지 않나 생각이 듬.
profile
70살까지 개발하고싶은 개발자

0개의 댓글