Home

Published

- 31 min read

[ Spring ] Spring MVC에서의 Exception Handling

img of [ Spring ] Spring MVC에서의 Exception Handling

다음은 스프링 공식 사이트에서 게시한 스프링 블로그에서 작성한 글을 번역한 내용입니다.(정리가 너무 잘 되어 있더라구요: 링크)

Spring Boot

Spring Boot를 사용하면 최소한의 구성으로 Spring 프로젝트를 설정할 수 있으며, 애플리케이션이 몇 년 전보다 최신이라면 이를 사용하고 있을 가능성이 높습니다.

Spring MVC는 기본적으로 (대체) 오류 페이지를 제공하지 않습니다. 기본 오류 페이지를 설정하는 가장 일반적인 방법은 항상 SimpleMappingExceptionResolver였습니다(실제로 Spring V1부터). 이에 대해서는 나중에 논의하겠습니다.

그러나 Spring Boot는 대체 오류 처리 페이지를 제공합니다.

시작 시, Spring Boot는 /error에 대한 매핑을 찾으려고 시도합니다. 관례상, /error로 끝나는 URL은 같은 이름의 논리적 뷰인 error에 매핑됩니다. 데모 애플리케이션에서 이 뷰는 차례로 error.html Thymeleaf 템플릿에 매핑됩니다. (JSP를 사용하는 경우, InternalResourceViewResolver 설정에 따라 error.jsp에 매핑될 것입니다). 실제 매핑은 여러분이나 Spring Boot가 설정한 ViewResolver(있는 경우)에 따라 달라집니다.

/error에 대한 ViewResolver 매핑을 찾을 수 없는 경우, Spring Boot는 자체 대체 오류 페이지를 정의합니다 - 이른바 “Whitelabel 오류 페이지”(HTTP 상태 정보와 잡히지 않은 예외의 메시지 같은 오류 세부 정보만 포함된 최소한의 페이지)입니다. 샘플 애플리케이션에서 error.html 템플릿의 이름을 예를 들어 error2.html로 변경한 후 재시작하면 이것이 사용되는 것을 볼 수 있습니다.

RESTful 요청을 하는 경우(HTTP 요청이 HTML 이외의 원하는 응답 유형을 지정한 경우) Spring Boot는 “Whitelabel” 오류 페이지에 넣는 것과 동일한 오류 정보의 JSON 표현을 반환합니다.

   $> curl -H "Accept: application/json" http://localhost:8080/no-such-page 

{"timestamp":"2018-04-11T05:56:03.845+0000","status":404,"error":"Not Found","message":"No message available","path":"/no-such-page"}

Spring Boot는 또한 컨테이너에 대한 기본 오류 페이지를 설정합니다. 이는 web.xml의 <error-page> 지시문과 동등합니다(비록 매우 다르게 구현되었지만). Spring MVC 프레임워크 외부에서 발생한 예외, 예를 들어 서블릿 필터에서 발생한 예외도 여전히 Spring Boot 대체 오류 페이지에 의해 보고됩니다. 샘플 애플리케이션은 이에 대한 예시도 보여줍니다.

Spring Boot 오류 처리에 대한 더 깊이 있는 논의는 이 글의 끝 부분에서 찾을 수 있습니다.

이 글의 나머지 부분은 Spring Boot를 사용하든 사용하지 않든 Spring을 사용하는 경우에 적용됩니다.

참을성 없는 REST 개발자들은 사용자 정의 REST 오류 응답 섹션으로 바로 건너뛰기로 선택할 수 있습니다. 그러나 그 후에는 전체 글을 읽어야 합니다. 대부분의 내용이 REST이든 아니든 모든 웹 애플리케이션에 동일하게 적용되기 때문입니다.

Using HTTP Status Codes

일반적으로 웹 요청을 처리할 때 처리되지 않은 예외가 발생하면 서버가 HTTP 500 응답을 반환하게 됩니다. 그러나 직접 작성한 예외에는 @ResponseStatus 어노테이션을 달 수 있습니다(이는 HTTP 명세에 정의된 모든 HTTP 상태 코드를 지원합니다). 어노테이션이 달린 예외가 컨트롤러 메서드에서 발생하고 다른 곳에서 처리되지 않으면, 자동으로 지정된 상태 코드와 함께 적절한 HTTP 응답이 반환되도록 합니다.

예를 들어, 다음은 주문을 찾을 수 없는 경우의 예외입니다.

   @ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order")  // 404
 public class OrderNotFoundException extends RuntimeException {
     // ...
 }

그리고 다음은 이를 사용하는 컨트롤러 메서드입니다.

   @RequestMapping(value="/orders/{id}", method=GET) 
public String showOrder(@PathVariable("id") long id, Model model) { 
	Order order = orderRepository.findOrderById(id); 
	
	if (order == null) throw new OrderNotFoundException(id);
	
	model.addAttribute(order); 
	return "orderDetail"; 
}

이 메서드가 처리하는 URL에 알 수 없는 주문 ID가 포함된 경우 익숙한 HTTP 404 응답이 반환될 것입니다.

Controller Based Exception Handling

Using @ExceptionHandler

같은 컨트롤러 내의 요청 처리(@RequestMapping) 메서드에서 발생한 예외를 특별히 처리하기 위해 모든 컨트롤러에 추가 (@ExceptionHandler) 메서드를 추가할 수 있습니다. 이러한 메서드는 다음과 같은 작업을 수행할 수 있습니다:

@ResponseStatus 어노테이션이 없는 예외 처리(일반적으로 여러분이 작성하지 않은 미리 정의된 예외) 사용자를 전용 오류 뷰로 리다이렉트 완전히 사용자 정의된 오류 응답 구축 다음 컨트롤러는 이 세 가지 옵션을 보여줍니다:

   @Controller
public class ExceptionHandlingController {

  // @RequestHandler methods
  ...

  // Exception handling methods

  // Convert a predefined exception to an HTTP Status code
  @ResponseStatus(value=HttpStatus.CONFLICT,
                  reason="Data integrity violation")  // 409
  @ExceptionHandler(DataIntegrityViolationException.class)
  public void conflict() {
    // Nothing to do
  }

  // Specify name of a specific view that will be used to display the error:
  @ExceptionHandler({SQLException.class,DataAccessException.class})
  public String databaseError() {
    // Nothing to do.  Returns the logical view name of an error page, passed
    // to the view-resolver(s) in usual way.
    // Note that the exception is NOT available to this view (it is not added
    // to the model) but see "Extending ExceptionHandlerExceptionResolver"
    // below.
    return "databaseError";
  }

  // Total control - setup a model and return the view name yourself. Or
  // consider subclassing ExceptionHandlerExceptionResolver (see below).
  @ExceptionHandler(Exception.class)
  public ModelAndView handleError(HttpServletRequest req, Exception ex) {
    logger.error("Request: " + req.getRequestURL() + " raised " + ex);

    ModelAndView mav = new ModelAndView();
    mav.addObject("exception", ex);
    mav.addObject("url", req.getRequestURL());
    mav.setViewName("error");
    return mav;
  }
}

이러한 메서드에서 추가 처리를 선택할 수 있습니다 - 가장 일반적인 예는 예외를 로깅하는 것입니다.

핸들러 메서드는 유연한 시그니처를 가지고 있어 HttpServletRequest, HttpServletResponse, HttpSession 및/또는 Principal과 같은 명백한 서블릿 관련 객체를 전달할 수 있습니다.

중요 참고사항: Model은 @ExceptionHandler 메서드의 매개변수가 될 수 없습니다. 대신, 위의 handleError()에서 보여진 것처럼 ModelAndView를 사용하여 메서드 내에서 모델을 설정하세요.

Exception and views

모델에 예외를 추가할 때는 주의해야 합니다. 사용자들은 Java 예외 세부 정보와 스택 트레이스가 포함된 웹 페이지를 보고 싶어 하지 않습니다. 오류 페이지에 어떤 예외 정보도 넣지 말라는 보안 정책이 있을 수 있습니다. 이는 Spring Boot의 화이트라벨 오류 페이지를 반드시 재정의해야 하는 또 다른 이유입니다.

예외가 테스트를 돕기 위해 페이지 소스에 주석으로 숨겨져 있도록 하는 것이 유용할 수 있습니다. JSP를 사용하는 경우, 예외와 해당 스택 트레이스를 출력하기 위해 다음과 같은 작업을 수행할 수 있습니다(숨겨진 <div>를 사용하는 것도 다른 옵션입니다).

   <h1>Error Page</h1> 
<p>Application has encountered an error. Please contact support on ...</p> 

<!-- 
	Failed URL: ${url} 
	Exception: ${exception.message} 
	<c:forEach items="${exception.stackTrace}" var="ste"> ${ste} 
	</c:forEach> 
-->

Global Exception Handling

using @ControllerAdvice Classes

@ControllerAdvice를 사용하면 정확히 동일한 예외 처리 기술을 사용할 수 있지만 개별 컨트롤러가 아닌 전체 애플리케이션에 적용할 수 있습니다. 이를 어노테이션 기반 인터셉터로 생각할 수 있습니다.

@ControllerAdvice 어노테이션이 붙은 모든 클래스는 전역적인 예외 처리와 바인딩 설정 등을 담당하는 특별한 컴포넌트가 되며, 이 클래스 내에서는 세 가지 유형의 메서드를 지원합니다.

  1. @ExceptionHandler로 어노테이션이 달린 예외 처리 메서드.
  2. @ModelAttribute로 어노테이션이 달린 모델 강화 메서드 (모델에 추가 데이터를 추가하기 위한 메서드). 이러한 속성들은 예외 처리 뷰에서는 사용할 수 없다는 점에 유의하세요.
  3. @InitBinder로 어노테이션이 달린 바인더 초기화 메서드 (폼 처리 구성에 사용됨).

우리는 예외 처리만 살펴볼 것입니다 - @ControllerAdvice 메서드에 대한 더 자세한 내용은 온라인 매뉴얼을 검색하세요.

위에서 본 예외 핸들러 중 어느 것이든 컨트롤러 어드바이스 클래스에 정의할 수 있습니다 - 그러나 이제 그들은 모든 컨트롤러에서 던져진 예외에 적용됩니다. 다음은 간단한 예입니다:

   @ControllerAdvice
class GlobalControllerExceptionHandler {
    @ResponseStatus(HttpStatus.CONFLICT)  // 409
    @ExceptionHandler(DataIntegrityViolationException.class)
    public void handleConflict() {
        // Nothing to do
    }
}

모든 예외에 대한 기본 핸들러를 갖고 싶다면 약간의 꼬임이 있습니다. 어노테이션이 달린 예외가 프레임워크에 의해 처리되도록 해야 합니다. 코드는 다음과 같습니다:

   @ControllerAdvice
class GlobalDefaultExceptionHandler {
    public static final String DEFAULT_ERROR_VIEW = "error";

    @ExceptionHandler(value = Exception.class)
    public ModelAndView
    defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
        // If the exception is annotated with @ResponseStatus rethrow it and let
        // the framework handle it - like the OrderNotFoundException example
        // at the start of this post.
        // AnnotationUtils is a Spring Framework utility class.
        if (AnnotationUtils.findAnnotation
                    (e.getClass(), ResponseStatus.class) != null)
            throw e;

        // Otherwise setup and send the user to a default error-view.
        ModelAndView mav = new ModelAndView();
        mav.addObject("exception", e);
        mav.addObject("url", req.getRequestURL());
        mav.setViewName(DEFAULT_ERROR_VIEW);
        return mav;
    }
}

Going Deeper

HandlerExceptionResolver

DispatcherServlet의 애플리케이션 컨텍스트에서 선언된 Spring 빈 중 HandlerExceptionResolver를 구현한 모든 빈은 MVC 시스템에서 발생하고 컨트롤러에서 처리되지 않은 모든 예외를 가로채고 처리하는 데 사용됩니다. 인터페이스는 다음과 같습니다:

   public interface HandlerExceptionResolver {
    ModelAndView resolveException(HttpServletRequest request, 
            HttpServletResponse response, Object handler, Exception ex);
}

여기서 handler는 예외를 생성한 컨트롤러를 참조합니다 (@Controller 인스턴스는 Spring MVC가 지원하는 핸들러 유형 중 하나일 뿐입니다. 예를 들어, HttpInvokerExporterWebFlow Executor도 핸들러 유형입니다).

내부적으로 MVC는 기본적으로 세 가지 Resolver를 생성합니다. 이러한 리졸버들이 위에서 논의한 동작을 구현합니다

  1. ExceptionHandlerExceptionResolver는 처리되지 않은 예외를 핸들러(컨트롤러)와 컨트롤러 어드바이스 모두에 있는 적절한 @ExceptionHandler 메서드와 매칭합니다.
  2. ResponseStatusExceptionResolver@ResponseStatus로 어노테이션이 달린 처리되지 않은 예외를 찾습니다 (섹션 1에서 설명한 대로).
  3. DefaultHandlerExceptionResolver는 표준 Spring 예외를 변환하여 HTTP 상태 코드로 만듭니다 (이는 Spring MVC 내부적인 것이므로 위에서 언급하지 않았습니다).

이들은 체인으로 연결되어 나열된 순서대로 처리됩니다 - 내부적으로 Spring은 이를 수행하기 위해 전용 빈(HandlerExceptionResolverComposite)을 생성합니다.

resolveException 메서드의 시그니처에 Model이 포함되어 있지 않다는 점에 주목하세요. 이것이 @ExceptionHandler 메서드에 모델을 주입할 수 없는 이유입니다.

원한다면 자신만의 HandlerExceptionResolver를 구현하여 자신만의 사용자 정의 예외 처리 시스템을 설정할 수 있습니다. 핸들러는 일반적으로 Spring의 Ordered 인터페이스를 구현하므로 핸들러가 실행되는 순서를 정의할 수 있습니다.

SimpleMappingExceptionResolver

Spring은 오랫동안 HandlerExceptionResolver의 간단하지만 편리한 구현을 제공해 왔으며, 여러분의 애플리케이션에서 이미 사용되고 있을 수 있습니다 - SimpleMappingExceptionResolver입니다. 이는 다음과 같은 옵션을 제공합니다:

  • 예외 클래스 이름을 뷰 이름에 매핑 - 패키지 없이 클래스 이름만 지정하면 됩니다.
  • 다른 곳에서 처리되지 않은 모든 예외에 대한 기본(대체) 오류 페이지 지정.
  • 메시지 로깅 (기본적으로 활성화되어 있지 않음).
  • 모델에 추가할 예외 속성의 이름 설정, 이를 통해 뷰 내에서 사용할 수 있습니다 (예: JSP와 같은). 기본적으로 이 속성의 이름은 exception입니다. null로 설정하여 비활성화할 수 있습니다. @ExceptionHandler 메서드에서 반환된 뷰는 예외에 접근할 수 없지만 SimpleMappingExceptionResolver에 정의된 뷰는 접근할 수 있다는 점을 기억하세요.

다음은 Java Configuration을 사용한 일반적인 구성입니다:

   @Configuration
@EnableWebMvc  // Optionally setup Spring MVC defaults (if you aren't using
               // Spring Boot & haven't specified @EnableWebMvc elsewhere)
public class MvcConfiguration extends WebMvcConfigurerAdapter {
  @Bean(name="simpleMappingExceptionResolver")
  public SimpleMappingExceptionResolver
                  createSimpleMappingExceptionResolver() {
    SimpleMappingExceptionResolver r =
                new SimpleMappingExceptionResolver();

    Properties mappings = new Properties();
    mappings.setProperty("DatabaseException", "databaseError");
    mappings.setProperty("InvalidCreditCardException", "creditCardError");

    r.setExceptionMappings(mappings);  // None by default
    r.setDefaultErrorView("error");    // No default
    r.setExceptionAttribute("ex");     // Default is "exception"
    r.setWarnLogCategory("example.MvcLogger");     // No default
    return r;
  }
  ...
}

또는 XML Configuration을 사용한 경우:

   <bean id="simpleMappingExceptionResolver" class=
   "org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
  <property name="exceptionMappings">
    <map>
       <entry key="DatabaseException" value="databaseError"/>
       <entry key="InvalidCreditCardException" value="creditCardError"/>
    </map>
  </property>

  <!-- See note below on how this interacts with Spring Boot -->
  <property name="defaultErrorView" value="error"/>
  <property name="exceptionAttribute" value="ex"/>

  <!-- Name of logger to use to log exceptions. Unset by default, 
         so logging is disabled unless you set a value. -->
  <property name="warnLogCategory" value="example.MvcLogger"/>
</bean>

defaultErrorView 속성은 특히 유용합니다. 이는 처리되지 않은 모든 예외가 적절한 애플리케이션 정의 오류 페이지를 생성하도록 보장합니다. (대부분의 애플리케이션 서버의 기본값은 Java 스택 트레이스를 표시하는 것입니다 - 이는 사용자가 절대 보면 안 되는 것입니다). Spring Boot는 “화이트라벨” 오류 페이지를 통해 동일한 작업을 수행하는 또 다른 방법을 제공합니다.

Extending SimpleMappingExceptionResolver

SimpleMappingExceptionResolver를 확장하는 것은 몇 가지 이유로 꽤 일반적입니다:

  • 생성자를 사용하여 속성을 직접 설정할 수 있습니다 - 예를 들어 예외 로깅을 활성화하고 사용할 로거를 설정합니다.
  • buildLogMessage를 재정의하여 기본 로그 메시지를 재정의할 수 있습니다. 기본 구현은 항상 다음과 같은 고정 텍스트를 반환합니다: Handler execution resulted in exception
  • doResolveException을 재정의하여 오류 뷰에 추가 정보를 사용할 수 있게 만들 수 있습니다.

예를 들어:

   public class MyMappingExceptionResolver extends SimpleMappingExceptionResolver {
  public MyMappingExceptionResolver() {
    // Enable logging by providing the name of the logger to use
    setWarnLogCategory(MyMappingExceptionResolver.class.getName());
  }

  @Override
  public String buildLogMessage(Exception e, HttpServletRequest req) {
    return "MVC exception: " + e.getLocalizedMessage();
  }

  @Override
  protected ModelAndView doResolveException(HttpServletRequest req,
        HttpServletResponse resp, Object handler, Exception ex) {
    // Call super method to get the ModelAndView
    ModelAndView mav = super.doResolveException(req, resp, handler, ex);

    // Make the full URL available to the view - note ModelAndView uses
    // addObject() but Model uses addAttribute(). They work the same. 
    mav.addObject("url", request.getRequestURL());
    return mav;
  }
}

이 코드는 데모 애플리케이션에서 ExampleSimpleMappingExceptionResolver로 있습니다.

Extending ExceptionHandlerExceptionResolver

ExceptionHandlerExceptionResolver를 확장하고 doResolveHandlerMethodException 메서드를 같은 방식으로 재정의하는 것도 가능합니다. 이는 거의 동일한 시그니처를 가지고 있습니다 (새로운 HandlerMethod를 Handler 대신 받습니다).

이것이 사용되도록 하려면, 상속된 order 속성을 설정해야 합니다 (예를 들어 새 클래스의 생성자에서). MAX_INT보다 작은 값으로 설정하여 기본 ExceptionHandlerExceptionResolver 인스턴스보다 먼저 실행되도록 합니다 (Spring이 생성한 인스턴스를 수정/교체하려고 하는 것보다 자신만의 핸들러 인스턴스를 생성하는 것이 더 쉽습니다). 더 자세한 내용은 데모 앱의 ExampleExceptionHandlerExceptionResolver를 참조하세요.

Errors and REST

RESTful GET 요청도 예외를 생성할 수 있으며, 우리는 이미 표준 HTTP 오류 응답 코드를 반환하는 방법을 보았습니다. 그러나 오류에 대한 정보를 반환하고 싶다면 어떻게 해야 할까요? 이는 매우 쉽게 할 수 있습니다. 먼저 오류 클래스를 정의합니다:

   public class ErrorInfo {
    public final String url;
    public final String ex;

    public ErrorInfo(String url, Exception ex) {
        this.url = url;
        this.ex = ex.getLocalizedMessage();
    }
}

이제 핸들러에서 @ResponseBody로 인스턴스를 반환할 수 있습니다:

   @ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MyBadDataException.class)
@ResponseBody ErrorInfo
handleBadRequest(HttpServletRequest req, Exception ex) {
    return new ErrorInfo(req.getRequestURL(), ex);
}

What to Use When?

Spring은 늘 그렇듯이 선택의 여지를 제공하므로, 무엇을 해야 할까요? 다음은 몇 가지 기본 규칙입니다. 그러나 XML 구성이나 어노테이션에 대한 선호도가 있다면, 그것도 괜찮습니다.

  • 직접 작성한 예외의 경우, @ResponseStatus를 추가하는 것을 고려하세요.
  • 다른 모든 예외에 대해서는 @ControllerAdvice 클래스에 @ExceptionHandler 메서드를 구현하거나 SimpleMappingExceptionResolver의 인스턴스를 사용하세요. 애플리케이션에 이미 SimpleMappingExceptionResolver가 구성되어 있을 수 있으며, 이 경우 @ControllerAdvice를 구현하는 것보다 새로운 예외 클래스를 추가하는 것이 더 쉬울 수 있습니다.
  • 컨트롤러 특정 예외 처리를 위해서는 컨트롤러에 @ExceptionHandler 메서드를 추가하세요.

경고: 같은 애플리케이션에서 이러한 옵션을 너무 많이 혼합하지 않도록 주의하세요. 같은 예외가 둘 이상의 방식으로 처리될 수 있다면, 원하는 동작을 얻지 못할 수 있습니다. 컨트롤러의 @ExceptionHandler 메서드는 항상 @ControllerAdvice 인스턴스의 메서드보다 먼저 선택됩니다. 컨트롤러 어드바이스가 처리되는 순서는 정의되어 있지 않습니다.

Sample Application

데모 애플리케이션은 github에서 찾을 수 있습니다. 이 애플리케이션은 Spring Boot와 Thymeleaf를 사용하여 간단한 웹 애플리케이션을 구축합니다.

이 애플리케이션은 두 번 개정되었으며(2014년 10월, 2018년 4월) (희망컨대) 더 나아지고 이해하기 쉬워졌습니다. 기본 원리는 동일하게 유지됩니다. Spring Boot V2.0.1과 Spring V5.0.5를 사용하지만, 코드는 Spring 3.x와 4.x에도 적용 가능합니다.

데모는 Cloud Foundry에서 http://mvc-exceptions-v2.cfapps.io/로 실행 중입니다.

About the Demo

이 애플리케이션은 사용자를 5개의 데모 페이지로 안내하며, 다양한 예외 처리 기술을 강조합니다:

  1. 자체 예외를 처리하기 위한 @ExceptionHandler 메서드가 있는 컨트롤러
  2. 전역 ControllerAdvice가 처리할 예외를 던지는 컨트롤러
  3. SimpleMappingExceptionResolver를 사용하여 예외 처리
  4. 비교를 위해 SimpleMappingExceptionResolver가 비활성화된 데모 3과 동일
  5. Spring Boot가 오류 페이지를 생성하는 방법 보여주기

애플리케이션에서 가장 중요한 파일들과 각 데모와의 관계에 대한 설명은 프로젝트의 README.md에서 찾을 수 있습니다.

홈 웹 페이지는 index.html이며 다음과 같습니다:

  • 각 데모 페이지로 링크
  • Spring Boot 엔드포인트로 링크 (Spring Boot에 관심 있는 사람들을 위해, 페이지 하단에 있음)

각 데모 페이지에는 여러 링크가 포함되어 있으며, 모두 의도적으로 예외를 발생시킵니다. 데모 페이지로 돌아가려면 매번 브라우저의 뒤로 가기 버튼을 사용해야 합니다.

Spring Boot 덕분에 이 데모를 Java 애플리케이션으로 실행할 수 있습니다 (내장된 Tomcat 컨테이너를 실행합니다). 애플리케이션을 실행하려면 다음 중 하나를 사용할 수 있습니다 (두 번째는 Spring Boot maven 플러그인 덕분입니다):

   mvn exec:java mvn spring-boot:run

선택은 여러분의 몫입니다. 홈페이지 URL은 http://localhost:8080이 될 것입니다.

Error Page Contents

또한 데모 애플리케이션에서는 HTML 소스에 숨겨진 (주석으로) 스택 트레이스가 포함된 “지원 준비” 오류 페이지를 만드는 방법을 보여줍니다. 이상적으로는 지원팀이 로그에서 이 정보를 얻어야 하지만, 삶이 항상 이상적이지는 않습니다. 이 페이지가 보여주는 것은 기본 오류 처리 메서드인 handleError가 오류 페이지에 추가 정보를 제공하기 위해 자체 ModelAndView를 어떻게 생성하는지입니다. 다음을 참조하세요:

  • github의 ExceptionHandlingController.handleError()
  • github의 GlobalControllerExceptionHandler.handleError()

Spring Boot and Error Handling

Spring Boot를 사용하면 최소한의 구성으로 Spring 프로젝트를 설정할 수 있습니다. Spring Boot는 클래스패스에서 특정 주요 클래스와 패키지를 감지할 때 자동으로 합리적인 기본값을 만듭니다. 예를 들어, 서블릿 환경을 사용하고 있다는 것을 감지하면 가장 일반적으로 사용되는 뷰 리졸버, 핸들러 매핑 등으로 Spring MVC를 설정합니다. JSP 및/또는 Thymeleaf를 감지하면 이러한 뷰 기술을 설정합니다.

Fallback Error Page

Spring Boot는 이 글의 시작 부분에서 설명한 기본 오류 처리를 어떻게 지원할까요?

  • 처리되지 않은 오류가 발생하면 Spring Boot는 내부적으로 /error로 포워딩합니다.
  • Boot는 /error에 대한 모든 요청을 처리하기 위해 BasicErrorController를 설정합니다. 이 컨트롤러는 내부 Model에 오류 정보를 추가하고 error를 논리적 뷰 이름으로 반환합니다.
  • 뷰 리졸버가 구성되어 있다면, 해당하는 error 뷰를 사용하려고 시도할 것입니다.
  • 그렇지 않으면, 전용 View 객체를 사용하여 기본 오류 페이지가 제공됩니다 (사용 중인 뷰 해결 시스템과 독립적으로 만들어집니다).

Spring Boot는 BeanNameViewResolver를 설정하여 /error가 같은 이름의 View에 매핑될 수 있도록 합니다. Boot의 ErrorMvcAutoConfiguration 클래스를 보면 defaultErrorView가 error라는 이름의 빈으로 반환되는 것을 볼 수 있습니다. 이것이 BeanNameViewResolver가 찾는 View 빈입니다.

“Whitelabel” 오류 페이지는 의도적으로 최소화되고 못생겼습니다. 이를 재정의할 수 있습니다:

  1. 오류 템플릿을 정의하여 - 우리 데모에서는 Thymeleaf를 사용하고 있으므로 오류 템플릿은 src/main/resources/templates/error.html에 있습니다 (이 위치는 Spring Boot 속성 spring.thymeleaf.prefix에 의해 설정됩니다 - JSP나 Mustache와 같은 다른 지원되는 서버 사이드 뷰 기술에 대해서도 유사한 속성이 존재합니다).
  2. 서버 사이드 렌더링을 사용하지 않는 경우: 2.1 error라는 이름의 빈으로 자체 오류 View를 정의합니다. 2.2 또는 server.error.whitelabel.enabled 속성을 false로 설정하여 Spring Boot의 “Whitelabel” 오류 페이지를 비활성화합니다. 대신 컨테이너의 기본 오류 페이지가 사용됩니다.

관례상, Spring Boot 속성은 일반적으로 application.properties 또는 application.yml에 설정됩니다.

Integration with SimpleMappingExceptionResolver

이미 SimpleMappingExceptionResolver를 사용하여 기본 오류 뷰를 설정하고 있다면 어떻게 될까요? 간단합니다. setDefaultErrorView()를 사용하여 Spring Boot가 사용하는 것과 동일한 뷰인 error로 정의하세요.

데모에서 SimpleMappingExceptionResolver의 defaultErrorView 속성은 의도적으로 error가 아닌 defaultErrorPage로 설정되어 있어 핸들러가 오류 페이지를 생성하는 시기와 Spring Boot가 책임지는 시기를 볼 수 있다는 점에 유의하세요. 일반적으로는 둘 다 error로 설정될 것입니다.

Container-Wide Exception Handling

서블릿 필터와 같이 Spring Framework 외부에서 발생한 예외도 Spring Boot의 대체 오류 페이지에 의해 보고됩니다.

이를 위해 Spring Boot는 컨테이너에 대한 기본 오류 페이지를 등록해야 합니다. Servlet 2에서는 web.xml에 추가할 수 있는 <error-page> 지시문이 있습니다. 안타깝게도 Servlet 3은 이에 대한 Java API 등가물을 제공하지 않습니다. 대신 Spring Boot는 다음과 같이 합니다:

  • 내장 컨테이너가 있는 Jar 애플리케이션의 경우, 컨테이너 특정 API를 사용하여 기본 오류 페이지를 등록합니다.
  • 전통적인 WAR 파일로 배포된 Spring Boot 애플리케이션의 경우, 서블릿 필터를 사용하여 더 아래에서 발생한 예외를 잡아 처리합니다.