05.Exception

Exception

์˜ˆ์™ธ ์ฒ˜๋ฆฌ์™€ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€

์„œ๋ธ”๋ฆฟ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ

์ˆœ์ˆ˜ ์„œ๋ธ”๋ฆฟ ์ปจํ…Œ์ด๋„ˆ์˜ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์ง€์› ๋ฐฉ์‹

.

Exception

์ž๋ฐ” ์ง์ ‘ ์‹คํ–‰ ์‹œ ์˜ˆ์™ธ ๋ฐœ์ƒ

  • ์ž๋ฐ” ๋ฉ”์ธ ๋ฉ”์„œ๋“œ๋ฅผ ์ง์ ‘ ์‹คํ–‰ํ•  ๊ฒฝ์šฐ main ์“ฐ๋ ˆ๋“œ ์‹คํ–‰

  • main ์“ฐ๋ ˆ๋“œ ์‹คํ–‰ ๋„์ค‘์— ์˜ˆ์™ธ๋ฅผ ์žก์ง€ ๋ชปํ•˜๊ณ  ์˜ˆ์™ธ๊ฐ€ ๋˜์ ธ์ง€๋ฉด, ์˜ˆ์™ธ ์ •๋ณด๋ฅผ ๋‚จ๊ธฐ๊ณ  ํ•ด๋‹น ์“ฐ๋ ˆ๋“œ ์ข…๋ฃŒ

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์˜ˆ์™ธ ๋ฐœ์ƒ

@GetMapping("/error-ex")
    public void errorEx() {
    throw new RuntimeException("์˜ˆ์™ธ ๋ฐœ์ƒ!");
}
  • ์‚ฌ์šฉ์ž ์š”์ฒญ๋ณ„๋กœ ๋ณ„๋„์˜ ์“ฐ๋ ˆ๋“œ๊ฐ€ ํ• ๋‹น๋˜๊ณ , ์„œ๋ธ”๋ฆฟ ์ปจํ…Œ์ด๋„ˆ ์•ˆ์—์„œ ์‹คํ–‰

  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ, try~catch ๋กœ ์˜ˆ์™ธ๋ฅผ ์žก์•„์„œ ์ฒ˜๋ฆฌํ•˜๋ฉด ๋ฌธ์ œ๊ฐ€ ์—†์Œ.

  • ํ•˜์ง€๋งŒ, ์˜ˆ์™ธ๋ฅผ ์žก์ง€ ๋ชปํ•˜๊ณ , ์„œ๋ธ”๋ฆฟ ๋ฐ–์œผ๋กœ ์˜ˆ์™ธ๊ฐ€ ์ „๋‹ฌ๋  ๊ฒฝ์šฐ WAS(tomcat)๊นŒ์ง€ ์˜ˆ์™ธ ์ „๋‹ฌ

    WAS <- ํ•„ํ„ฐ <- ์„œ๋ธ”๋ฆฟ <- ์ธํ„ฐ์…‰ํ„ฐ <- ์ปจํŠธ๋กค๋Ÿฌ(์˜ˆ์™ธ๋ฐœ์ƒ)
  • WAS ๋Š” Exception ์˜ˆ์™ธ๊ฐ€ ์˜ฌ๋ผ์˜ค๋ฉด ์„œ๋ฒ„ ๋‚ด๋ถ€์—์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒƒ์œผ๋กœ ์ธ์ง€ํ•˜๊ณ  HTTP Status 500 โ€“ Internal Server Error ๋ฐ˜ํ™˜

    # ์Šคํ”„๋ง ๋ถ€ํŠธ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋ณธ ์˜ˆ์™ธ ํŽ˜์ด์ง€ OFF
    server.error.whitelabel.enabled=false

.

response.sendError(HTTP ์ƒํƒœ ์ฝ”๋“œ, ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€)

@GetMapping("/error-404")
public void error404(HttpServletResponse response) throws IOException {
    response.sendError(404, "404 ์˜ค๋ฅ˜!"); // HTTP ์ƒํƒœ ์ฝ”๋“œ์™€ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ
}
  • ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ HttpServletResponse.sendError ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

    • sendError ํ˜ธ์ถœ ์‹œ ๋ฐ”๋กœ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ์ง€๋งŒ, response ๋‚ด๋ถ€์— ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜์—ฌ WAS(Servlet container) ์—๊ฒŒ ์ „๋‹ฌ

    • WAS(Servlet container) ๋Š” ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์‘๋‹ต ์ „์— response ์— sendError() ํ˜ธ์ถœ ๊ธฐ๋ก ํ™•์ธ ํ›„, ํ˜ธ์ถœ๋˜์—ˆ๋‹ค๋ฉด ์„ค์ •ํ•œ ์˜ค๋ฅ˜ ์ฝ”๋“œ์— ๋งž๋Š” ๊ธฐ๋ณธ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€ ์ถœ๋ ฅ

  • sendError ํ๋ฆ„

    WAS(sendError ํ˜ธ์ถœ ๊ธฐ๋ก ํ™•์ธ) <- ํ•„ํ„ฐ <- ์„œ๋ธ”๋ฆฟ <- ์ธํ„ฐ์…‰ํ„ฐ <- ์ปจํŠธ๋กค๋Ÿฌ

์„œ๋ธ”๋ฆฟ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€

์„œ๋ธ”๋ฆฟ์€ Exception ์ด ์„œ๋ธ”๋ฆฟ ๋ฐ–์œผ๋กœ ์ „๋‹ฌ๋˜๊ฑฐ๋‚˜ response.sendError() ํ˜ธ์ถœ ์‹œ ๊ฐ ์ƒํ™ฉ์— ๋งž์ถ˜ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ๊ธฐ๋Šฅ ์ œ๊ณต

์„œ๋ธ”๋ฆฟ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€ ๋“ฑ๋ก

@Component
public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {

    @Override
    public void customize(ConfigurableWebServerFactory factory) {

        ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/404"); // response.sendError(404)
        ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error-page/500"); // response.sendError(500)

        ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/error-page/500"); // RuntimeException ๋˜๋Š” ๊ทธ ์ž์‹ ํƒ€์ž…์˜ ์˜ˆ์™ธ

        factory.addErrorPages(errorPage404, errorPage500, errorPageEx);
    }
}
  • ์˜ค๋ฅ˜ ํŽ˜์ด์ง€๋Š” ์˜ˆ์™ธ๋ฅผ ๋‹ค๋ฃฐ ๋•Œ ํ•ด๋‹น ์˜ˆ์™ธ์™€ ๊ทธ ์ž์‹ ํƒ€์ž…์˜ ์˜ค๋ฅ˜๋ฅผ ํ•จ๊ป˜ ์ฒ˜๋ฆฌ

์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ์ปจํŠธ๋กค๋Ÿฌ

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

์˜ค๋ฅ˜ ํŽ˜์ด์ง€ ์š”์ฒญ ํ๋ฆ„

# ์˜ˆ์™ธ ๋ฐœ์ƒ ํ๋ฆ„
์ปจํŠธ๋กค๋Ÿฌ(์˜ˆ์™ธ ๋ฐœ์ƒ) โž” ์Šคํ”„๋ง ์ธํ„ฐ์…‰ํ„ฐ โž” ์„œ๋ธ”๋ฆฟ โž” ํ•„ํ„ฐ โž” WAS

# sendError ํ๋ฆ„
์ปจํŠธ๋กค๋Ÿฌ(response.sendError()) โž” ์Šคํ”„๋ง ์ธํ„ฐ์…‰ํ„ฐ โž” ์„œ๋ธ”๋ฆฟ โž” ํ•„ํ„ฐ โž” WAS(sendError ํ˜ธ์ถœ ๊ธฐ๋ก ํ™•์ธ)

---

# ์˜ค๋ฅ˜ ํŽ˜์ด์ง€ ์š”์ฒญ ํ๋ฆ„
WAS '/error-page/500' ์š”์ฒญ โž” ํ•„ํ„ฐ โž” ์„œ๋ธ”๋ฆฟ โž” ์Šคํ”„๋ง ์ธํ„ฐ์…‰ํ„ฐ โž” ์ปจํŠธ๋กค๋Ÿฌ(/error-page/500) โž” View
  • WAS ๊นŒ์ง€ ์˜ˆ์™ธ๊ฐ€ ์ „ํŒŒ๋  ๊ฒฝ์šฐ, WAS ๋Š” ํ•ด๋‹น ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์˜ค๋ฅ˜ ํŽ˜์ด์ง€ ์ •๋ณด ํ™•์ธ ํ›„ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€๋ฅผ ๋‹ค์‹œ ์š”์ฒญ

  • WAS ๋Š” ์˜ค๋ฅ˜ ํŽ˜์ด์ง€ ์š”์ฒญ ์‹œ ์˜ค๋ฅ˜ ์ •๋ณด๋ฅผ request.attribute ์— ์ถ”๊ฐ€ํ•ด์„œ ์ „๋‹ฌ

private void printErrorInfo(HttpServletRequest request) {
    log.info("ERROR_EXCEPTION: ex=", request.getAttribute(RequestDispatcher.ERROR_EXCEPTION));
    log.info("ERROR_EXCEPTION_TYPE: {}", request.getAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE));
    log.info("ERROR_MESSAGE: {}", request.getAttribute(RequestDispatcher.ERROR_MESSAGE)); // ex์˜ ๊ฒฝ์šฐ NestedServletException ์Šคํ”„๋ง์ด ํ•œ๋ฒˆ ๊ฐ์‹ธ์„œ ๋ฐ˜ํ™˜
    log.info("ERROR_REQUEST_URI: {}", request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI));
    log.info("ERROR_SERVLET_NAME: {}", request.getAttribute(RequestDispatcher.ERROR_SERVLET_NAME));
    log.info("ERROR_STATUS_CODE: {}", request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE));
    log.info("dispatchType={}", request.getDispatcherType());
}

DispatcherType ๐ŸŒž

ํด๋ผ์ด์–ธํŠธ๋กœ ๋ถ€ํ„ฐ ๋ฐœ์ƒํ•œ ์ •์ƒ ์š”์ฒญ์ธ์ง€, ์˜ค๋ฅ˜ ํŽ˜์ด์ง€๋ฅผ ์ถœ๋ ฅํ•˜๊ธฐ ์œ„ํ•œ ๋‚ด๋ถ€ ์š”์ฒญ์ธ์ง€ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด ์„œ๋ธ”๋ฆฟ์€ DispatcherType ์ •๋ณด ์ œ๊ณต

  • ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€ ์ถœ๋ ฅ์„ ์œ„ํ•ด WAS ๋‚ด๋ถ€์—์„œ ํ•„ํ„ฐ, ์„œ๋ธ”๋ฆฟ, ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ๋‹ค์‹œ ํ˜ธ์ถœ

  • ์ด๋ฏธ ์ดˆ๊ธฐ ์š”์ฒญ์—์„œ ๊ฒ€์ฆ์ด ์™„๋ฃŒ๋œ ๋ถ€๋ถ„์€ ์žฌํ˜ธ์ถœ ๋  ๊ฒฝ์šฐ ๋น„ํšจ์œจ์ 

.

DispatcherType

  • ์„œ๋ธ”๋ฆฟ ์ŠคํŽ™์€ ์‹ค์ œ ๊ณ ๊ฐ์ด ์š”์ฒญํ•œ ๊ฒƒ์ธ์ง€, ์„œ๋ฒ„๊ฐ€ ๋‚ด๋ถ€์—์„œ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•˜๋Š” ๊ฒƒ์ธ์ง€ DispatcherType ์œผ๋กœ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์ œ๊ณต

public enum DispatcherType {
    FORWARD, // ๋‹ค๋ฅธ ์„œ๋ธ”๋ฆฟ์ด๋‚˜ JSP ํ˜ธ์ถœ โž” RequestDispatcher.forward(request, response)
    INCLUDE, // ๋‹ค๋ฅธ ์„œ๋ธ”๋ฆฟ์ด๋‚˜ JSP ๊ฒฐ๊ณผ ํฌํ•จ โž” RequestDispatcher.include(request, response)
    REQUEST, // ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ
    ASYNC, // ์„œ๋ธ”๋ฆฟ ๋น„๋™๊ธฐ ํ˜ธ์ถœ
    ERROR // ์˜ค๋ฅ˜ ์š”์ฒญ
}

.

ํ•„ํ„ฐ์™€ DispatcherType

  • DispatcherType ๋กœ๊ทธ ํ•„ํ„ฐ

    @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");
        }
    }
  • ๋กœ๊ทธ ํ•„ํ„ฐ ๋“ฑ๋ก

    @Configuration
    public class DispatcherTypeWebConfig implements WebMvcConfigurer {
    
        @Bean
        public FilterRegistrationBean logFilter() {
            FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
            filterRegistrationBean.setFilter(new LogFilter());
            filterRegistrationBean.setOrder(1);
            filterRegistrationBean.addUrlPatterns("/*");
            // default: REQUEST. ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ ์‹œ์—๋งŒ ํ•„ํ„ฐ ์ ์šฉ
            filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
            return filterRegistrationBean;
        }
    }

.

ํ•„ํ„ฐ์™€ Interceptor

  • DispatcherType ๋กœ๊ทธ ์ธํ„ฐ์…‰ํ„ฐ

    @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);
            }
        }
    }
  • ๋กœ๊ทธ ์ธํ„ฐ์…‰ํ„ฐ ๋“ฑ๋ก

@Configuration
public class DispatcherTypeWebConfig implements WebMvcConfigurer {

    /**
     * ํ•„ํ„ฐ๋Š” ํ•„ํ„ฐ ๋“ฑ๋ก ์‹œ ํŠน์ • DispatcherType ์ธ ๊ฒฝ์šฐ ํ•„ํ„ฐ๊ฐ€ ์ ์šฉ๋˜๋„๋ก ์„ค์ •์ด ๊ฐ€๋Šฅํ–ˆ์ง€๋งŒ,
     * ์ธํ„ฐ์…‰ํ„ฐ๋Š” ์Šคํ”„๋ง์ด ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋ผ์„œ DispatcherType ์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ํ•ญ์ƒ ํ˜ธ์ถœ
     * 
     * ๋Œ€์‹  ์ธํ„ฐ์…‰ํ„ฐ์˜ excludePathPatterns ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํŠน์ • ๊ฒฝ๋กœ ์ œ์™ธ ๊ฐ€๋Šฅ
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns(
                        "/css/**", "/*.ico"
                        , "/error", "/error-page/**" //์˜ค๋ฅ˜ ํŽ˜์ด์ง€ ๊ฒฝ๋กœ
                );
    }
}

.

DispatcherType ํ๋ฆ„

1. WAS(/error-ex, dispatchType=REQUEST) โž” ํ•„ํ„ฐ โž” ์„œ๋ธ”๋ฆฟ โž” ์ธํ„ฐ์…‰ํ„ฐ โž” ์ปจํŠธ๋กค๋Ÿฌ
2. ์ปจํŠธ๋กค๋Ÿฌ(์˜ˆ์™ธ๋ฐœ์ƒ) โž” ์ธํ„ฐ์…‰ํ„ฐ โž” ์„œ๋ธ”๋ฆฟ โž” ํ•„ํ„ฐ โž” WAS
3. WAS ์˜ค๋ฅ˜ ํŽ˜์ด์ง€ ํ™•์ธ
4. WAS(/error-page/500, dispatchType=ERROR) โž” ํ•„ํ„ฐ(x) โž” ์„œ๋ธ”๋ฆฟ โž” ์ธํ„ฐ์…‰ํ„ฐ(x) โž”
์ปจํŠธ๋กค๋Ÿฌ(/error-page/500) โž” View

์Šคํ”„๋ง ๋ถ€ํŠธ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€ ๐ŸŒž

์Šคํ”„๋ง ๋ถ€ํŠธ๋Š” ์„œ๋ธ”๋ฆฟ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€ ํ˜ธ์ถœ์— ํ•„์š”ํ–ˆ๋˜ ์•„๋ž˜ ๋ณต์žกํ•œ ๊ณผ์ •์„ ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณต

  • WebServerCustomizer ๋งŒ๋“ค๊ธฐ

  • ์˜ˆ์™ธ ์ข…๋ฅ˜์— ๋”ฐ๋ผ ErrorPage ์ถ”๊ฐ€ โž” ErrorPage ์ž๋™ ๋“ฑ๋ก

    • /error ๊ฒฝ๋กœ๋ฅผ ๊ธฐ๋ณธ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€๋กœ ์„ค์ •

    • ์„œ๋ธ”๋ฆฟ ๋ฐ–์œผ๋กœ ์˜ˆ์™ธ๊ฐ€ ๋˜์ ธ์ง€๊ฑฐ๋‚˜, response.sendError(...) ํ˜ธ์ถœ ์‹œ ๋ชจ๋“  ์˜ค๋ฅ˜๋Š” /error ํ˜ธ์ถœ

    • ErrorMvcAutoConfiguration ํด๋ž˜์Šค๊ฐ€ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€๋ฅผ ์ž๋™์œผ๋กœ ๋“ฑ๋กํ•˜๋Š” ์—ญํ• 

  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ์šฉ ์ปจํŠธ๋กค๋Ÿฌ(ErrorPageController) ์ƒ์„ฑ โž” ์ž๋™ ๋“ฑ๋ก(BasicErrorController)

    • ErrorPage ์—์„œ ๋“ฑ๋กํ•œ /error ๋ฅผ ๋งคํ•‘ํ•ด์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ

.

BasicErrorController

  • ๊ธฐ๋ณธ์ ์ธ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€ ๋กœ์ง์ด ๋ชจ๋‘ ๊ตฌํ˜„

  • ๊ฐœ๋ฐœ์ž๋Š” ์˜ค๋ฅ˜ ํŽ˜์ด์ง€ ํ™”๋ฉด๋งŒ BasicErrorController ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋ฃฐ๊ณผ ์šฐ์„ ์ˆœ์œ„์— ๋”ฐ๋ผ์„œ ๋“ฑ๋ก

    • ์ •์  HTML ์ผ ๊ฒฝ์šฐ ์ •์  ๋ฆฌ์†Œ์Šค, ๋ทฐ ํ…œํ”Œ๋ฆฟ์„ ์‚ฌ์šฉํ•œ ๋™์  ์˜ค๋ฅ˜ ํ™”๋ฉด์ผ ๊ฒฝ์šฐ ๋ทฐ ํ…œํ”Œ๋ฆฟ ๊ฒฝ๋กœ์— ์˜ค๋ฅ˜ ํŽ˜์ด์ง€ ํŒŒ์ผ ์ƒ์„ฑ

.

BasicErrorController View ์„ ํƒ ์šฐ์„ ์ˆœ์œ„

.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

ํ•ด๋‹น ๊ฒฝ๋กœ ์œ„์น˜์— HTTP ์ƒํƒœ ์ฝ”๋“œ ์ด๋ฆ„์˜ ๋ทฐ ํŒŒ์ผ์„ ๋„ฃ์–ด๋‘์ž.

๋ทฐ ํ…œํ”Œ๋ฆฟ์ด ์ •์  ๋ฆฌ์†Œ์Šค๋ณด๋‹ค ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’๊ณ ,

404, 500 ์ฒ˜๋Ÿผ ๊ตฌ์ฒด์ ์ธ ๊ฒƒ์ด 5xx์ฒ˜๋Ÿผ ๋œ ๊ตฌ์ฒด์ ์ธ ๊ฒƒ ๋ณด๋‹ค ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’๋‹ค.

.

BasicErrorController ์ œ๊ณต ๊ธฐ๋ณธ ์ •๋ณด

  • ๊ธฐ๋ณธ ์ •๋ณด๋ฅผ model ์— ๋‹ด์•„ View ์— ์ „๋‹ฌ

timestamp: Fri Feb 05 00:00:00 KST 2021
path: `/hello` (Client ์š”์ฒญ ๊ฒฝ๋กœ )
status: 400
message: Validation failed for object='data'. Error count: 1
error: Bad Request
exception: org.springframework.validation.BindException
errors: Errors(BindingResult)
trace: ์˜ˆ์™ธ trace
  • message, exception, errors, trace ์ •๋ณด๋Š” ๋ณด์•ˆ์ƒ default ๋กœ ํฌํ•จ์ด ๋˜์–ด์žˆ์ง€ ์•Š์Œ

    • properties ์„ค์ •์„ ํ†ตํ•ด ์˜ค๋ฅ˜ ์ •๋ณด๋ฅผ model ์— ํฌํ•จํ• ์ง€ ์—ฌ๋ถ€ ์„ ํƒ

    # exception ํฌํ•จ ์—ฌ๋ถ€(true, false)
    server.error.include-exception=true
    # message ํฌํ•จ ์—ฌ๋ถ€
    server.error.include-message=always
    # trace ํฌํ•จ ์—ฌ๋ถ€
    server.error.include-stacktrace=always
    # errors ํฌํ•จ ์—ฌ๋ถ€
    server.error.include-binding-errors=always
    • never: ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ

    • always: ํ•ญ์ƒ ์‚ฌ์šฉ

    • on_param: ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์žˆ์„ ๋•Œ ํ•ด๋‹น ์ •๋ณด ๋…ธ์ถœ

      • HTTP ์š”์ฒญ์‹œ ํŒŒ๋ผ๋ฏธํ„ฐ(?message=&errorsa=&trace=)๋ฅผ ์ „๋‹ฌํ•˜๋ฉด ํ•ด๋‹น ์ •๋ณด๋“ค์ด model ์— ๋‹ด๊ฒจ ๋ทฐ ํ…œํ”Œ๋ฆฟ์— ์ถœ๋ ฅ

      • ์šด์˜ ์„œ๋ฒ„์—์„œ๋Š” ๋น„๊ถŒ์žฅ

์‹ค๋ฌด์—์„œ๋Š” ์ด ์ •๋ณด๋“ค์„ ๋…ธ์ถœํ•˜๋ฉด ์•ˆ๋œ๋‹ค.

์‚ฌ์šฉ์ž์—๊ฒŒ๋Š” ๊น”๋”ํ•œ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€์™€ ๊ณ ๊ฐ์ด ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ„๋‹จํ•œ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ ,

์˜ค๋ฅ˜๋Š” ์„œ๋ฒ„์— ๋กœ๊ทธ๋กœ ๋‚จ๊ฒจ์„œ ๋กœ๊ทธ๋กœ ํ™•์ธํ•˜์ž.

.

์Šคํ”„๋ง ๋ถ€ํŠธ ์˜ค๋ฅ˜ ๊ด€๋ จ ์˜ต์…˜

# ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ํ™”๋ฉด์„ ๋ชป ์ฐพ์„ ๊ฒฝ์šฐ, ์Šคํ”„๋ง whitelabel ์˜ค๋ฅ˜ ํŽ˜์ด์ง€ ์ ์šฉ
server.error.whitelabel.enabled=true

# ์˜ค๋ฅ˜ ํŽ˜์ด์ง€ ๊ฒฝ๋กœ
# ์Šคํ”„๋ง์ด ์ž๋™ ๋“ฑ๋กํ•˜๋Š” ์„œ๋ธ”๋ฆฟ ๊ธ€๋กœ๋ฒŒ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€ ๊ฒฝ๋กœ์™€ BasicErrorController ์˜ค๋ฅ˜ ์ปจํŠธ๋กค๋Ÿฌ ๊ฒฝ๋กœ์— ํ•จ๊ป˜ ์‚ฌ์šฉ
server.error.path=/error

ํ™•์žฅ ํฌ์ธํŠธ

  • ์—๋Ÿฌ ๊ณตํ†ต ์ฒ˜๋ฆฌ ์ปจํŠธ๋กค๋Ÿฌ์˜ ๊ธฐ๋Šฅ์„ ๋ณ€๊ฒฝํ•˜๊ณ  ์‹ถ์„ ๊ฒฝ์šฐ ErrorController ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ƒ์† ๋ฐ›์•„์„œ ๊ตฌํ˜„ํ•˜๊ฑฐ๋‚˜, BasicErrorController ๋ฅผ ์ƒ์† ๋ฐ›์•„์„œ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ด๋ณด์ž.

์Šคํ”„๋ง ๋ถ€ํŠธ ๊ธฐ๋ณธ ์ œ๊ณต ์˜ค๋ฅ˜ ํŽ˜์ด์ง€๋ฅผ ํ™œ์šฉํ•˜๋ฉด ์˜ค๋ฅ˜ ํŽ˜์ด์ง€ ๊ด€๋ จ ๋Œ€๋ถ€๋ถ„์˜ ๋ฌธ์ œ๋Š” ์†์‰ฝ๊ฒŒ ํ•ด๊ฒฐ ๊ฐ€๋Šฅํ•˜๋‹ค.

API ์˜ˆ์™ธ ์ฒ˜๋ฆฌ

์˜ค๋ฅ˜ ํŽ˜์ด์ง€๋Š” ๋‹จ์ˆœํžˆ ๊ณ ๊ฐ์—๊ฒŒ ์˜ค๋ฅ˜ ํ™”๋ฉด์„ ๋ณด์—ฌ์ฃผ๋ฉด ๋˜์ง€๋งŒ, API ๋Š” ๊ฐ ์˜ค๋ฅ˜ ์ƒํ™ฉ์— ๋งž๋Š” ์˜ค๋ฅ˜ ์‘๋‹ต ์ŠคํŽ™์„ ์ •ํ•˜๊ณ , JSON์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์‘๋‹ตํ•ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค.

produces ์„ค์ •

@RequestMapping(value = "/error-page/500", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Object>> errorPage500Api(HttpServletRequest request, HttpServletResponse response) {
    // ...
    return new ResponseEntity(result, HttpStatus.valueOf(statusCode));
}
  • produces = MediaType.APPLICATION_JSON_VALUE

    • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญํ•˜๋Š” HTTP Header Accept ๊ฐ’์ด application/json ์ผ ๋•Œ ํ•ด๋‹น ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ

  • ResponseEntity ๋Š” ๋ฉ”์‹œ์ง€ ์ปจ๋ฒ„ํ„ฐ๊ฐ€ ๋™์ž‘ํ•˜๋ฉด์„œ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ JSON ๊ตฌ์กฐ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋ฐ˜ํ™˜

Spring Boot ๊ธฐ๋ณธ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ

API ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋„ ์Šคํ”„๋ง ๋ถ€ํŠธ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋ณธ ์˜ค๋ฅ˜ ๋ฐฉ์‹์„ ์‚ฌ์šฉ

BasicErrorController ์ฝ”๋“œ ์ผ๋ถ€

// ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ์˜ Accept ํ•ด๋” ๊ฐ’์ด text/html ์ธ ๊ฒฝ์šฐ ํ˜ธ์ถœ(view ์ œ๊ณต)
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { ... }

// ๊ทธ์™ธ ๊ฒฝ์šฐ์— ํ˜ธ์ถœ(ResponseEntity ๋กœ HTTP Body ์— JSON ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜)
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { ... }
  • ์Šคํ”„๋ง ๋ถ€ํŠธ์˜ ๊ธฐ๋ณธ ์„ค์ •์€ ์˜ค๋ฅ˜ ๋ฐœ์ƒ์‹œ /error ๋ฅผ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€๋กœ ์š”์ฒญ

  • BasicErrorController ๋Š” /error ๊ฒฝ๋กœ๋ฅผ ๊ธฐ๋ณธ์œผ๋กœ ๋ฐ›์Œ.(server.error.path ๋กœ ์ˆ˜์ • ๊ฐ€๋Šฅ

BasicErrorController ๋Š” HTML ์˜ค๋ฅ˜ ํŽ˜์ด์ง€๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒฝ์šฐ ๋งค์šฐ ํŽธ๋ฆฌ

๋‹จ, API ๋Š” ๊ฐ ์ปจํŠธ๋กค๋Ÿฌ๋‚˜ ์˜ˆ์™ธ๋งˆ๋‹ค ์„œ๋กœ ๋‹ค๋ฅธ ์‘๋‹ต ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•ด์•ผ ํ•˜๋ฏ€๋กœ @ExceptionHandler ์‚ฌ์šฉ ๊ถŒ์žฅ

ExceptionResolver

HandlerExceptionResolver

public interface HandlerExceptionResolver {
    ModelAndView resolveException(
        HttpServletRequest request, 
        HttpServletResponse response, 
        Object handler, // ํ•ธ๋“ค๋Ÿฌ(์ปจํŠธ๋กค๋Ÿฌ) ์ •๋ณด
        Exception ex // ํ•ธ๋“ค๋Ÿฌ(์ปจํŠธ๋กค๋Ÿฌ)์—์„œ ๋ฐœ์ƒํ•œ ์˜ˆ์™ธ
    ); 
}
  • ์Šคํ”„๋ง MVC ๋Š” ์ปจํŠธ๋กค๋Ÿฌ(ํ•ธ๋“ค๋Ÿฌ) ๋ฐ–์œผ๋กœ ์˜ˆ์™ธ๊ฐ€ ๋˜์ ธ์ง„ ๊ฒฝ์šฐ ์˜ˆ์™ธ๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ , ๋™์ž‘์„ ์ƒˆ๋กœ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋Š” HandlerExceptionResolver ์ œ๊ณต

    • ExceptionResolver ์ ์šฉ ์ „ ์˜ˆ์™ธ์ฒ˜๋ฆฌ

      Result
    • ExceptionResolver ์ ์šฉ ํ›„ ์˜ˆ์™ธ์ฒ˜๋ฆฌ

      Result
  • ์ฐธ๊ณ . ExceptionResolver ๋กœ ์˜ˆ์™ธ๋ฅผ ํ•ด๊ฒฐํ•ด๋„ postHandle() ํ˜ธ์ถœ X

HandlerExceptionResolver Interface ๊ตฌํ˜„

@Slf4j
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
    /**
     * ModelAndView ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ด์œ ๋Š” try-catch ์ฒ˜๋Ÿผ Exception ์„ ์ฒ˜๋ฆฌํ•ด์„œ ์ •์ƒ ํ๋ฆ„์ฒ˜๋Ÿผ ๋ณ€๊ฒฝํ•˜์—ฌ ์˜ˆ์™ธ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒƒ์ด ๋ชฉ์ 
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        try {
            // IllegalArgumentException ๋ฐœ์ƒ ์‹œ ์˜ˆ์™ธ๋ฅผ HTTP ์ƒํƒœ ์ฝ”๋“œ 400์œผ๋กœ ์ „๋‹ฌ
            if (ex instanceof IllegalArgumentException) {
                log.info("IllegalArgumentException resolver to 400");
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
                
                //1. ๋นˆ ModelAndView ๋ฐ˜ํ™˜ ์‹œ ๋ทฐ ๋ Œ๋”๋ง์„ ํ•˜์ง€ ์•Š๊ณ  ์ •์ƒ ํ๋ฆ„์œผ๋กœ ์„œ๋ธ”๋ฆฟ ๋ฐ˜ํ™˜
                //2. ModelAndView ์— View, Model ๋“ฑ์˜ ์ •๋ณด๋ฅผ ์ง€์ •ํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋ทฐ ๋ Œ๋”๋ง
                return new ModelAndView();
            }
        } catch (IOException e) {
            log.error("resolver ex", e);
        }

        //3. null ๋ฐ˜ํ™˜ ์‹œ ๋‹ค์Œ ExceptionResolver ์ฐพ์•„์„œ ์‹คํ–‰
        //์ฒ˜๋ฆฌ ๊ฐ€๋Šฅํ•œ ExceptionResolver ๊ฐ€ ์—†์„ ๊ฒฝ์šฐ ๊ธฐ์กด ๋ฐœ์ƒํ•œ ์˜ˆ์™ธ๋ฅผ ์„œ๋ธ”๋ฆฟ ๋ฐ–์œผ๋กœ ์ „๋‹ฌ
        return null;
    }
}

.

HandlerExceptionResolver ๋ฐ˜ํ™˜ ๊ฐ’์— ๋”ฐ๋ฅธ DispatcherServlet ๋™์ž‘ ๋ฐฉ์‹

  • ๋นˆ ModelAndView

    • new ModelAndView() ์ฒ˜๋Ÿผ ๋นˆ ModelAndView ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋ทฐ๋ฅผ ๋ Œ๋”๋ง ํ•˜์ง€์•Š๊ณ , ์ •์ƒ ํ๋ฆ„์œผ๋กœ ์„œ๋ธ”๋ฆฟ์ด ๋ฆฌํ„ด

  • ModelAndView ์ง€์ •

    • ModelAndView ์— View, Model ๋“ฑ์˜ ์ •๋ณด๋ฅผ ์ง€์ •ํ•ด์„œ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋ทฐ๋ฅผ ๋ Œ๋”๋ง

  • nul

    • null ์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด, ๋‹ค์Œ ExceptionResolver ๋ฅผ ์ฐพ์•„์„œ ์‹คํ–‰

    • ๋งŒ์•ฝ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ExceptionResolver ๊ฐ€ ์—†์œผ๋ฉด ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ฐ€ ์•ˆ๋˜๊ณ , ๊ธฐ์กด์— ๋ฐœ์ƒํ•œ ์˜ˆ์™ธ๋ฅผ ์„œ๋ธ”๋ฆฟ ๋ฐ–์œผ๋กœ ๋˜์ง

.

ExceptionResolver ํ™œ์šฉ

  • ์˜ˆ์™ธ ์ƒํƒœ ์ฝ”๋“œ ๋ณ€ํ™˜

    • ์˜ˆ์™ธ๋ฅผ response.sendError(..) ํ˜ธ์ถœ๋กœ ๋ณ€๊ฒฝํ•ด์„œ ์„œ๋ธ”๋ฆฟ์—์„œ ์ƒํƒœ ์ฝ”๋“œ์— ๋”ฐ๋ฅธ ์˜ค๋ฅ˜๋ฅผ ์ฒ˜๋ฆฌํ•˜๋„๋ก ์œ„์ž„

    • ์ดํ›„ WAS ๋Š” ์„œ๋ธ”๋ฆฟ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€๋ฅผ ์ฐพ์•„์„œ ๋‚ด๋ถ€ ํ˜ธ์ถœ(default. /error)

    • ex. ์‹ค์ œ ์„œ๋ฒ„์—์„œ๋Š” 500 ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€์ง€๋งŒ Client ์—๊ฒŒ๋Š” 4xx ์ฝ”๋“œ ์ „๋‹ฌ

  • ๋ทฐ ํ…œํ”Œ๋ฆฟ ์ฒ˜๋ฆฌ

    • ModelAndView ์— ๊ฐ’์„ ์ฑ„์›Œ์„œ ์˜ˆ์™ธ์— ๋”ฐ๋ฅธ ์ƒˆ๋กœ์šด ์˜ค๋ฅ˜ ํ™”๋ฉด์„ ๋ทฐ ๋ Œ๋”๋ง์„ ํ†ตํ•ด ๊ณ ๊ฐ์—๊ฒŒ ์ œ๊ณต

    • return new ModelAndView("error/400");

  • API ์‘๋‹ต ์ฒ˜๋ฆฌ

    • response.getWriter().println("hello"); ์ฒ˜๋Ÿผ HTTP ์‘๋‹ต ๋ฐ”๋””์— ์ง์ ‘ ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด์„œ ์ „๋‹ฌ

    • ์—ฌ๊ธฐ์— JSON ์œผ๋กœ ์‘๋‹ตํ•˜๋ฉด API ์‘๋‹ต ์ฒ˜๋ฆฌ

.

WebMvcConfigurer ์— ๋“ฑ๋ก

@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
    resolvers.add(new MyHandlerExceptionResolver());
}
  • configureHandlerExceptionResolvers ์‚ฌ์šฉ ์‹œ ์Šคํ”„๋ง์ด ๊ธฐ๋ณธ์œผ๋กœ ๋“ฑ๋กํ•˜๋Š” ExceptionResolver ๊ฐ€ ์ œ๊ฑฐ๋˜๋ฏ€๋กœ extendHandlerExceptionResolvers ๋ฅผ ์‚ฌ์šฉ

ExceptionResolver ์ ์šฉ

Exception

public class UserException extends RuntimeException {
    public UserException() {
        super();
    }

    public UserException(String message) {
        super(message);
    }

    public UserException(String message, Throwable cause) {
        super(message, cause);
    }

    public UserException(Throwable cause) {
        super(cause);
    }

    protected UserException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

...

// ์˜ˆ์™ธ ์ƒ์„ฑ
throw new UserException("์‚ฌ์šฉ์ž ์˜ค๋ฅ˜");

HandlerExceptionResolver ๊ตฌํ˜„

@Slf4j
public class UserHandlerExceptionResolver implements HandlerExceptionResolver {
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        try {
            if (ex instanceof UserException) {
                log.info("UserException resolver to 400");
                String acceptHeader = request.getHeader("accept");
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);

                // HTTP ์š”์ฒญ ํ•ด๋”์˜ ACCEPT ๊ฐ’์ด application/json ์ผ ๊ฒฝ์šฐ JSON ์‘๋‹ต
                if ("application/json".equals(acceptHeader)) {
                    Map<String, Object> errorResult = new HashMap<>();
                    errorResult.put("ex", ex.getClass());
                    errorResult.put("message", ex.getMessage());
                    String result = objectMapper.writeValueAsString(errorResult);
                    response.setContentType("application/json");
                    response.setCharacterEncoding("utf-8");
                    response.getWriter().write(result);

                    return new ModelAndView();
                } else { // ๊ทธ ์™ธ์˜ ๊ฒฝ์šฐ์—๋Š” TEXT/HTML ์˜ค๋ฅ˜ ํŽ˜์ด์ง€ ๋…ธ์ถœ
                    return new ModelAndView("error/500");
                }
            }
        } catch (IOException e) {
            log.error("resolver ex", e);
        }

        return null;
    }
}

WebConfig ์— HandlerExceptionResolver ๋“ฑ๋ก

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

์ •๋ฆฌ.

์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ด๋„ ExceptionResolver ์—์„œ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌ

์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ด๋„ ์„œ๋ธ”๋ฆฟ ์ปจํ…Œ์ด๋„ˆ๊นŒ์ง€ ์˜ˆ์™ธ๊ฐ€ ์ „๋‹ฌ๋˜์ง€ ์•Š๊ณ (๊ฒฐ๊ณผ์ ์œผ๋กœ WAS ์ž…์žฅ์—์„œ๋Š” ์ •์ƒ ์ฒ˜๋ฆฌ)

์Šคํ”„๋ง MVC ์—์„œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋Š” ์ข…๋ฃŒ

Spring ExceptionResolver

Spring Boot ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ExceptionResolver

HandlerExceptionResolverComposite ์— ์•„๋ž˜ ์ˆœ์„œ๋กœ ๋“ฑ๋ก

  • ExceptionHandlerExceptionResolver

    • @ExceptionHandler ์ฒ˜๋ฆฌ

    • API ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋Š” ๋Œ€๋ถ€๋ถ„ ์ด ๊ธฐ๋Šฅ์œผ๋กœ ํ•ด๊ฒฐ

  • ResponseStatusExceptionResolver

    • HTTP ์ƒํƒœ ์ฝ”๋“œ ๋ณ€๊ฒฝ

  • DefaultHandlerExceptionResolver

    • ์Šคํ”„๋ง ๋‚ด๋ถ€ ๊ธฐ๋ณธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ

ExceptionHandler ๐ŸŒž

ExceptionHandlerExceptionResolver

์Šคํ”„๋ง์€ API ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด @ExceptionHandler ๋ฅผ ์‚ฌ์šฉํ•œ ํŽธ๋ฆฌํ•œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๊ธฐ๋Šฅ ์ œ๊ณต

  • ๊ฐ ์‹œ์Šคํ…œ๋งˆ๋‹ค ๋‹ค๋ฅธ ์‘๋‹ต ๋ชจ์–‘๊ณผ ์ŠคํŽ™

  • ์˜ˆ์™ธ์— ๋”ฐ๋ฅธ ๊ฐ๊ธฐ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ ์‘๋‹ต

  • ์ปจํŠธ๋กค๋Ÿฌ์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์˜ˆ์™ธ ์‘๋‹ต

  • ModelAndView ๊ฐ€ ์•„๋‹Œ Json ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜

  • ๋“ฑ.. ์„ธ๋ฐ€ํ•œ ์ œ์–ด ํ•„์š”

.

@ExceptionHandler ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•

  • @ExceptionHandler ์„ ์–ธ ํ›„ ํ•ด๋‹น ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‹ถ์€ ์˜ˆ์™ธ ์ง€์ •

  • ํ•ด๋‹น ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ํ•ด๋‹น ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ

  • ์ง€์ •ํ•œ ์˜ˆ์™ธ ๋˜๋Š” ํ•˜์œ„ ์ž์‹ ํด๋ž˜์Šค ๋ชจ๋‘ ์ฒ˜๋ฆฌ

    /**
     * ๋ถ€๋ชจ, ์ž์‹ ํด๋ž˜์Šค ๋ชจ๋‘ ์ง€์ •๋˜์–ด ์žˆ์„ ๊ฒฝ์šฐ ์ž์„ธํ•œ ๊ฒƒ์ด ์šฐ์„ ๊ถŒ
     */
    @ExceptionHandler(๋ถ€๋ชจ์˜ˆ์™ธ.class)
    public String ๋ถ€๋ชจ์˜ˆ์™ธ์ฒ˜๋ฆฌ()(๋ถ€๋ชจ์˜ˆ์™ธ e) {}
    
    @ExceptionHandler(์ž์‹์˜ˆ์™ธ.class)
    public String ์ž์‹์˜ˆ์™ธ์ฒ˜๋ฆฌ()(์ž์‹์˜ˆ์™ธ e) {}
  • ๋‹ค์–‘ํ•œ ์˜ˆ์™ธ๋ฅผ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ

    @ExceptionHandler({AException.class, BException.class})
    public String ex(Exception e) {
        log.info("exception e", e);
    }
  • ์˜ˆ์™ธ ์ƒ๋žต

    /**
     * ์˜ˆ์™ธ ์ƒ๋žต ์‹œ ๋ฉ”์„œ๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ์˜ˆ์™ธ(UserException)๊ฐ€ ์ง€์ •
     */
    @ExceptionHandler
    public ResponseEntity<ErrorResult> userExHandle(UserException e) {}
  • ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ์‘๋‹ต

.

@ExceptionHandler ์‹คํ–‰ ํ๋ฆ„

throw new IllegalArgumentException("์ž˜๋ชป๋œ ์ž…๋ ฅ ๊ฐ’");

...

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandle(IllegalArgumentException e) {
    return new ErrorResult("BAD", e.getMessage());
}
  • ์ปจํŠธ๋กค๋Ÿฌ ํ˜ธ์ถœ ๊ฒฐ๊ณผ๋กœ ์˜ˆ์™ธ(IllegalArgumentException)๊ฐ€ ์ปจํŠธ๋กค๋Ÿฌ ๋ฐ–์œผ๋กœ ๋˜์ ธ์ง

  • DispatcherServlet ์„ ๊ฑฐ์ณ ์˜ˆ์™ธ ๋ฐœ์ƒ์œผ๋กœ ExceptionResolver ์ž‘๋™

    • ๊ฐ€์žฅ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ExceptionHandlerExceptionResolver ์‹คํ–‰

  • ExceptionHandlerExceptionResolver ๋Š” ํ•ด๋‹น ์ปจํŠธ๋กค๋Ÿฌ์— IllegalArgumentException ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” @ExceptionHandler ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ

  • @ExceptionHandler ์„ ์–ธ ๋ฉ”์„œ๋“œ ์‹คํ–‰

    • @RestController ์ด๋ฏ€๋กœ @ResponseBody ์ ์šฉ โž” HTTP ์ปจ๋ฒ„ํ„ฐ๊ฐ€ ์‚ฌ์šฉ๋˜๊ณ  JSON ์‘๋‹ต

  • @ResponseStatus(HttpStatus.BAD_REQUEST) ๋ฅผ ์ง€์ •ํ–ˆ์œผ๋ฏ€๋กœ HTTP ์ƒํƒœ ์ฝ”๋“œ 400 ์‘๋‹ต

    • ์„œ๋ธ”๋ฆฟ ์ปจํ…Œ์ด๋„ˆ๊นŒ์ง€ ๋‚ด๋ ค๊ฐ€์ง€ ์•Š๊ณ  ์ •์ƒ ํ๋ฆ„์œผ๋กœ ๋ฐ˜ํ™˜

.

์ƒํ™ฉ์— ๋”ฐ๋ฅธ @ExceptionHandler ํ™œ์šฉ

/**
 * ์—์™ธ ์ฒ˜๋ฆฌ์šฉ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ
 * ํ˜„์žฌ Controller ์—์„œ IllegalArgumentException ๋ฐœ์ƒ ์‹œ ํ˜ธ์ถœ
 * 
 * @ResponseStatus ๋Š” ์• ๋…ธํ…Œ์ด์…˜์ด๋ฏ€๋กœ HTTP ์‘๋‹ต ์ฝ”๋“œ๋ฅผ ๋™์ ์œผ๋กœ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€
 */
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandle(IllegalArgumentException e) {
    return new ErrorResult("BAD", e.getMessage());
}

/**
 * ํ˜„์žฌ Controller ์—์„œ UserException ๋ฐœ์ƒ ์‹œ ํ˜ธ์ถœ
 * 
 * ResponseEntity ๋ฅผ ์‚ฌ์šฉํ•ด์„œ HTTP ๋ฉ”์‹œ์ง€ ๋ฐ”๋””์— ์ง์ ‘ ์‘๋‹ต(HTTP ์ปจ๋ฒ„ํ„ฐ ์‚ฌ์šฉ)
 * HTTP ์‘๋‹ต ์ฝ”๋“œ๋ฅผ ํ”„๋กœ๊ทธ๋ž˜๋ฐํ•ด์„œ ๋™์ ์œผ๋กœ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ
 */
@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandle(UserException e) {
    ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
    return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
}

/**
 * ํ˜„์žฌ Controller ์—์„œ RuntimeException(Exception ์˜ ์ž์‹ ํด๋ž˜์Šค) ๋ฐœ์ƒ ์‹œ ํ˜ธ์ถœ
 * 
 * ์ฒ˜๋ฆฌ๋˜์ง€ ๋ชปํ•œ ๋‚จ์€ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌ
 */
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ErrorResult exHandle(Exception e) {
    return new ErrorResult("EX", "๋‚ด๋ถ€ ์˜ค๋ฅ˜");
}

/**
 * ModelAndView ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์˜ค๋ฅ˜ ํ™”๋ฉด(HTML) ์‘๋‹ต
 */
@ExceptionHandler(ViewException.class)
public ModelAndView ex(ViewException e) {
    return new ModelAndView("error");
}

@ControllerAdvice ๐ŸŒž

์—ฌ๋Ÿฌ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ค๋ฅ˜๋ฅผ ๋ชจ์•„์„œ ์ฒ˜๋ฆฌ

  • @ExceptionHandler ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์˜ˆ์™ธ๋ฅผ ๊น”๋”ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ์ •์ƒ ์ฝ”๋“œ์™€ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์ฝ”๋“œ๊ฐ€ ํ•˜๋‚˜์˜ ์ปจํŠธ๋กค๋Ÿฌ์— ์„ž์—ฌ ์žˆ๋Š” ๋‹จ์ ์ด ์กด์žฌ

  • @ControllerAdvice ๋˜๋Š” @RestControllerAdvice ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ถ„๋ฆฌํ•ด ๋ณด์ž.

@ControllerAdvice

@RestControllerAdvice
public class ExControllerAdvice {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public ErrorResult illegalExHandle(IllegalArgumentException e) {
        return new ErrorResult("BAD", e.getMessage());
    }

    @ExceptionHandler
    public ResponseEntity<ErrorResult> userExHandle(UserException e) {
        ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
        return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler
    public ErrorResult exHandle(Exception e) {
        return new ErrorResult("EX", "๋‚ด๋ถ€ ์˜ค๋ฅ˜");
    }
}
  • ๋Œ€์ƒ์œผ๋กœ ์ง€์ •ํ•œ ์—ฌ๋Ÿฌ ์ปจํŠธ๋กค๋Ÿฌ์— @ExceptionHandler, @InitBinder ๊ธฐ๋Šฅ์„ ๋ถ€์—ฌ

    • ๋Œ€์ƒ ์ปจํŠธ๋กค๋Ÿฌ ์ง€์ • โž” ๋ณดํ†ต ํŒจํ‚ค์ง€๋ช… ์ •๋„ ์ง€์ •

      // Target all Controllers annotated with @RestController
      @ControllerAdvice(annotations = RestController.class)
      public class ExampleAdvice1 {}
      
      // Target all Controllers within specific packages
      @ControllerAdvice("org.example.controllers")
      public class ExampleAdvice2 {}
      
      // Target all Controllers assignable to specific classes
      @ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
      public class ExampleAdvice3 {}
  • @ControllerAdvice ์— ๋Œ€์ƒ์„ ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ๋ชจ๋“  ์ปจํŠธ๋กค๋Ÿฌ์— ์ ์šฉ(๊ธ€๋กœ๋ฒŒ ์ ์šฉ)

  • @RestControllerAdvice ๋Š” @ControllerAdvice ์™€ ๋™์ผํ•˜๊ณ , @ResponseBody ๊ฐ€ ์ถ”๊ฐ€

    • @Controller, @RestController ์ฐจ์ด์™€ ๋™์ผ

@ExceptionHandler ์™€ @ControllerAdvice ๋ฅผ ์กฐํ•ฉํ•˜๋ฉด ์˜ˆ์™ธ๋ฅผ ๊น”๋”ํ•˜๊ฒŒ ํ•ด๊ฒฐ ๊ฐ€๋Šฅ

ResponseStatusExceptionResolver

์˜ˆ์™ธ์— ๋”ฐ๋ผ HTTP ์ƒํƒœ ์ฝ”๋“œ ์ง€์ •

  • @ResponseStatus ๊ฐ€ ๋‹ฌ๋ ค์žˆ๋Š” ์˜ˆ์™ธ

  • ResponseStatusException ์˜ˆ์™ธ

.

@ResponseStatus ์˜ˆ์™ธ

@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "์ž˜๋ชป๋œ ์š”์ฒญ ์˜ค๋ฅ˜")
public class BadRequestException extends RuntimeException { }

...

throw new BadRequestException();
  • ํ•ด๋‹น ์˜ˆ์™ธ๊ฐ€ ์ปจํŠธ๋กค๋Ÿฌ ๋ฐ–์œผ๋กœ ๋„˜์–ด๊ฐ€๋ฉด ResponseStatusExceptionResolver ์˜ˆ์™ธ๊ฐ€ ํ•ด๋‹น ์• ๋…ธํ…Œ์ด์…˜์„ ํ™•์ธํ•ด์„œ HTTP ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝ(HttpStatus.BAD_REQUEST(400))ํ•˜๊ณ , ๋ฉ”์‹œ์ง€ ํฌํ•จ

    • ResponseStatusExceptionResolver ์—์„œ response.sendError(statusCode, resolvedReason) ํ˜ธ์ถœ

  • sendError(400) ํ˜ธ์ถœ๋กœ WAS ์—์„œ ๋‹ค์‹œ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€(/error) ๋‚ด๋ถ€ ์š”์ฒญ

  • reason ์„ MessageSource ์—์„œ ์ฐพ๋Š” ๋ฉ”์„ธ์ง€ ๊ธฐ๋Šฅ ์ œ๊ณต โž” reason = "error.bad"

`

ResponseStatusException ์˜ˆ์™ธ

throw new ResponseStatusException(HttpStatus.NOT_FOUND, "error.bad", new IllegalArgumentException());
  • ์ง์ ‘ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋Š” ์˜ˆ์™ธ์— ResponseStatusException ์ ์šฉ

    • @ResponseStatus ๋Š” ์• ๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์ง์ ‘ (์กฐ๊ฑด์— ๋”ฐ๋ผ ๋™์ ์œผ๋กœ)๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋Š” ์˜ˆ์™ธ์—๋Š” ์ ์šฉ ๋ถˆ๊ฐ€

DefaultHandlerExceptionResolver

  • ์Šคํ”„๋ง ๋‚ด๋ถ€์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์Šคํ”„๋ง ์˜ˆ์™ธ ์ฒ˜๋ฆฌ

    • ์Šคํ”„๋ง ๋‚ด๋ถ€ ์˜ค๋ฅ˜๋ฅผ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ• ์ง€์— ๋Œ€ํ•œ ๋งŽ์€ ๋‚ด์šฉ์ด ์ •์˜

  • TypeMismatchException ์œผ๋กœ ๋ฐœ์ƒํ•˜๋Š” 500 ์˜ค๋ฅ˜๋ฅผ DefaultHandlerExceptionResolver ๊ฐ€ 400 ์˜ค๋ฅ˜๋กœ ๋ณ€๊ฒฝ

DefaultHandlerExceptionResolver.handleTypeMismatch

protected ModelAndView handleTypeMismatch(TypeMismatchException ex, HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {
  // response.sendError() ๋ฅผ ํ†ตํ•ด ๋ฌธ์ œ ํ•ด๊ฒฐ.
  // sendError(400) ๋ฅผ ํ˜ธ์ถœํ–ˆ์œผ๋ฏ€๋กœ WAS ์—์„œ ๋‹ค์‹œ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€(/error) ๋‚ด๋ถ€ ์š”์ฒญ
  response.sendError(HttpServletResponse.SC_BAD_REQUEST);
  return new ModelAndView();
}

Last updated