やりたいこと
Spring Boot + Thymeleafでの画面開発において、何らかのエラーが発生した時にステータスコード(4xx、5xx等)に応じて表示するエラー画面をカスタマイズしたい。
Spring Boot Version
2.1.3.RELEASE
BasicErrorController
Spring Boot + Thymeleafでの画面開発は、エラーが発生した時用のエンドポイントが用意されている。エンドポイントとなるコントローラークラスは以下。
@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メソッドをオーバーライドしてそこに動的カスタマイズしたModelAndViewを作成しreturnする。これだけで動的カスタマイズ可能となる。
@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);
}
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;
}
以下のようなイメージ。
@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;
}
}
以上です。