스프링 MVC 2 (3)

Seung·2023년 2월 17일
0
post-thumbnail

예외처리와 오류페이지

예외처리

  • 서블릿 예외처리

    예외발생 흐름
    WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
    sendError 흐름
    =>response.sendError(HTTP 상태코드, 오류메시지)
    WAS(SendError 호출기록확인) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러
    오류페이지 요청흐름
    WAS(오류페이지 다시 요청) -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(에러페이지 컨트롤러) -> view(오류페이지)
    정리하자면 예외가 발생시 WAS까지 전파후 오류페이지를 찾아서 다시 오류 페이지를 호출하는 방식

서블릿 예외처리 (필터/인터셉터)

  • 컨트롤러에서 예외가 발생하기전에 필터나 인터셉터를 호출 하였는데
    이후에 예외가 발생하고 오류페이지를 요청시 다시한번 호출해야하는데
    해당 필터나 인터셉터가 한번 더 호출되는 것은 비효율적이다.

  • 필터

    DispatcherType
    필터는 위에 말한 비효율적인 방법을 개선하기 위해 해당 옵션을 제공함.

    1. REQUEST : 클라이언트 요청
     2. ERROR : 오류 요청
     3. FORWARD : 서블릿에서 다른 서블릿이나 JSP 호출시
     4. INCLUDE : 서블릿에서 다른 서블릿이나 JSP 결과를포함할때
     5. ASYN : 서블릿 비동기 호출
    
     filterRegistration.setDispatcherType(DispatcherType.REQUEST, DispatcherType.ERROR);
     필터 등록시 setDispatcherType에서 허용 타입을 설정이 가능하다.
     기본값은 REQUEST만 허용됨.
  • 인터셉터

    인터셉터 같은 경우 서블릿이 제공하는 기능이아니라 스프링이 제공하는 기능이다. 그래서 DispatcherType과는 무관하게 항상 호출한다.
    => 인터셉터 등록시 사용한 excludePathPatterns를 사용해서 제외가 가능하다.
    registry.excludePathPatterns("/error") //오류 페이지 경로

  • 스프링부트 오류페이지

    스프링 부트사용시 오류가 발생했을 때 "/error"를 기본으로 요청한다.
    스프링부트가 자동 등록한 'BasicErrorController'가 이경로를 기본으로 받는다.

  • 뷰선택 우선순위

    1. 뷰템플릿 
      - resource/templates/error/500.html
      - resource/templates/error/5xx.html
     
    2. 정적 리소스 (static,publc)
      - resource/static/error/400.html
    3. 적용대상이 없을때
      - resource/templacte/erro.html

    해당 경로에 HTTP 상태코드 이름의 뷰파일을 넣어두면 된다.
    뷰템플릿은 정적리소스보다 우선순위가 높고, 404나500처럼 구체적인 것이 5xx처럼 덜 구체적인것 보다 우선순위가 높다.

API 예외처리

  • HTML은 스프링부트에서 /error 에 오류페이지만 만들면 대부분의 문제를 해결 할 수 있다.
    API같은 경우에는 각 오류상황에 맞는 오류 응답 스펙을 정하고, JSON으로 데이터를 내려줘야한다.

  • 스프링이 제공하는 ExceptionResolver

    HandlerExceptionResolverComposite에 등록
    1. ExceptionHandlerExceptionResolver
    2. ResponseStatusExceptionResolver
    3.DefaultHandlerExceptionResolver ->우선순위가 가장 낮다.

    1. ExceptionHandlerExceptionResolver
      @ExceptionHandler를 처리한다. API예외 대부분이 이기능으로 처리
    2. ResponseStatusExceptionResolver
      HTTP상태코드를 지정해준다.
      @ResponseStatus(value = HttpStatus.NOT_FOUND)
    3. DefaultHandlerExceptionResolver
      스프링 내부 기본 예외를 처리한다.
  • ResponseStatusExceptionResolver

    • @ResponseStatus
      @ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "잘못된 요청")
      public class BadRequestException extends RuntimeException{}
    • ResponseStatusException
      개발자기 직접 변경할 수 없는 예외에 사용가능
      public String responseStatus(){
      	throw new ResponseStatusException(HttpStatus.NOT_FOUNT,"error.bad", new IllegalArgumentException());
      }
  • DefaultHandlerExceptionResolver

    스프링 내부에서 발생하는 스프링 예외를 해결한다.
    대표적으로 파라미터 바인딩시 타입이 맞지 않으면 내부에서 TypeMismatchException이 발생하는데 예외가 발생했기 때문에 그냥 두면
    서블릿까지 올라가서 500에러가 발생하지만, 이 파라미터 바인딩 같은 경우에는
    대부분 클라이언트의 실수로 발생하는 문제여서 400오류로 변경한다.

  • ExceptionHandlerExceptionResolver
    @ExceptionHandler
    해당 컨트롤러에 처리하고 싶은 예외를 지정해주면 된다.

@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExhandle(IllegalArgumentException e){
}
  • IllegalArgumentException 처리
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler //예외 생략시 메서드 파라미터가 예외가 지정됨.
public ErrorResult illegalExhandle(IllegalArgumentException e){
	return new ErrorResult("BAD",e.getMessage); //예외발생시 API응답객체
}
//실행흐름
1. 컨트롤러가 호출한 결과 IllegalArgumentException 예외가 컨트롤러 밖으로 던져진다.
2. 예외가 발생했으므로 ExceptionResolver가 작동, 
   우선순위가 높은 ExceptionHandlerExceptionResolver가 실행
3. ExceptionHandlerExceptionResolver 는 해당 컨트롤러에
   IllegalArgumentException을 처리할 수 있는 @ExceptionHandler가 있는지 확인
4. illegalExhandle() 실행, @ResponseStatus(HttpStatus.BAD_REQUEST) 지정했으므로 400으로 응답
  • @ControllerAdvice
    앞서 설명한 @ㄷExceptionHanlder를 사용해서 예외를 처리할수 있지만,
    정상코드와 예외처리 코드가 컨트롤러에 섞여 있어서 분리하여 깔끔하게 사용
    1. @ControllerAdvice는 대상으로 지정한 여러 컨트롤러에 @ExceptionHandler,@InitBinder 기능을 부여해주는 역할
    2. @ControllerAdvice에 대상을 지정하자 않으면 글로벌 적용
    3. @RestControllerAdvice는 @ResponseBody가 추가된 형태
@ControllerAdivce 컨트롤 지정방법
//애노테이션
@ControllerAdivce(annotations = RestController.class)
public class ExampleAdvice1{}

//특정 패키지
@ControllerAdivce("org.example.controllers")
public class ExampleAdvice2{}

//특정 클래스
@ControllerAdivce(assignableType = 
{ControllerInterface.class, AbstractController.calss})
public class ExampleAdvice3{}
profile
한번 해봅시다.

0개의 댓글