BackEnd/스프링 MVC

스프링 오류페이지

연향동큰손 2025. 1. 7. 14:20

스프링 부트는 기본적으로 오류페이지를 제공한다.

 

BasicErrorController ==> 스프링이 기본적으로 제공하는 오류관련 컨트롤러로 기본적인 로직이 모두 개발되어 있다.

 

<BasicErrorController>

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot.autoconfigure.web.servlet.error;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.error.ErrorAttributeOptions.Include;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
    private final ErrorProperties errorProperties;

    public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
        this(errorAttributes, errorProperties, Collections.emptyList());
    }

    public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
        super(errorAttributes, errorViewResolvers);
        Assert.notNull(errorProperties, "ErrorProperties must not be null");
        this.errorProperties = errorProperties;
    }

    @RequestMapping(
        produces = {"text/html"}
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }

    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        HttpStatus status = this.getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return new ResponseEntity(status);
        } else {
            Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
            return new ResponseEntity(body, status);
        }
    }

    @ExceptionHandler({HttpMediaTypeNotAcceptableException.class})
    public ResponseEntity<String> mediaTypeNotAcceptable(HttpServletRequest request) {
        HttpStatus status = this.getStatus(request);
        return ResponseEntity.status(status).build();
    }

    protected ErrorAttributeOptions getErrorAttributeOptions(HttpServletRequest request, MediaType mediaType) {
        ErrorAttributeOptions options = ErrorAttributeOptions.defaults();
        if (this.errorProperties.isIncludeException()) {
            options = options.including(new ErrorAttributeOptions.Include[]{Include.EXCEPTION});
        }

        if (this.isIncludeStackTrace(request, mediaType)) {
            options = options.including(new ErrorAttributeOptions.Include[]{Include.STACK_TRACE});
        }

        if (this.isIncludeMessage(request, mediaType)) {
            options = options.including(new ErrorAttributeOptions.Include[]{Include.MESSAGE});
        }

        if (this.isIncludeBindingErrors(request, mediaType)) {
            options = options.including(new ErrorAttributeOptions.Include[]{Include.BINDING_ERRORS});
        }

        options = this.isIncludePath(request, mediaType) ? options.including(new ErrorAttributeOptions.Include[]{Include.PATH}) : options.excluding(new ErrorAttributeOptions.Include[]{Include.PATH});
        return options;
    }

    protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) {
        boolean var10000;
        switch (this.getErrorProperties().getIncludeStacktrace()) {
            case ALWAYS -> var10000 = true;
            case ON_PARAM -> var10000 = this.getTraceParameter(request);
            case NEVER -> var10000 = false;
            default -> throw new IncompatibleClassChangeError();
        }

        return var10000;
    }

    protected boolean isIncludeMessage(HttpServletRequest request, MediaType produces) {
        boolean var10000;
        switch (this.getErrorProperties().getIncludeMessage()) {
            case ALWAYS -> var10000 = true;
            case ON_PARAM -> var10000 = this.getMessageParameter(request);
            case NEVER -> var10000 = false;
            default -> throw new IncompatibleClassChangeError();
        }

        return var10000;
    }

    protected boolean isIncludeBindingErrors(HttpServletRequest request, MediaType produces) {
        boolean var10000;
        switch (this.getErrorProperties().getIncludeBindingErrors()) {
            case ALWAYS -> var10000 = true;
            case ON_PARAM -> var10000 = this.getErrorsParameter(request);
            case NEVER -> var10000 = false;
            default -> throw new IncompatibleClassChangeError();
        }

        return var10000;
    }

    protected boolean isIncludePath(HttpServletRequest request, MediaType produces) {
        boolean var10000;
        switch (this.getErrorProperties().getIncludePath()) {
            case ALWAYS -> var10000 = true;
            case ON_PARAM -> var10000 = this.getPathParameter(request);
            case NEVER -> var10000 = false;
            default -> throw new IncompatibleClassChangeError();
        }

        return var10000;
    }

    protected ErrorProperties getErrorProperties() {
        return this.errorProperties;
    }
}

 

 

컨트롤러를 스프링에서 기본적으로 제공하기 때문에 개발자는 오류 페이지만 만들어주면 된다.

 

오류 페이지 생성 방법

  1. templates 디렉터리 아래 error 디렉터리 생성
  2. 500.html , 5xx.html, 400.html, 404.html, 4xx.html 등과 같이 오류 코드에 맞는 html 파일 생성

이때 404나 500과 같이 구체적인 것이 4xx나 5xx보다 우선순위가 높다.

 

테스트

 

예외 발생 시키는 컨트롤러

package com.example.exception.servlet;

import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import java.io.IOException;

@Slf4j
@Controller
public class ServletExController {

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

    @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,"500 오류!");
    }
}

 

<500.html>

<!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>

 

 

결과