Notice
Recent Posts
Recent Comments
Link
«   2025/01   »
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 29 30 31
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

[API예외] @ExceptionHandler 본문

인프런/[인프런] 스프링 MVC 2

[API예외] @ExceptionHandler

Seung__ 2021. 10. 1. 18:08

1. HTML 화면 오류 & API 오류


  • 웹 브라우저에 HTML 화면을 제공할 때는 오류가 발생하면 BasicErrorController 사용이 편함
  • 이때는 단순히 5xx, 4xx 관련된 오류 화면을 보여주면 된다.
  • BasicErrorController 는 이런 메커니즘을 모두 구현해둠.

  • 그런데 API는 각 시스템 마다 응답의 모양도 다르고, 스펙도 모두 다르다. 
  • 예외 상황에 단순히 오류 화면을 보여주는 것이 아니라, 
  • 예외에 따라서 각각 다른 데이터를 출력해야 할 수도 있다.
  • 그리고 같은 예외라고해도 어떤 컨트롤러에서 발생했는가에 따라서 다른 예외 응답을 내려주어야 할 수 있다. 
  • 즉 세밀한 제어가 필요하다.

 

2. API예외 처리의 어려운 점.


  • HandlerExceptionResolver 를 떠올려 보면 ModelAndView 를 반환해야 했다. 
  • 이것은 API 응답에는 필요하지 않다.
  • API 응답을 위해서 HttpServletResponse 에 직접 응답 데이터를 넣어주었다. 
  • 특정 컨트롤러에서만 발생하는 예외를 별도로 처리하기 어렵다. 
  • 예를 들어서 회원을 처리하는 컨트롤러에서 발생하는 RuntimeException 예외와 
  • 상품을 관리하는 컨트롤러에서 발생하는 동일한 RuntimeException 예외를 서로 다른 방식으로 처리하고 싶다??

 

 

 

3. @ExceptionHandler


  • 스프링은 API 예외 처리 문제를 해결하기 위해 @ExceptionHandler 라는 애노테이션을 사용
  • 이것이 바로 ExceptionHandlerExceptionResolver 이다.
  • 스프링은 ExceptionHandlerExceptionResolver 를 기본으로 제공하고,
  • 기본으로 제공하는 ExceptionResolver 중에 우선순위도 가장 높다. 
  • 실무에서 API 예외 처리는 대부분 이 기능을 사용

 

 

 

4. ApiExceptionV2Controller


  • @ExceptionHandler(Exception)
  • return으로 원하는 객체 반환 가능.
  • 해당 객체는 JSON으로 변환됨.
@RestController
@Slf4j
public class ApiExceptionV2Controller {

    @ExceptionHandler(IllegalArgumentException.class)
    public ErrorResult illegalExHandler(IllegalArgumentException e){
        log.error("[exceptionHandler] 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);
    }


    @Data
    @AllArgsConstructor
    static class MemberDto{
        private String memberId;
        private String name;
    }

}
  • ErrorResult
@Data
@AllArgsConstructor
public class ErrorResult {

    private String code;
    private String message;
}
  • 처리 흐름
    • 예외 발생
    • DispatcherServlet에서 ExceptionResolver 호출 
    • 이 때 ExceptionHandlerExceptionResolver가 호출됨.
    • 해당 ExceptionResolver는 예외가 발생한 Handler 내부에서 @ExceptionHandler가 붙은 메서드를 찾음.
    • 해당 메서드가 처리할 수 있는 exception이 맞으면 해당 메서드 실행.
    • @RestController이니까 @ResponseBody도 적용됨,HttpMessageConverter 실행됨.
    • 그래서 예외가 발생했지만 정상 흐름으로 바꿔서 return을 한다.
    • 어???정상?? HttpStatus가 200으로 나간다.. ㅋㅋㅋ
    • 어.. 상태코드 바꾸고 싶음..

 

 

5. 상태 코드 변경


  • 예외를 처리할 메서드에 @ResponseStatus를 붙이고, 상태코드를 지정하면됨 ㅋㅋㅋ
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public ErrorResult illegalExHandler(IllegalArgumentException e){
        log.error("[exceptionHandler] ex", e);
    return new ErrorResult("BAD", e.getMessage());
    }

  • 이렇게 되면 ExceptionHandlerExceptionResolver에서 모든 예외 처리가 끝난다.
    • 해당 ExceptionHandler가 호출한 예외 처리 메서드에서
    • json반환, 상태코드 지정, 정상흐름으로 끝! 그냥 정상임. 지금부턴 예외 상황 아님. 
    • 예외가 터진 상황이 아니기 때문에  DiapatcherServlet에서 예외 처리를 위한 별도 실행이 필요 없음.

 

 

 

6. Test2 : ResponseEntity


  • ResponseEntity<>도 return 가능
  • HttpEntity
    • 메시지 바디 정보 직접 반환 -> new HttpEntity<>(body)
    • 헤더 정보 포함 가능
    • view를 조회하지 않음 -> Http 응답 메시지 body에 데이터 넣어서 보내버림.
    @ExceptionHandler(UserException.class)
    public ResponseEntity<ErrorResult> UserExHandler(UserException e){
        log.error("[exceptionHandler] ex", e);
        ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
        return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
    }

 

 

 

7. Test3 : Exception


  • @ExceptionHandler를 통해 최상위 예외인 Exception을 처리해 보자.
  • 다른 method에서 처리하지 못하는 Exception을 받을 수 있음.
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler
    public ErrorResult exHandler(Exception e){
        log.error("[exceptionHandler] ex", e);
        return new ErrorResult("EX","내무오류");
    }

 

 

8. 정리 


  • @ExceptionHandler는 해당 Controller에 내부에만 영향이 있음.
  • 참고로 지정한 예외 또는 그 예외의 자식 클래스는 모두 잡을 수 있음.
  • 스프링의 우선순위는 항상 자세한 것이 우선권을 가진다.
  •  예를 들어서 부모, 자식 클래스가 있고 다음과 같이 예외가 처리된다.
@ExceptionHandler(부모예외.class)
public String 부모예외처리()(부모예외 e) {}

@ExceptionHandler(자식예외.class)
public String 자식예외처리()(자식예외 e) {}
  • 다양한 예외를 한번에 처리 가능
@ExceptionHandler({AException.class, BException.class})
public String ex(Exception e) {
	log.info("exception e", e);
}
  • @ExceptionHandler 에 예외를 생략할 수 있다. 생략하면 메서드 파라미터의 예외가 지정됨.
@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandle(UserException e) {}
  • 파리미터와 응답
    • @ExceptionHandler 에는 마치 스프링의 컨트롤러의 파라미터 응답처럼 
    • 다양한 파라미터와 응답을 지정할 수 있음.
 

Web on Servlet Stack

Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. The formal name, “Spring Web MVC,” comes from the name of its source module (spring-webmvc), but it is more com

docs.spring.io

 

 

9. HTML 오류 화면 


  • ModelAndView를 반환하면 view return 가능 ㅋㅋ
@ExceptionHandler(ViewException.class)
public ModelAndView ex(ViewException e) {
  log.info("exception e", e);
  return new ModelAndView("error");
}

 

 

10. GitHub : 211001 @ExceptionHandler


 

GitHub - bsh6463/Exception

Contribute to bsh6463/Exception development by creating an account on GitHub.

github.com

 

Comments