[Spring MVC] [2] 8. 예외 처리와 오류 페이지

윤경·2021년 9월 27일
0

Spring MVC

목록 보기
23/26
post-thumbnail

[1] 프로젝트 생성


[2] 서블릿 예외 처리 - 시작

📌 순수 서블릿 컨테이너 예외처리

  • Exception(예외)
  • response.sendError(Http 상태코드, 오류 메시지)

Exception

  • 자바 직접 실행
    자바 메인 메소드를 직접 실행하는 경우 main이라는 이름의 쓰레드가 실행된다.
    실행 도중 예외를 잡지 못하고 main() 메소드를 넘어서 예외가 던져지면 예외 정보를 남기고 해당 쓰레드는 종료된다.

  • 웹 애플리케이션
    웹 애플리케이션은 쓰레드 하나가 돌아가는 것이 아니라 사용자 요청별로 별도의 쓰레드가 할당되고 서블릿 컨테이너 안에서 실행된다.
    애플리케이션 예외가 발생했는데 어디선가 try-catch로 예외를 잡아 처리하면 아무런 문제가 없지만 예외를 잡지 못하고 서블릿 밖까지 예외가 전달 된다면

WAS → 필터 → 서블릿 → 스프링 인터셉터 → 컨트롤러(예외 발생) 이렇게 WAS가 필터를 호출,, 호출,, 호출,,,,, 되다가

컨트롤러에서 예외가 발생하면 반대로 컨트롤러 → 인터셉터 → 서블릿 → 필터 → WAS 여기까지 전파가 된다.

response.sendError
오류가 발생했을 때 HttpServletResponse가 제공하는 sendError라는 메소드를 사용해도 된다.
이걸 호출한다고 당장 예외가 발생하는 것은 아니지만 서블릿 컨테이너에게 오류가 발생했다는 것을 전달할 수 있다.

response.sendError(Http 상태코드)
response.sendError(Http 상태코드, 오류메시지)

sendError 흐름
WAS(sendError 호출 기록 확인) < 필터 < 서블릿 < 인터셉터 < 컨트롤러(response.sendError())

response.sendError()를 호출하면 response 내부에 오류가 발생했다는 상태를 저장해둔다.
그리고 서블릿 컨테이너는 고객에게 응답 전 response에 sendError()가 호출되었는지 확인한다. 이때, 호출 되었다면 설정한 오류 코드에 맞춰 기본 오류 페이지를 보여준다.


[3] 서블릿 예외 처리 - 오류 화면 제공

서블릿은 Exception이 발생해서 서블릿 밖으로 전달되거나 response.sendError()가 호출되었을 때 상황에 맞게 오류 처리 기능을 제공한다.

오류 페이지는 예외를 다룰 때 해당 예외뿐 아니라 그 자식의 타입 오류까지 함께 처리한다.


[4] 서블릿 예외 처리 - 오류 페이지 작동 원리

서블릿은 Exception이 발생해서 서블릿 밖으로 전달되거나 response.sendError()가 호출되었을 때 설정된 오류 페이지를 찾는다.

sendError 흐름
WAS(sendError 호출 기록 확인) < 필터 < 서블릿 < 인터셉터 < 컨트롤러(response.sendError())

이렇게 WAS는 해당 예외를 처리하는 오류 페이지 정보를 확인한다. 예를 들어 런타임 예외라면 우리는 예제에서 오류 페이지로 /error-page/500를 설정해두었다. 그러므로 WAS는 오류 페이지를 출력하기 위해 /error-page/500를 다시 요청한다.
(http요청이 다시 온 것 마냥 동작하지만 실제로 다시 온 것은 아니다.)

오류 페이지 요청 흐름
WAS /error-page/500 다시 요청 → 필터 → 서블릿 → 인터셉터 → 컨트롤러(/error-page/ 500) → View

예외 발생과 오류 페이지 요청 흐름
1. WAS(여기까지 전파) < 필터 < 서블릿 < 인터셉터 < 컨트롤러(예외발생)
2. WAS /error-page/500 다시 요청 → 필터 → 서블릿 → 인터셉터 → 컨트롤러(/error- page/500) → View

클라이언트(웹 브라우저)는 서버 내부에 이러한 일이 일어나는지 전혀 모름. 오직 서버 내부에서 오류 페이지를 찾기 위한 추가적인 호출.

즉,
1. 예외 발생 > WAS까지 전파
2. WAS는 오류 페이지 경로를 찾아 내부에서 오류 페이지 호출.
이때 오류 페이지 경로로 필터, 서블릿, 인터셉터, 컨트롤러가 모두 다시 호출

WAS는 오류 페이지를 요청 뿐 아니라 request, attribute에 오츄 정보를 넘겨준다.


[5] 서블릿 예외 처리 - 필터

오류가 발생하면 오류 페이지를 출력하기 위해 WAS 내부에서 다시 호출한다고 했다. 이때, 필터, 서블릿, 인터셉터, 컨트롤러가 모두 다시 호출된다. 그런데 이미 인증된 경우인데도 오류 페이지를 위해 또 다시 인증 체크를 하는 것은 번거롭다.

그래서 클라이언트로부터 발생한 오류인지, 오류 페이지를 출력하기 위한 내부 요청인지 구분할 수 있어야 한다.

서블릿은 이런 문제를 해결하기 위해 ([4]에서 사용했던) DispatcherType이라는 추가 정보를 제공한다.

DispatcherType
[4]의 결과(사진)를 참고하면 dispatchType=ERROR의 결과를 확인할 수 있다.

고객이 처음 요청했다면 dispatchType=REQUEST이다.

이렇게 dispatchType을 이용하면 실제 고객 요청인지, 오류 페이지 요청인지 구분할 수 있게된다.

filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);: 이렇게 두 가지를 모두 넣으면 클라이언트 요청뿐만 아니라 오류 페이지 요청에도 필터가 호출된다.

아무것도 넣지 않으면 DispatcherType.REQUEST가 default값이다. (클라이언트 요청에만 호출)


[6] 서블릿 예외 처리 - 인터셉터

필터의 경우 DispatcherType으로 필터를 적용 할지 안 할지 선택할 수 있었다.
그런데 인터셉터는 서블릿이 제공하는 기능이 아니라 스프링이 제공하는 기능이기 때문에 DispatcherType과 무관하게 항상 호출된다.

대신 인터셉터는 excludePathPatterns가 있다.

정상요청 흐름
WAS(/hello, dispatchType=REQUEST) → 필터 → 서블릿 → 인터셉터 → 컨트롤러 → View

오류요청 흐름
1. WAS(/error-ex, dispatchType=REQUES) → 필터 → 서블릿 → 인터셉터 → 컨트롤러
2. WAS(여기까지 전파) < 필터 < 서블릿 < 인터셉터 < 컨트롤러(예외발생)
3. WAS 오류 페이지 확인
4. WAS(/error-page/500,dispatchType=ERROR) → 필터(x) → 서블릿 → 인터셉터(x) → 컨트롤러(/error-page/500) → View

필터DispatchType으로 중복 호출 제거(dispatchType=REQUEST)
인터셉터는 경로 정보로 중복 호출 제거(excludePathPatterns("/error-page/**"))


[7] 스프링 부트 - 오류 페이지1

지금까지
WebServerCustomizer → 예외 종류에 따라 ErrorPage추가 → 예외 처리용 컨트롤러 ErrorPageController 생성

이렇게 복잡한 과정을 거쳤다.

그런데 스프링 부트는 이런 과정을 모두 기본으로 제공한다.

  • ErrorPage 자동 등록. 이때 /error라는 경로로 기본 오류 페이지를 설정
  • BasicErrorController(기본 로직들이 모두 개발되어 있음)라는 스프링 컨트롤러를 자동 등록.

개발자는 BasicErrorController에 따라 오류 페이지 화면만 등록하면 된다.

📌 참고로 우선 순위는 500 > 5xx 이렇듯 더 구체적인 것이 더 높다.


[8] 스프링 부트 - 오류 페이지2

하지만 오류 관련 내부 정보들을 고객에게 노출하는 것은 좋지 않다.

on-param: 특정 파라미터가 있을 때만 노출


그만,,

profile
개발 바보 이사 중

0개의 댓글