Web/Spring

Spring MVC (2) - 예외 처리와 오류 페이지

now0204 2023. 11. 21. 22:53

 

1. 서블릿 예외처리 

 

- 서블릿은 2가지 방식으로 예외 처리를 지원한다. (Exception, response.sendError(HTTP 상태코드, 오류메시지))

 

1.1 Exception

 

- 자바의 경우 try-catch를 하지 못하고 main쓰레드까지 예외가 던져지면, 예외 정보 출력 후 쓰레드 종료

- 웹 애플리케이션의 경우 예외를 잡지 못하는 경우 인터셉터 -> 서블릿 -> 필터 -> WAS 까지 전파

@GetMapping("/error-ex")
 public void errorEx() {
 throw new RuntimeException("예외 발생!");
 }

 

- 위와 같이 예외를 발생시키면, 톰캣 기본 오류 화면을 볼 수 있다. (HTTP Status 500 - Internal Server Error)

- Exception의 경우 서버 내부에서 처리할 수 없는 오류가 발생한 것으로 보고 HTTP 상태 코드 500을 반환한다.

 

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

 

- 당장 예외 발생 X

- 서블릿 컨테이너에 오류 발생을 전달할 수 있다.

- 컨트롤러 -> 인터셉터 -> 서블릿 -> 필터 -> WAS (sendError 확인)

- sendError에 전달된 에러를 보고 오류 페이지를 보여줌

 

2. 서블릿 예외 처리 - 오류 화면 제공

 

- Exception 혹은 sendError()시 상황에 따라 오류 처리 기능 제공한다.

- 스프링 부트를 통해서 서블릿 오류 페이지를 등록해보자.

 

public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
 
 @Override
 public void customize(ConfigurableWebServerFactory factory) {
 ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/errorpage/404");
 ErrorPage errorPage500 = new
ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error-page/500");
 ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/errorpage/500");
 factory.addErrorPages(errorPage404, errorPage500, errorPageEx);

 }
}

 

- 위와 같이 에러 페이지를 등록할 수 있다.

- 오류 페이지는 예외를 다룰 때 해당 예외의 자식 타입의 오류도 함께 처리한다.

 

2.1 오류 처리 컨트롤러

 

@Controller
public class ErrorPageController {
 @RequestMapping("/error-page/404")
 public String errorPage404(HttpServletRequest request, HttpServletResponse 
response) {
 log.info("errorPage 404");
 return "error-page/404";
 }
 @RequestMapping("/error-page/500")
 public String errorPage500(HttpServletRequest request, HttpServletResponse 
response) {
 log.info("errorPage 500");
 return "error-page/500";
 }
}

 

 

3. 서블릿 예외 처리 - 오류 페이지 작동 원리

 

- Exception 혹은 sendError()이 호출되면, 설정된 오류 페이지를 찾는다.

- WAS는 예외를 처리하는 오류페이지 정보를 확인한다.

  (new ErrorPage(RuntimeException.class, "/error-page/500") 

- 내부적으로 해당 url을 다시 요청한다 

   (필터 -> 서블릿 -> 인터셉터 -> 컨트롤러 (/error-page/500) -> 뷰 

 

* 중요한 점은 클라이언트는 이런 내부 호출을 전혀 모름 (오류 페이지를 찾기 위한 추가 호출)

   또한, 내부 재호출시에도 필터, 인터셉터 등 다시 호출 됨을 확인 할 수 있다.

 

3.1 오류 정보 추가

 

- WAS는 단순히 재요청 뿐 아니라 request등에 추가 정보를 넘겨준다.

  (필요시 사용 가능하다)

- RequestDispatcher 클래스에 상수로 정의되어 있다.

- request의 getDispatcherType은 중요하게 쓰임! 

출처: 스프링 MVC 2 (김영한) -인프런

 

4. 서블릿 예외 처리 - 필터

 

- WAS의 내부 호출 시 필터,서블릿,인터셉터가 다시 호출 된다. 

- 근데 필터나 인터셉터등은 대부분 정상 호출에서 이미 처리됨 -> 재호출은 비효율적 

- 따라서 서버는 클라이언트의 정상호출과 에러 페이지 호출을 구분해야함 

  (DispatcherType을 제공함) 

 

4.1 Dispatcher Type 

 

- 정상호출 시 REQUEST, 에러 페이지 호출 시 ERROR

- > 뿐만 아니라, INCLUDE,ASYNC,FORWARD 등이 있음  

 

@Bean
 public FilterRegistrationBean logFilter() {
 FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
 
 filterRegistrationBean.setFilter(new LogFilter());
 
 filterRegistrationBean.setOrder(1);
 
 filterRegistrationBean.addUrlPatterns("/*");
 
 filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST,DispatcherType.ERROR);
 
 return filterRegistrationBean;
 }

 

- 위와 같이 필터를 등록할 때 setDispatcherTypes를 통해 지정할 수 있다.

 

5. 서블릿 예외 처리 - 인터셉터 

 

- 인터셉터의 경우 서블릿의 기능이 아니라, 스프링이 제공하는 기능이다.

- 따라서 DispatcherType과는 무관하게 작동한다.

- 다만, 인터셉터가 처리하는 경로는 추가하거나 제외하기 쉽다 (excludePathPatterns를 사용하자)

 

@Override
 public void addInterceptors(InterceptorRegistry registry) {

	registry.addInterceptor(new LogInterceptor())
 			.order(1)
 			.addPathPatterns("/**")
 			.excludePathPatterns(
 				"/css/**", "/*.ico"
 				, "/error", "/error-page/**" //오류 페이지 경로
 			);

 

 

6 스프링 부트 - 오류 페이지 

 

- 스프링 부트는 ErrorPage, ErrorPageController 만드는 작업을 기본으로 제공함

- 이때 ErrorPage 등록 시 /error로 url을 매핑한다. 

- 모든 Exception, 혹은 response.sendError을 /error하나로 처리한다.

- 이때 컨트롤러로 BasicErrorController이 사용된다.

   ErrorPage에서 등록한 /error을 매핑해서 처리한다.

 

* BasicErrorController에 기본 로직이 모두 개발되어 있다.

   - BasicErrorController가 제공하는 룰과 우선순위에 따라 오류 페이지 등록하자 

     (정적 HTML - > 정적 리소스 자리, 뷰 템플릿 -> 동적 리소스 자리)

 

6.1 뷰 선택 우선순위

 

출처: 스프링 MVC 2 (김영한) -인프런

 

 

- 해당 경로 위치에 HTTP 상태 코드 이름의 뷰 파일을 넣어두자

- 5xx, 4xx로 두면, 500 혹은 400대 오류를 모두 처리해준다.

 

7. 스프링 부트 - 오류 페이지2

 

- BasicErrorController는 오류 정보를 model에 담아서 전달한다. (message,path, status 등)

- 뷰 템플릿은 이 값을 활용해서 출력할 수 있다.

- 또한 application.properties를 통해 model에 포함할 오류 정보를 선택할 수 있다.

 

#application.properties
server.error.include-exception=false #: exception 포함 여부( true , false )
server.error.include-message=never #: message 포함 여부 on_param 등과 같이 파라미터 있을 때에만 제공할 수 있다.
server.error.include-stacktrace=never #: trace 포함 여부
server.error.include-binding-errors=never #: errors 포함 여부

server.error.whitelabel.enabled=true # 오류 페이지 못찾으면, whirelabel 오류 페이지 적용
server.error.path=/error # 스프링이 자동 등록하는 글로벌 오류 페이지 경로와 BasicErrorController 경로 함께 사용됨

 

- on_param을 사용하면, 파라미터 정보가 노출됨 -> 디버그시 유용 (개발 서버에서만 사용하자)

 

* 에러 공통 처리 컨트롤러를 변경하고 싶으면, ErrorController 인터페이스를 상속 받아서 구현하거나

   BasicErrorController를 상속 받아서 기능을 추가하자