ExceptionHandler

JIWOO YUN·2024년 4월 3일
0

SpringMVC2

목록 보기
26/26
post-custom-banner

기존 HandlerExceptionResolver 가 API 예외 처리에서 어려운점

  • 기본적으로 ModelAndView를 반환해야하는데, API 응답의 경우에는 필요하지않음.
  • API 응답을 위해서 Response에 응답 데이터를 직접 넣어줘야하는 불편함이 존재
  • 특정 컨트롤러에서만 발생하는 예외를 별도로 처리하기 힘듬.
    • 회원을 처리하는 컨트롤러에서 발생하는 runtimeException 과 상품관리 컨트롤러에서 발생하는 runtimeException 예외 처리를 서로 다른 방식으로 처리할려하면 이전까지 했던 방식으로는 따로처리하기 힘들다.

@ExceptionHandler

  • @ExceptionHandler 어노테이션을 선언해두고, 해당 컨트롤러에서 처리하고 싶은 예외를 지정해주면 된다.
    • 이말이 무슨말인지 이해가 안됬는데 쭉 읽어보니 해당 컨트롤러 내부에다가 내가 처리하고 싶은 예외를 지정해두면 만약 이 예외가 발생하게된다면 이 예외처리 방식으로 처리된다는 의미였다.
@Slf4j
@RestController
public class ApiExceptionV2Controller {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public ErrorResult illegalExHandle(IllegalArgumentException e) {
        log.error("[exceptionHandle] ex", e);
        return new ErrorResult("BAD", e.getMessage());
    }
    
        @GetMapping("/api2/members/{id}")
    public MemberDto getMember(@PathVariable("id") String id) {

        if (id.equals("ex")) {
            throw new RuntimeException("잘못된 사용자");

        }

        if (id.equals("bad")) {
            throw new IllegalArgumentException("잘못된 입력 값");
        }

        if (id.equals("user-ex")) {
            throw new UserException("사용자 오류");
        }

        return new MemberDto(id, "hello" + id);
    }
}
  • illegalArgumentException 예외가 터질 경우 먼저 ExceptionHandlerExceptionResolver가 먼저 실행되고나서 컨트롤러에 현재 ExceptionHandler가 있는지 확인해서 있으면 그 handler를 실행해준다.
    • 현재 RestController기 때문에 기본적으로 ResponseBody 적용되있기 때문에 return 한 값은 JSON 형식으로 반환된다.
@ExceptionHandler
public ResponseEntity<ErrorResult> userHandle(UserException e) {
    log.error("[exceptionHandle] ex", e);
    ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
    return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
}
  • ExceptionHandler에 예외를 지정하지 않은 경우 ==> 해당 메서드 파라미터 예외를 사용함.
    • 위의 경우에는 UserException을 사용
  • ResponseEntity를 사용해서 Http바디에 직접 응답 -> HTTP 컨버터가 사용된다.
  • ResponseEntity 사용시 Http 응답코드를 프로그래밍해서 동적으로 변환 가능
    • 이전에 사용한 코드의 경우 @ResponseStatus는 애노테이션이라 동적 변환이 불가능.

Exception을 처리해주는 exhandle 추가

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ErrorResult exHandle(Exception e) {
    log.error("[exceptionHandle] ex", e);
    return new ErrorResult("EX", "내부 오류");
}
  • 이게 존재하는 이유
    • ExceptionHandler에 지정한 부모 클래스는 자식 클래스까지 처리가 가능하다.
    • 자식 예외가 발생하면 부모예외도 같이 호출대상이 되고, 더 자세한게 우선권을 가지게됨.
    • 그렇기 때문에 위에서 만든 illegalException과 UserException에서 처리를 못하는 경우 위에 만든 exhandle이 전부 받아서 처리하게된다.
      • exception이 최상위기 때문에.

만약 다른 컨트롤러에도 적용하고 싶은 경우

  • 이 경우에 ControllerAdvice를 이용해서 적용해야함.

@ControllerAdvice

  • 정상 코드와 예외처리 코드가 하나의 컨트롤러에 섞여있는 것을 @ControllerAdvice 또는@RestControllerAdvice를 사용하면 둘을 분리 가능.

ExControllerAdvice 코드

@Slf4j
@RestControllerAdvice
public class ExControllerAdvice {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public ErrorResult illegalExHandle(IllegalArgumentException e) {
        log.error("[exceptionHandle] ex", e);
        return new ErrorResult("BAD", e.getMessage());
    }

    @ExceptionHandler
    public ResponseEntity<ErrorResult> userHandle(UserException e) {
        log.error("[exceptionHandle] ex", e);
        ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
        return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
    }


    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler
    public ErrorResult exHandle(Exception e) {
        log.error("[exceptionHandle] ex", e);
        return new ErrorResult("EX", "내부 오류");
    }

}
  • Controller에 예외처리했던 부분을 모아서 놓은곳
  • 대상으로 지정한 여러 컨트롤레어 @ExceptionHandler,@InitBinder 기능을 부여해주는 역할
  • 대상을 지정하지 않으면 모든 컨트롤러에 적용됨.

만약 특정 대상 컨트롤러를 지정하고 싶은 경우

// restController 애노테이션이 붙어있는 애들에 적용
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1{}

//특정 패키지 안에있는 컨트롤러에만 적용
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2{}

//특정 클래스안에있는 컨트롤러에 적용
@ControllerAdvice(assignableTypes ={ContollerInterface.class})
public class ExampleAdvice3{}

@ExceptionHandler 와 @ControllerAdvice를 잘 조합하면 예외를 깔끔하게 해결하는게 가능하다!

profile
열심히하자
post-custom-banner

0개의 댓글