SpringBoot异常处理 之 如何使用最简单的方式覆盖默认错误页面?

一、用法简介

  前面一篇内容中,我们学习了SpringBoot默认的异常处理机制,但是在实际的工作中,这种方式肯定是无法满足个性化的需求的,如何实现自定义错误页面呢?我们这里将会使用一种最简单的方式来实现,即通过在src/main/resources/templates 目录下创建 error.html 页面实现,具体用法如下:

本篇内容旨在学习该用法背后的实现机制,所以我们先简单演示用法,重点在后面的分析。

  首先,因为这里我们使用到了thymeleaf视图组件,所以需要引入该依赖,如下:

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

  然后,我们创建error.html页面(默认需要使用该名称),内容如下(用户自定义):

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>自定义异常</title>
</head>
<body>
<h1>自定义错误页面!</h1>
<!--SpringBoot默认存储异常信息的key为exception-->
<span th:text="${error}" />
</body>
</html>

  经过上述的简单配置,我们自定义的错误页面就生效了,当访问不存在的页面时,就会出现如下界面:

该页面可以根据需求进行丰富,这里仅为学习其中的原理,所以比较简单。

在这里插入图片描述
  至此,我们就完成了自定义错误页面的配置了,接下来将分析其中的原理。

二、原理分析

  经过前面《SpringBoot默认的处理异常机制,默认错误页面是怎么产生的呢?》内容,我们知道解析视图的地方是DispatcherServlet的processDispatchResult()方法中通过调用render(mv, request, response);方法实现的,在render()方法中,又调用了resolveViewName()方法,进行视图解析,我们这里就从resolveViewName()方法开始分析。

  首先,我们了解一下resolveViewName()方法的实现,如下所示:

@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
		Locale locale, HttpServletRequest request) throws Exception {

	if (this.viewResolvers != null) {
		for (ViewResolver viewResolver : this.viewResolvers) {
			View view = viewResolver.resolveViewName(viewName, locale);
			if (view != null) {
				return view;
			}
		}
	}
	return null;
}

  在resolveViewName()方法中,通过迭代视图解析器,分别调用视图解析器的resolveViewName()方法进行视图解析,如果可以解析到合适的视图,就会直接返回,即后续的解析器将不会再继续处理。

  我们通过debug可以发现,这个时候viewResolvers变量中有如下四个视图解析器:
在这里插入图片描述
  首先,会进入ContentNegotiatingViewResolver视图解析器的resolveViewName()方法进行处理,实现如下:

@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
	RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
	Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
	List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
	if (requestedMediaTypes != null) {
		List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
		View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
		if (bestView != null) {
			return bestView;
		}
	}

	//省略 ……
}

  在resolveViewName()方法中,首先通过调用getCandidateViews()方法获取候选的视图,然后再通过getBestView()方法获取合适的视图。我们这里先分析一下getCandidateViews()方法,实现如下:

private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
		throws Exception {

	List<View> candidateViews = new ArrayList<>();
	if (this.viewResolvers != null) {
		Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
		for (ViewResolver viewResolver : this.viewResolvers) {
			View view = viewResolver.resolveViewName(viewName, locale);
			if (view != null) {
				candidateViews.add(view);
			}
			for (MediaType requestedMediaType : requestedMediaTypes) {
				List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
				for (String extension : extensions) {
					String viewNameWithExtension = viewName + '.' + extension;
					view = viewResolver.resolveViewName(viewNameWithExtension, locale);
					if (view != null) {
						candidateViews.add(view);
					}
				}
			}
		}
	}
	if (!CollectionUtils.isEmpty(this.defaultViews)) {
		candidateViews.addAll(this.defaultViews);
	}
	return candidateViews;
}

  在getCandidateViews()方法中,通过迭代变量viewResolvers中的视图解析器,并调用resolveViewName()方法,筛选可能的视图,这里可用的视图解析器有如下几个:

这个和项目的配置有关系,我这里为了避免干扰,特意创建了一个最简单的项目进行分析,所以仅有如下几个视图解析器。

在这里插入图片描述
  通过这几个视图解析,最终找到以下几个可用的视图,并返回到上层方法中,如下图所示:
在这里插入图片描述
  然后,上述返回的视图,又通过getBestView()方法,找到最佳视图,这里返回了ThymeleafView视图,即我们配置的视图。

  至此,我们配置的视图就生效了,通过后续的处理就渲染到了前端页面。

三、默认的StaticView视图跑哪里去了呢?

  通过前面的分析,我们好像没有看到StaticView视图出现过,那么为什么配置了自定义错误视图后,StaticView视图就消失了呢?带着疑问,我们把自定义视图的“error.html”页面删除了,然后按照上面的过程再debug了一遍代码,发现如下不同:

  首先,在DispatcherServlet对象的resolveViewName()方法中,viewResolvers变量中的视图解析器多了一个BeanNameViewResover实例,如下所示:
在这里插入图片描述
  然后,在ContentNegotiatingViewResolver视图解析器的getCandidateViews()方法中,viewResolvers变量中同样多了一个BeanNameViewResover实例,如下所示:
在这里插入图片描述
  因为这个BeanNameViewResover视图解析器,获取到的候选视图,就有了默认的StaticView视图,如下所示,说明BeanNameViewResover视图解析器加载了该默认视图,而当我们进行了开篇的自定义错误视图配置,就没有了BeanNameViewResover视图解析器实例。
在这里插入图片描述
  至此,我们就需要继续分析为什么BeanNameViewResover视图解析器实例当配置了自定义错误配置视图后就消失了呢?其实,这个需要在SpringBoot初始化的阶段来寻找答案。

  在上一篇博文中,我们学习了默认的一些配置实在ErrorMvcAutoConfiguration类中进行配置的,其中内部还有一个配置子类WhitelabelErrorViewConfiguration ,实现逻辑如下:

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
@Conditional(ErrorTemplateMissingCondition.class)
protected static class WhitelabelErrorViewConfiguration {

	private final StaticView defaultErrorView = new StaticView();

	@Bean(name = "error")
	@ConditionalOnMissingBean(name = "error")
	public View defaultErrorView() {
		return this.defaultErrorView;
	}

	@Bean
	@ConditionalOnMissingBean
	public BeanNameViewResolver beanNameViewResolver() {
		BeanNameViewResolver resolver = new BeanNameViewResolver();
		resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
		return resolver;
	}
}

  当我们看到beanNameViewResolver()方法上的@ConditionalOnMissingBean注解,这个时候我们就该恍然大悟了,究其根本原因,就是因为该注解,因为已经加载了thymeleafViewResolver解析器,所以beanNameViewResolver解析器就不会再加载了。
  当我再次Debug的时候,发现当引入thymeleaf依赖, 但是不创建对应的error页面时,这个时候thymeleaf视图解析器也会被被注入,不过这个时候BeanNameViewResolver也会被注入,因此这应该不是beanNameViewResolver()方法上的@ConditionalOnMissingBean注解生效造成的原因。这时看到类上的@Conditional(ErrorTemplateMissingCondition.class)注解,根据类的命名我们基本上就可以猜到,是因为该注解引起的,当没有对应的错误页面定义时,才会进行WhitelabelErrorViewConfiguration 配置,即注入默认的StaticView 视图和BeanNameViewResolver 解析器。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
SpringBoot提供了一套默认异常处理机制。一旦程序出现异常,SpringBoot会向/error的URL发送请求,并通过BasicErrorController来处理该请求。默认情况下,SpringBoot会跳转到默认显示异常信息的页面来展示异常信息。如果我们希望将所有的异常统一跳转到自定义的错误页面,可以在src/main/resources/templates目录下创建一个名为error.html的页面。通过覆盖默认错误页面,我们可以实现自定义的异常处理。 除了使用SpringBoot默认配置外,还可以通过自定义错误页面来处理异常。我们可以在src/main/resources/templates目录下创建error.html页面,并将其命名为error。通过这种方式,我们可以自定义错误页面的内容和样式来展示异常信息。 在处理异常的过程中,可以关注ErrorMvcAutoConfiguration中的三个关键点。通过对SpringBoot错误处理机制源码的跟踪,我们可以更深入地了解异常处理的实现细节。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [SpringBoot异常处理](https://blog.csdn.net/Linging_24/article/details/126077782)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [SpringBoot 异常处理详解](https://blog.csdn.net/qq_42402854/article/details/91415966)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姠惢荇者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值