IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> SpringBoot异常处理 之 如何使用最简单的方式覆盖默认错误页面? -> 正文阅读

[Java知识库]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 解析器。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-25 12:04:45  更:2021-08-25 12:06:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 9:18:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码