Spring Boot DefaultErrorViewResolver를 확장하여 오류 화면을 동적으로 사용자 지정

하고 싶은 일



Spring Boot + Thymeleaf에서의 화면 개발에서 어떤 오류가 발생했을 때 상태 코드 (4xx, 5xx 등)에 따라 표시하는 오류 화면을 사용자 정의하고 싶습니다.

Spring Boot Version



2.1.3.RELEASE

BasicErrorController



Spring Boot + Thymeleaf에서의 화면 개발은 에러가 발생했을 때의 엔드포인트가 준비되어 있다. 엔드포인트가 되는 컨트롤러 클래스는 이하.

org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.java
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
@RequestMapping("${server.error.path:${error.path:/error}}")  의 기술에 있는 바와 같이, application.yaml(properties)에 「server.error.path」로 에러 화면의 URL을 기술해 두면 그 URL이 엔드 포인트가 된다. 설정되어 있지 않으면 디폴트로 「/error」가 엔드포인트가 된다(${error.path:/error}의 기술대로).
그 때문에, 개발자가 에러용 컨트롤러를 만들지 않아도, 이하와 같은 구성으로 에러 화면 HTML을 배치해 두는 것만으로 에러 화면의 표시가 가능해진다.

http://localhost:8080/error -> 5xx.html
http://localhost:8080/error/404 -> 4xx.html

오류 화면 동적 사용자 정의



다만 이 경우, 준비되어 있는 에러 HTML은 정적이기 때문에, 예를 들면 「세션으로부터 로그인 정보를 꺼내 에러 화면 헤더, 꼬리말에 표시한다」 「DB로부터 어느 값을 꺼내 XXX 하고 에러 화면 에 표시한다」와 같이, 동적으로 커스터마이즈하고 싶다고 하는 요구도 나온다.

그런 때는 이하의 요령으로 확장해 본다.
BasicErrorController#resolveErrorView(request, response, status, model)에서 부모 클래스인 AbstractErrorController의 resolveErrorView를 호출하고 있으며, 그 안의 resolver.resolveErrorView(request, status, model)에서 ErrorResolver의 resolveErrorView를 호출하고 있다. resolver는 디폴트로 「org.springframework.boot.autoconfigure.web.servlet.errorDefaultErrorViewResolver」가 이용되기 때문에, 이 클래스를 extends한 Resolver(여기서는 CustomeErrorViewResolver라고 명명)를 작성해, resolveErrorView 메소드를 오버라이드(override) 해 거기에 동적 커스터마이즈 된 ModelAndView를 작성하고 return합니다. 이것만으로 동적 커스터마이즈 가능하다.

org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.java
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request,
        HttpServletResponse response) {
    HttpStatus status = getStatus(request);
    Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
            request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
    response.setStatus(status.value());
    ModelAndView modelAndView = resolveErrorView(request, response, status, model);
    return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}

org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController.java
protected ModelAndView resolveErrorView(HttpServletRequest request,
        HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
    for (ErrorViewResolver resolver : this.errorViewResolvers) {
        ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
        if (modelAndView != null) {
            return modelAndView;
        }
    }
    return null;
}

다음과 같은 이미지.

CustomeErrorViewResolver.java
@Component
public class CustomeErrorViewResolver extends DefaultErrorViewResolver {

    /**
     * Create a new {@link DefaultErrorViewResolver} instance.
     * @param applicationContext the source application context
     * @param resourceProperties resource properties
     */
    public CustomeErrorViewResolver(ApplicationContext applicationContext,ResourceProperties resourceProperties) {
        super(applicationContext, resourceProperties);
    }

    @Override
    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        final ModelAndView mav = super.resolveErrorView(request, status, model);
        if (status.is4xxClientError()) {
            // 4XX系エラーの時の処理
       } else if (status.is5xxServerError()) {
            // 5XX系エラーの時の処理
        }
        return mav;
   }
}

이상입니다.

좋은 웹페이지 즐겨찾기