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を配置しておくだけでエラー画面の表示が可能となる。
![d.PNG](https://qiita-image-store.s3.amazonaws.com/0/23101/2562832a-a590-a819-a3c6-4230e6d6d079.png)
※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メソッドをオーバーライドしてそこに動的カスタマイズした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;
   }
}
```

以上です。