ABAP DUMP ERROR 24시

[Spring MVC] 예외처리 + 오류페이지(AllInOne) -part1 본문

[WEB]Back-end/Spring MVC

[Spring MVC] 예외처리 + 오류페이지(AllInOne) -part1

이운형 2022. 4. 7. 16:37
반응형

# 인프런 김영한의 스프링 MVC 2편 - 백엔드 웹 개발 핵심 기술 개인적으로 정리한 글입니다.

정리


Q.순수 서블릿 컨테이너는 예외를 어떻게 처리하나?

1.Exception을 활용한 예외 처리(예외)

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

를 활용하여 예외를 처리한다.

 

Q. 서블릿 컨테이너의 오류 처리방식

자바는

메인 메서드 실행시 main 쓰레드가 실행.

 

실행 도중에 예외를 잡지 못하고 

main() 메소드를 넘어 예외가 던져지면,

예외 정보를 남기고 쓰레드가 종료.

 

웹 어플리케이션

사용자 요청별로 쓰레드가 할당이되고, 서블릿 컨테이너 안에서 실행.

App에서 예외가 발생했는데 이 예외를 잡지 못하고 서블릿 밖(WAS까지)으로 까지 예외가 전달이 된다.

어떻게 전달이 되는걸까?

 

 

Q. 웹 어플리케이션의 예외가 전달 되는 방법

 

<Response.sendError 순서정리>

1.오류 발생시 HTTPServletResponse의 response.sendError를 통해 상태코드와, 오류 메세지를 보낸다.

2. WAS가 서블릿의 sendError 호출 기록을 받는다. 

3.WAS에서 서블릿의 sendError 호출 기록이 있다면 오류 코드에 맞추어서 오류 페이지를 내보낸다.

 

 

<웹 어플리케이션에서 WAS까지 예외가 전달되는 방법>

 

 

1. Exception (예외)

 

public class ServletExController {

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

정말 간단하게 Controller에서 throw new RuntimeException("오류메시지")를 사용하여 나타낸다.

Tomcat에서 제공하는 기본 오류 화면을 보여주고 HTTP 상태코드 또한 500으로 보내준다.

Exception의 경우 서버 내부에서 처리할수 없는 오류가 발생한것으로 생각했기 때문이다.

 

 

2.response.sendError

 

<순서정리>

1.오류 발생시 HTTPServletResponse의 response.sendError를 통해 상태코드와, 오류 메세지를 보낸다.

2. WAS가 서블릿의 sendError 호출 기록을 받는다. 

3.WAS에서 서블릿의 sendError 호출 기록이 있다면 오류 코드에 맞추어서 오류 페이지를 내보낸다.

 

 

오류 발생시 서블릿

HTTPServletResponse의 response.sendError 활용하여 오류를 해결한다.

response.sendError는 HTTP상태 코드와 오류메세지를 추가할수 있다.

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

 

response.sendError()를 호출하면 response 내부에 오류가 발생했다는 상태를 저장해둔다.

 

서블릿 컨테이너

고객응답전에 response에 sendError가 호출되어있는지를 확인한다.

호출되어 있다면 설정한 오류 코드에 맞추어서 기본 오류 페이지를 내보낸다.

 

# 서블릿 컨테이너란?  =>서블릿을 지원하는 WAS를 서블릿 컨테이너라고 한다.

 

 

Q. 필터와 인터셉터의 중복 제거 방법

 

필터의 경우 서블릿이 제공하는 기능이다.

필터의 경우에는 필터를 등록할 때 DispatcherType을 선정해서  필터를 적용여부를 선택할 수 있었다.

 

인터셉터는  스프링이 제공하는 기능이다. 따라서 DispatcherType 과 무관하게 항상 호출

인터셉터는 요청 경로에 따라서 추가하거나 제외하기 쉽게 되어 있기 때문에,

이러한 설정을 사용해서 오류 페이지 경로를 excludePathPatterns 를 사용.

 

 

살펴보기


1.서블릿 오류 페이지 등록

review>

ErrorPage를 일일히 만들어서 사용하겠다는 전략.

 

과거에는 .xml을 활용한 정적인 뷰를 만들어서 제공

현제에는 SpringBoot가 제공하는 WebSeverFactoryCustomizer를 활용해서 사용하자.

public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {

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

        factory.addErrorPages(errorPage404, errorPage500, errorPageEx);

    }
}

2. 해당 404 , 500 오류를 처리할 Controller 제작.

 

review>

단순히 Request , Response를 활용해서 에러페이지를 제공.

// error-page/500관련된 error-page/500 view를 호출해줘~
public class ErrorPageController {
    
    // static  오류//
    @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. 흐름도 정리

review>

예를 들어 RunTimeException이 발생했다면

1. WAS는 WebSeverFactoryCustomizer의 ErrorPage를 확인.

2. RuntimeExceptiom.class를 찾아 "/error-page/500"를 확인.

3. "/error-page/500" 가지고 있는 컨트롤러를 호출.

4. @RequestMapping("error-page/500") 메서드를 통해 error-page/500.html을 찾는다.

 

4. 서블릿의 추가 기능

review>

이때  WAS는 오류 페이지를 단순히 요청하는 것뿐만 아니라 request.getAttribute()에 추가해서 넘겨줄수 있다.

따라서 요류 페이지에서 전달된 오류 정보를 사용할수 있다.

public class ErrorPageController {

    // static  오류//
    @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";
    }

    //RequestDispatcher 상수로 정의되어 있음
    public static final String ERROR_EXCEPTION = "javax.servlet.error.exception"; // 예외
    public static final String ERROR_EXCEPTION_TYPE = "javax.servlet.error.exception_type"; // 예외 타입
    public static final String ERROR_MESSAGE = "javax.servlet.error.message"; // 오류 메세지
    public static final String ERROR_REQUEST_URI = "javax.servlet.error.request_uri"; // 클라이언트 요청 URI
    public static final String ERROR_SERVLET_NAME = "javax.servlet.error.servlet_name"; // 오류가 발생한 서블릿 이름
    public static final String ERROR_STATUS_CODE = "javax.servlet.error.status_code"; // HTTP   상태 코드

    

    private void printErrorInfo(HttpServletRequest request) {
        log.info("ERROR_EXCEPTION: ex=", request.getAttribute(ERROR_EXCEPTION));
        log.info("ERROR_EXCEPTION_TYPE: {}", request.getAttribute(ERROR_EXCEPTION_TYPE));
        log.info("ERROR_MESSAGE: {}", request.getAttribute(ERROR_MESSAGE)); //ex의 경우 NestedServletException 스프링이 한번 감싸서 반환
        log.info("ERROR_REQUEST_URI: {}", request.getAttribute(ERROR_REQUEST_URI));
        log.info("ERROR_SERVLET_NAME: {}", request.getAttribute(ERROR_SERVLET_NAME));
        log.info("ERROR_STATUS_CODE: {}", request.getAttribute(ERROR_STATUS_CODE));
        log.info("dispatchType={}", request.getDispatcherType());
    }

 

5. 한계와 해결방법

한계>

review>

WAS는 오류 페이지 경로를 찾아서 내부에서 오류 페이지를 호출한다.

이때 오류 페이지 경로로 필터, 서블릿, 인터셉터, 컨트롤러가 모두 다시 호출된다.

 

서블릿은 이런 문제를 해결하기 위해 DispatcherType 이라는 추가 정보를 제공

log.info("dispatchType={}", request.getDispatcherType())

 

해결방법>

1.필터 중복 제거

review>

핵심코드 WebConfig에 filterResgistartionBean.setDispathcerTypes()를 활용한다.

filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST,DispatcherType.ERROR);

 

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
       resolvers.add(new MyHandlerExceptionResolver());
       resolvers.add(new UserHandlerExceptionResolver());
    }

    // @Bean
    public FilterRegistrationBean logFilter(){
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();

        filterRegistrationBean.setFilter(new LogFilter());
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.addUrlPatterns("/*");
/**
        과거 filterRegistrationBean 추가된 부분.
        DispatcherType.REQUEST => 클라이언트 요청시 Filter 호출
        DispatcherType.ERROR => 오류 페이지 용청시 Filter 호출

        동시에 사용했으므로 클라이언트 요청, 오류 페이지 요청에 Filter가 적용된다.

 요류 페이지 전용 필터를 적용하고싶으면
 filterRegistrationBean.setDispatcherTypes(DispatcherType.ERROR)만 적용하면 된다.
 */
        filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST,DispatcherType.ERROR);

        return filterRegistrationBean;

 

filterRegistrationBean을 통해서 DispathcerType을 정의해서 클라이언트가 요청할때 제공할지, 오류가 날때만 제공할지 일일히 선택해서 제공이 가능하다.

 

2. 인터셉터 중복 제거

필터의 경우 서블릿이 제공하는 기능이다.

필터의 경우에는 필터를 등록할 때 DispatcherType을 선정해서  필터를 적용여부를 선택할 수 있었다.

 

인터셉터는  스프링이 제공하는 기능이다. 따라서 DispatcherType 과 무관하게 항상 호출

인터셉터는 요청 경로에 따라서 추가하거나 제외하기 쉽게 되어 있기 때문에,

이러한 설정을 사용해서 오류 페이지 경로를 excludePathPatterns 를 사용.

 

즉,

필터는 WebConfig에서 dispatcherType을 제공해서 해결을하고

인터셉터는 WebConfig 에서 excludePathPatterns를 활용해서 제거합니다.

 

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns("/css/**", "/*.ico", "/error", "/error-page/**");
    }

 

 

6.중복 제거시 흐름도

 

반응형
Comments