자바 예외는 최상위 예외 계층인 Throwable, 그 하위로 Exception과 Error가 있는데 Error는 메모리 부족 같이 애플리케이션에서 복구 불가능한 시스템 예외인데 catch로 예외를 잡으면 하위 예외(Error)까지 함께 잡기 때문에 Exception 예외를 실질적인 최상위 예외로 생각하면 된다. 

 

Exception과 그 하위 예외는 RuntimeException을 제외하고 전부 컴파일러가 체크하는 체크 예외(잡아서 처리하거나 밖으로 던지지 않을 경우 컴파일 오류 발생)인데 RuntimeException은 컴파일러가 체크 하지 않는 언체크 예외(하위 예외 포함)로 런타임 예외라고 부른다.

 

예외는 잡아서 처리하거나 호출한 곳으로 던지게 되는데 잡거나(catch) 던질(throws) 경우 지정한 예외의 하위 예외들도 함께 처리가 된다. 예외를 처리하지 못할 경우 자바 main() 쓰레드는 예외 로그를 출력하면서 시스템이 종료되는데 웹 애플리케이션은 시스템이 종료되지 않는 대신 WAS가 해당 예외를 받아서 오류 페이지를 보여주는 식으로 처리를 한다.

 

체크 예외는 예외를 잡아서 처리하지 않는 경우 예외를 던지는 throws를 무조건 선언해야 하는데 예외를 누락하지 않도록 컴파일 시점에서 확인을 해준다는 장점이 있지만 실제로는 모든 예외를 전부 처리해야 되고 의존관계에 문제가 생기는 등의 단점이 있다.

언체크 예외는 말 그대로 컴파일러가 예외를 체크하지 않는 예외로 체크 예외와 달리 throws를 생략해도 되는데 중요한 예외의 경우에는 throws를 선언해서 코드를 호출하는 개발자에게 정보를 줄 수 있다.

 

기본적으로는 런타임 예외를 사용하고 체크 예외는 비즈니스 로직상 의도적으로 던지는, 반드시 처리해야 하는 문제에만 사용을 하는 것이 좋다. 예를 들어 JDBC를 사용하다가 JPA 같은 기술로 변경하는 경우 throws SQLException 코드를 전부 throws JPAException으로 바꿔야 하는 등 OCP, DI에 큰 문제가 생기는데 대부분의 예외는 서비스, 컨트롤러에서 복구 불가능한 예외라서 불필요한 의존관계 문제가 생기는 것이다.

 

복구 불가능한 예외의 경우 서블릿 필터나 스프링 인터셉터, 스프링 ControllerAdvice를 통해 일관성 있게 공통으로 처리해서 오류를 빠르게 인지하는 것이 중요한데 체크 예외를 런타임 예외로 변환하는 경우 예외를 일일히 throws 하지 않아도 되고 공통으로 처리를 하게 되면 변경시에도 영향 범위가 최소화 된다.

 

 

[참고] 인프런 김영한님 강의를 공부한 내용입니다.

 

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

 

웹어플리케이션은 사용자 요쳥별로 별도의 쓰레드가 할당되고, 서블릿 컨테이너 안에서 실행된다. 만약에 예외가 발생했는데 컨트롤러에서 예외를 잡지 못하고 WAS까지 예외가 전달이 되면 서블릿 컨테이너가 기본으로 제공하는 오류 화면이 호출된다.

 

서블릿과 서블릿 컨테이너

더보기

 CGI 웹 서버와 웹 애플리케이션 서버(WAS) 간에 데이터를 주고 받기 위한 규악으로 서버 프로그램은 CGI 규약에 따라 클라이언트의 데이터를 환경변수로 전달하고, 프로그램의 표준 출력 결과를 클라이언트에게 전송한다.

 서블릿은 자바 웹 서버가 동적인 페이지를 생성할 수 있도록 클라이언트의 요청을 처리하고, 결과를 반환하는 자바 웹 프로그래밍 기술로 흔히 자바로 구현된 CGI 라고 한다. 서블릿은 독립적으로 실행되지 않으며 서블릿 컨테이너에 의해 생명주기 관리가 되는데 대표적인 오픈 소스 서블릿 컨테이너로 톰캣(WAS)이 있다.

 

 

 

 서블릿 컨테이너는 클라이언트와 서버 간의 소켓 통신에 필요한 TCP/IP 연결, HTTP 프로토콜 해석 등의 네트워크 기반 작업을 추상화해 API로 제공하여 복잡한 과정을 생략하고 개발자가 비즈니스 로직에 집중하게 도와준다.

 

 서블릿 컨테이너는 서블릿 인터페이스(javax.servlet.Servlet)를 구현하여 GenericServlet 이라는 추상 클래스를 제공하는데 이는 service() 메서드를 제외하고 대부분의 서블릿에 필요한 메서드를 구현한 일종의 서블릿 어댑터이다.

 

 일반적으로 알고 있는 서블릿인 HttpServlet은 GenericServlet을 상속 받아 추상 메서드인 service()를 HTTP 프로토콜 요청 메서드에 적합하게 구현한 서블릿으로 서블릿 컨테이너는 웹 서버로부터 요청을 받으면 HTTP 프로토콜로 request, response 객체를 생성하여 서블릿을 호출하고 service()를 실행한다.

 

 

참고)

https://yangbongsoo.gitbook.io/study/servlet_container 

https://mangkyu.tistory.com/14

https://12bme.tistory.com/555

 

 

오류가 발생했을 때, HttpServletResponse가 제공하는 response.sendError(HTTP StatusCode, ErrorMessage)를 사용해서 서블릿 컨테이너에게 오류가 발생했다는 것을 전달할 수도 있다. 서블릿 컨테이너는 클라이언트에게 응답을 보내기 전 response 객체에 sendError()가 호출되었는지 확인하고 오류 페이지를 보여준다.


 

서블릿 컨테이너가 제공하는 기본 예외 처리 화면 대신 서블릿이 제공하는 오류 화면 기능을 사용하면 고객 친화적으로 오류 화면을 보여줄 수 있다. WebServerFactoryCustomizer의 customize를 오버라이드해서 오류 페이지를 등록할 수 있는데 HTTP 상태 코드와 오류 페이지 url로 ErrorPage를 생성해서 등록하고 오류 페이지를 처리할 컨트롤러를 생성하면 된다. (스프링 부트 자동화)

 

WAS는 ErrorPage를 확인하고 오류 정보를 request의 attribute에 담아서 오류 페이지 url로 다시 요청을 하는데 서버 내부에서 추가적인 호출을 하는 것이기 때문에 클라이언트는 알 수가 없다. 오류 페이지를 요청할 때 필터, 서블릿, 인터셉터가 전부 다시 호출되는데 서블릿은 해당 요청이 정상 요청인지, 오류 요청인지 알 수 있는 추가 정보로 DispatcherType(ENUM)을 제공한다.

(DispatcherType = FORWARD, REQUEST, ERROR ..)

// 필터를 등록할때 추가로 DispatcherType 설정 가능
// 기본 값이 REQUEST라서, 별도의 설정이 없으면 정상 요청시에만 필터를 적용한다.
filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);

필터는 위와 같이 등록 시 설정을 해주면 되지만 기본 설정이 오류 요청에는 필터를 적용하지 않는 것이라 문제가 없다. 인터셉터는 서블릿이 아닌 스프링이 제공하는 기능이기 때문에 DispatcherType을 이용할 수 없고 excludePathPatterns에 오류 페이지 경로를 넣어주면 된다.

 

스프링 부트는 ErrorPage를 '/error' 라는 기본 경로로 자동으로 등록하고 BasicErrorController라는 스프링 컨트롤러를 생성한다.

BasicErrorController는 기본 로직이 전부 등록되어 있기 때문에 해당 컨트롤러가 제공하는 규칙에 따라 오류 페이지를 등록만 해두면 된다. 그리고 오류 관련 정보를 model에 담아서 전달하기 때문에 뷰 템플릿은 이 값을 활용해서 출력할 수 있다.

옵션으로 오류 관련 정보를 보낼지 설정이 가능한데 실무에서는 오류 정보를 전송하는 것은 위험하기 때문에 사용하지 않는 것이 좋다.

<ul>
	<li>오류 정보</li>
	<ul>
        <li th:text="|timestamp: ${timestamp}|"></li>
        <li th:text="|path: ${path}|"></li>
        <li th:text="|status: ${status}|"></li>
        <li th:text="|message: ${message}|"></li>
        <li th:text="|error: ${error}|"></li>
        <li th:text="|exception: ${exception}|"></li>
        <li th:text="|errors: ${errors}|"></li>
        <li th:text="|trace: ${trace}|"></li>
	</ul>
	</li>
</ul>
server.error.include-exception=true
// never : 사용 x, always: 항상 사용, on_param : 파라미터가 있을 때 사용
server.error.include-message=on_param
server.error.include-stacktrace=on_param
server.error.include-binding-errors=on_param

 

 

  • 정적 리소스
    • resources/static/error/404.html (구체적인 것이 우선 순위)
    • resources/static/error/4xx.html (400대 오류 디폴트 처리)

 

  • 뷰 템플릿 (정적 리소스보다 우선 순위가 높다.)
    • resources/templates/error/

'Spring' 카테고리의 다른 글

[스프링 MVC2] 스프링 타입 컨버터  (0) 2022.06.17
[스프링 MVC2] API 예외 처리  (0) 2022.06.17
[스프링 MVC2] 스프링 인터셉터  (0) 2022.06.16
[스프링 MVC2] 서블릿 필터  (0) 2022.06.16
[스프링 MVC2] 쿠키와 세션  (0) 2022.06.16

+ Recent posts