Spring

[인프런/스프링 MVC 2편] 8. 예외 처리와 오류 페이지

주니어주니 2023. 5. 27. 17:49

 

 

학습 순서
서블릿 예외 처리 -> 스프링 예외 처리

 

 

1. 서블릿 예외 처리

스프링이 아닌 순수 서블릿 컨테이너의 예외 처리

 

 

📌서블릿의 예외 처리 방식 2가지 

  • Exception (예외) -> 무조건 500 에러 (WAS까지 예외가 왔을 때)
  • response.sendError (HTTP 상태 코드, 오류 메시지) -> 상태 코드 지정 가능 

 

 

✔ 스프링 부트가 제공하는 기본 예외 페이지 끄기

 

* application.properties 

server.error.whitelabel.enabled=false

 

 

1-1. Exception (예외)

 

  • 자바의 기본 예외 동작 방식
    • 자바의 메인 메소드 직접 실행
      -> main 쓰레드 실행
      -> exception 발생
      -> 예외를 못잡으면, 나를 호출한 메소드한테로 예외를 계속 던짐
      -> main() 메소드를 넘어서 예외가 던져지면
      -> 예외 정보를 남기고 해당 쓰레드 종료

  • 웹 애플리케이션에서의 예외 동작 방식 
    • 사용자 요청별로 별도의 쓰레드 할당 (멀티 쓰레드)
      -> 해당 쓰레드가 서블릿 컨테이너 안에서 실행
      -> exception 발생
      -> 어디서 try ~ catch 로 예외를 잡아서 처리하면 문제 X
      -> 애플리케이션에서 예외를 잡지 못하고, 서블릿 밖으로까지 예외가 전달되면?
      (인터셉터에서는 completion에서 예외를 잡아주긴 하지만, 만약 못잡고 넘어간다면)

 

HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러 (예외 발생)
                           WAS <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러 (예외 발생)
  • WAS(톰캣)까지 예외 전달
    • 예외에 따라 지정해 둔 예외 페이지가 있으면 -> 그 페이지로 url 재요청
    • 예외에 따라 지정해 둔 예외 페이지가 없으면 -> 서블릿이 기본으로 제공하는 오류 페이지로 500 응답

 

 

1) 서블릿 예외 컨트롤러

 

* ServletExController 

  • RuntimeExceiption -> throws로 예외를 직접 던지지 않아도 알아서 위로 전달됨
@Slf4j
@Controller
public class ServletExController {

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

 

 

2) 실행

 

* 톰캣이 기본으로 제공하는 오류 화면 

 

(1) Exception 발생

WAS는 서버 내부에서 처리할 수 없는 오류가 발생했다고 인식 -> 500 에러 반환 -> 톰캣이 기본으로 제공하는 오류 화면

 

 

(2) 없는 url 호출

기존의 해당 에러 페이지가 아닌 톰캣 404 에러 페이지 반환

 

 


 

 

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

 

서블릿 컨테이너에게 오류가 발생했다는 점 전달

(당장 오류 발생 X -> 정상 동작)

 

 

1) 컨트롤러 

 

* ServletExController 

 

@GetMapping("/error-404")
public void error404(HttpServletResponse response) throws IOException {
    response.sendError(404, "404 오류!");
}

@GetMapping("/error-500")
public void error500(HttpServletResponse response) throws IOException {
    response.sendError(500);
}

 

HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러 (response.sendError() 호출)
                          WAS <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러 (response.sendError() 호출)
  • sendError() 호출 -> response 내부에 오류가 발생했다는 상태 저장
  • WAS(톰캣)에서 response에 sendError() 호출 여부 확인
    • 예외에 따라 지정해 둔 예외 페이지가 있으면 -> 그 페이지로 url 재요청
    • 예외에 따라 지정해 둔 예외 페이지가 없으면 -> 설정한 오류 코드에 맞춰 기본 오류 페이지 응답

 

 

 

2) 정리

Exception : 예외 발생 시, 모두 500으로 반환
sendError() : 지정한 상태 코드에 따라 반환

 

 


 

1-3. 서블릿이 기본으로 제공하는 오류 화면 커스텀

 

서블릿 컨테이너가 제공하는 못생긴 기본 예외 처리 화면 커스텀하기

 

 

1) 과거 오류 화면 등록 방법 - web.xml 

<web-app>
    <error-page>
        <error-code>404</error-code>
        <location>/error-page/404.html</location>
    </error-page>
    <error-page>
        <error-code>500</error-code>
        <location>/error-page/500.html</location>
    </error-page>
    <error-page>
        <exception-type>java.lang.RuntimeException</exception-type>
        <location>/error-page/500.html</location>
    </error-page>
</web-app>

 

 

지금은 스프링 부트로 서블릿 컨테이너 실행

-> 스프링 부트가 제공하는 기능 사용해서 서블릿 오류 페이지 등록

 

2) 서블릿 오류 페이지 등록 - WebServerCustumizer

package hello.exception;

import org.springframework.boot.web.server.ConfigurableWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

@Component
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");

        // Exception 발생
        ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/error-page/500");

        factory.addErrorPages(errorPage404, errorPage500, errorPageEx);

    }
}

 

  • @Component : 스프링 빈에 등록 ! 
  • response.sendError(404) -> errorPage404 호출
  • response.sendError(500) -> errorPage500 호출
  • RuntimeException 또는 그 자식 예외 -> errorPageEx 호출

 

 

3) 오류 처리 컨트롤러

package hello.exception.servlet;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
@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";
    }
}

 

 

 

4) 오류 처리 뷰 (에러페이지)

* error-page/404

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
</head>
<body>
<div class="container" style="max-width: 600px">
    <div class="py-5 text-center">
        <h2>404 오류 화면</h2>
    </div>
    <div>
        <p>오류 화면 입니다.</p>
    </div>
    <hr class="my-4">
</div> <!-- /container -->
</body>
</html>

 

 

* error-page/500

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="utf-8">
</head>
<body>
<div class="container" style="max-width: 600px">
  <div class="py-5 text-center">
    <h2>500 오류 화면</h2>
  </div>
  <div>
    <p>오류 화면 입니다.</p>
  </div>
  <hr class="my-4">
</div> <!-- /container -->
</body>
</html>

 

 

 

5) 실행

  • 서블릿 예외 처리 방식 2가지
    1. Exception (예외) 발생해서 서블릿 밖으로 전달
    2. response.sendError() 호출

 

  • 서블릿 오류페이지 요청 흐름
WAS (여기까지 전달)                 <- 필터   <- 서블릿   <- 인터셉터   <- 컨트롤러 (예외 발생 / sendError() 호출)
WAS (오류페이지 다시 요청)   -> 필터   -> 서블릿   -> 인터셉터   -> 컨트롤러 (오류페이지 호출)     -> View

 

  • 중요한 점
    • 웹 브라우저(클라이언트)는 서버 내부에서 이런 일이 일어나는 것을 모름 !
    • WAS에서 웹 브라우저로 내보내기 전에 확인하고 다시 추가 요청하기 때문 -> 최종 응답 결과만 봄

 

 

 

(1) Exception 예외 발생의 경우

 

 

① 컨트롤러 - 예외 발생

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

 

 

② WAS 에서 오류 페이지 정보 확인 (RuntimeException 확인) -> 내부에서 오류페이지 호출 

@Component
public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
    @Override
    public void customize(ConfigurableWebServerFactory factory) {

        // Exception 발생
        ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/error-page/500");

        factory.addErrorPages(errorPage404, errorPage500, errorPageEx);
    }
}

 

 

③ 오류 페이지 경로로 다시 요청(호출)

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

 

 

④ 오류 페이지 반환

 

 

 

(2) sendError() 를 호출하는 경우

 

 

① 컨트롤러 - sendError() 호출

@GetMapping("/error-404")
public void error404(HttpServletResponse response) throws IOException {
    response.sendError(404, "404 오류!");
}

@GetMapping("/error-500")
public void error500(HttpServletResponse response) throws IOException {
    response.sendError(500);
}

 

 

② WAS 에서 오류 페이지 정보 확인 (404, 500 상태 확인) -> 내부에서 오류페이지 호출 

@Component
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");

        factory.addErrorPages(errorPage404, errorPage500, errorPageEx);
    }
}

 

 

③ 오류 페이지 경로로 다시 요청(호출)

@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";
}

 

 

④ 오류 페이지 반환

 

 

 

 

6) 오류 정보 추가 

 

WAS가 오류 페이지를 다시 요청할 때, 오류 정보를 request의 attribute에 추가해서 넘길 수 있음

 

 

(1) 오류 정보 추가 - 컨트롤러 

 

package hello.exception.servlet;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
@Controller
public class ErrorPageController {

    // 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";
    public static final String ERROR_SERVLET_NAME = "javax.servlet.error.servlet_name";
    public static final String ERROR_STATUS_CODE = "javax.servlet.error.status_code";

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

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

    private void printErrorInfo(HttpServletRequest request) {
        log.info("ERROR_EXCEPTION: {}", request.getAttribute(ERROR_EXCEPTION));
        log.info("ERROR_EXCEPTION_TYPE: {}", request.getAttribute(ERROR_EXCEPTION_TYPE));
        log.info("ERROR_MESSAGE: {}", request.getAttribute(ERROR_MESSAGE));
        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());
    }
}
  • javax.servlet.error.exception : 예외
  • javax.servlet.error.exception_type : 예외 타입
  • javax.servlet.error.message : 오류 메시지
  • javax.servlet.error.request_uri : 클라이언트 요청 URI
  • javax.servlet.error.servlet_name : 오류가 발생한 서블릿 이름
  • javax.servlet.error.status_code : HTTP 상태 코드

  • dispatcherType : 필터 옵션

 

 

 

(2) 실행

 

① Exception 예외 발생 시 ( /error-ex 호출 ) 로그 출력

 

② sendError() 호출 시 ( /error-404, /error-500 호출 ) 로그 출력

 

 


 

1-4. 필터 중복호출 제거 (오류페이지로 재요청 시)

 

1) DispatcherType의 필요성

 

  • 서블릿 오류페이지 요청 흐름
WAS (여기까지 전달)                 <- 필터   <- 서블릿   <- 인터셉터   <- 컨트롤러 (예외 발생 / sendError() 호출)
WAS (오류페이지 다시 요청)   -> 필터   -> 서블릿   -> 인터셉터   -> 컨트롤러 (오류페이지 호출)     -> View

 

오류 발생 -> 필터, 서블릿, 인터셉터도 모두 다시 호출 (예- 로그인 체크 필터 등)

-> 오류페이지를 호출할 때 모두 다시 호출되는 것은 비효율적

-> 클라이언트로부터 발생한 정상 요청인지, 오류 페이지를 출력하기 위한 내부 요청인지 구분 필요

-> 서블릿이 DispatcherType 이라는 추가 정보 제공

-> 오류페이지를 호출할 때는 필터를 호출하지 않도록 dispatcherType 설정하기

 

 

 

2) DispatcherType

 

실제 고객이 요청한 것인지, 서버가 내부에서 오류페이지를 요청하는 것인지 구분

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

로그 출력시 

dispatchType = REQUEST 		// 고객이 처음 요청
dispatchType = ERROR 		// 오류 페이지 요청

 

 

  • javax.servlet.DispatcherType
    • REQUEST : 클라이언트 요청
    • ERROR : 오류 요청
    • FORWARD : 서블릿에서 다른 서블릿이나 JSP 호출 시 (RequestDispatcher.forward(request, response))
    • INCLUDE : 서블릿에서 다른 서블릿이나 JSP의 결과 포함 시 (RequestDispatcher.include(request, response))
    • ASYNC : 서블릿 비동기 호출
public enum DispatcherType {
    FORWARD,
    INCLUDE,
    REQUEST,
    ASYNC,
    ERROR
}

 

 

 

3) 필터와 DispatcherType

 

(1) DispatcherType 확인을 위한 로그 추가 - LogFilter

  • log.info("REQUEST [{}][{}][{}]", uuid, request.getDispatcherType(), requestURI);
  • log.info("RESPONSE [{}][{}][{}]", uuid, request.getDispatcherType(), requestURI);
package hello.exception.filter;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.UUID;
@Slf4j
public class LogFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("log filter init");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String requestURI = httpRequest.getRequestURI();

        String uuid = UUID.randomUUID().toString();

        try {
            log.info("REQUEST [{}][{}][{}]", uuid, request.getDispatcherType(), requestURI);
            chain.doFilter(request, response);
        } catch (Exception e) {
            throw e;
        } finally {
            log.info("RESPONSE [{}][{}][{}]", uuid, request.getDispatcherType(), requestURI);
        }
    }

    @Override
    public void destroy() {
        log.info("log filter destroy");
    }
}

 

 

(2) 필터 설정 - WebConfig

 

package hello.exception;

import hello.exception.filter.LogFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.servlet.DispatcherType;
import javax.servlet.Filter;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @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;
    }
}
  • filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
    • 클라이언트 요청, 오류 페이지 요청 시 모두 필터 호출
    • 기본 값 : DispatcherType.REQUEST (클라이언트의 요청이 있는 경우만 필터 적용 -> 중복 필터 X)

 

 

(3) 로그 출력 ( /error-ex 호출 시 )

 

  • 클라이언트의 요청
    • LogFIlter - try - 필터 실행 ( request.getDispatcherType() - REQUEST )
                         - doFilter - 필터->서블릿->인터셉터->컨트롤러 (예외 발생) 
                         - catch 잡아서 WAS로 던짐
                         - finally - 필터 나옴 ( request.getDispatcherType() - REQUEST )
    • WAS에서 예외 확인 -> 오류 페이지 요청 -> 필터 다시 호출
  • 내부에서 오류 페이지 요청 (LogFilter 중복 호출)
    • LogFilter - try - 필터 실행 ( request.getDispatcherType() - ERROR )
                         - doFIlter - 필터->서블릿->인터셉터->컨트롤러 (오류 페이지, 오류 정보 로그 출력)
                         - finally - 필터 나옴 ( request.getDispatcherType() - ERROR ) 

 

=> filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST) 으로 중복 필터 제거

 

 

 

 

1-5. 인터셉터 중복 제거 (오류페이지 재요청 시)

 

1) 로그 추가 - LogInterceptor 

  • log.info("REQUEST [{}][{}][{}]", uuid, request.getDispatcherType(), requestURI);
  • log.info("RESPONSE [{}][{}][{}]", uuid, request.getDispatcherType(), requestURI);
package hello.exception.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

@Slf4j
public class LogInterceptor implements HandlerInterceptor {

    public static final String LOG_ID = "logId";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String requestURI = request.getRequestURI();

        String uuid = UUID.randomUUID().toString();
        request.setAttribute(LOG_ID, uuid);

        log.info("REQUEST [{}][{}][{}][{}]", uuid, request.getDispatcherType(), requestURI, handler);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle [{}]", modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        String requestURI = request.getRequestURI();
        String logId = (String)request.getAttribute(LOG_ID);

        log.info("RESPONSE [{}][{}][{}]", logId, request.getDispatcherType(), requestURI);

        if (ex != null) {
            log.error("afterCompletion error!!", ex);
        }
    }
}

 

 

2) 인터셉터 설정 - WebConfig 

 

@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/**");   // 오류 페이지 경로 및 그 하위 페이지를 뺌
    }
  • 필터 -> DispatcherType에 따라 필터를 적용할지 말지 선택 O
  • 인터셉터 -> 서블릿이 아닌 스프링이 제공하는 기능 -> DispatcherType과 무관하게 항상 호출
                     => 오류페이지 경로를 빼줌
                       ( 오류페이지 경로를 빼지 않으면, "error-page/500"같은 내부 호출의 경우에도 인터셉터 호출 )

 

 

3) 로그 

 

  • 예외 발생 -> WAS로 갔다가 -> 인터셉터 중복 호출 X -> 바로 컨트롤러 -> 예외 페이지 출력

 

 

 

4) 정리 

 

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

 

  • /error-ex 오류 요청
    • 필터 : DispatchType으로 중복 호출 제거 (setDispatcherTypes(DispatcherType.REQUEST))
    • 인터셉터 : 경로 정보로 중복 호출 제거 (excludePathPatterns("/error-page/**"))
1. WAS(/error-ex, dispatchType=REQUEST) -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러
2. WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
3. WAS 오류 페이지 확인
4. WAS(/error-page/500, dispatchType=ERROR) -> 필터(x) -> 서블릿 -> 인터셉터(x) -> 컨트롤러(/error-page/500) -> View

 

 

 


 

2. 스프링 부트 예외 처리

 

  • 순수 서블릿 예외 처리 과정 -> 복잡
    • WebServerCustomizer 생성
    • 예외 종류에 따라서 ErrorPage 추가
    • 예외 처리용 컨트롤러 ErrorPageController 생성

 

  • 스프링 부트 예외 처리
    • ErrorPage 자동 등록 -> /error 라는 경로로 기본 오류 페이지 설정
      • new ErrorPage("/error") - 상태코드와 예외를 설정하지 않으면 기본 오류 페이지로 사용
      • 서블릿 밖으로 예외 발생(Exception)하거나, response.sendError(...) 호출되면
        -> 모든 오류는 /error 호출(재요청)하게 됨

    • BasicErrorController라는 스프링 컨트롤러 자동 등록
      • ErrorPage에서 등록한 /error를 매핑해서 처리

    • 오류 발생
      -> /error 를 기본 요청
      -> BasicErrorController가 이 경로를 기본으로 매핑해서 처리

      -> 개발자는 오류 페이지 화면만 등록

 

  • 뷰 선택 우선순위 (BasicErrorController의 처리 순서)
    1. 뷰 템플릿 
      • resources/templates/error/500.html
      • resources/templates/error/5xx.html
    2. 정적 리소스 (static, public)
      • resources/static/error/400.html
      • resources/static/error/404.html
      • resources/static/error/4xx.html
    3. 적용 대상이 없을 때 뷰 이름 (error)
      • resources/templates/error.html

 

 

 

1) 오류 뷰 템플릿 추가

 

* resources/templates/error/4xx.html (+400, 500)

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
</head>
<body>
<div class="container" style="max-width: 600px">
    <div class="py-5 text-center">
        <h2>4xx 오류 화면 스프링 부트 제공</h2>
    </div>
    <div>
        <p>오류 화면 입니다.</p>
    </div>
    <hr class="my-4">
</div> <!-- /container -->
</body>
</html>

 

 

  •  

 

 

 

 

2) BasicErrorController가 제공하는 기본 정보들 

아래 정보들을 model에 담아서 뷰에 전달

 

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
</head>
<body>
<div class="container" style="max-width: 600px">
    <div class="py-5 text-center">
        <h2>500 오류 화면 스프링 부트 제공</h2>
    </div>
    <div>
        <p>오류 화면 입니다.</p>
    </div>
    <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>
    <hr class="my-4">
</div> <!-- /container -->
</body>
</html>

 

 

근데 이렇게 오류 관련 내부 정보 노출하면 X

-> 오류 정보를 model에 포함할지 여부 선택 가능 

 

* application.properties 

server.error.include-exception=true		// exception 포함여부
server.error.include-message=on_param		// message 포함 여부
server.error.include-stacktrace=on_param	// trace 포함 여부
server.error.include-binding-errors=on_param	// errors 포함 여부

 

  • never : 사용 X
  • always : 항상 사용
  • on_param : 파라미터 있을 때 (message=&errors=&trace=)