springboot情操陶冶-web配置(五)
本文讲讲mvc的异常处理机制,方便查阅以及编写合理的异常响应方式
入口例子
很简单,根据之前的文章,我们只需要复写WebMvcConfigurer接口的异常添加方法即可,如下
1.创建简单的异常处理类,本例针对绑定异常
package com.example.demo.web.validation;
import com.example.demo.web.model.ResEntity;
import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author nanco
* -------------
* resolve bindexception
* -------------
* @create 18/9/9
*/
public class SimpleExceptionResolver extends AbstractHandlerExceptionResolver {
private static final Logger EXCEPTION_LOG = LoggerFactory.getLogger(SimpleExceptionResolver.class);
private final Map<String, List<String>> errorResultMap = new HashMap<>(2);
private final String ERROR_KEY = "error_result";
private Gson gson = new Gson();
@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// only process BindException,unless return null to allow the next handler understanding the exception
if (BindException.class.isInstance(ex)) {
ResEntity resEntity = new ResEntity();
try {
BindException bindException = BindException.class.cast(ex);
List<ObjectError> allErrors = bindException.getAllErrors();
List<String> resMessages = new ArrayList<>(allErrors.size());
allErrors.stream().forEach(error -> {
resMessages.add(error.getDefaultMessage());
});
errorResultMap.put(ERROR_KEY, resMessages);
resEntity.setData(errorResultMap);
response.getOutputStream().write(gson.toJson(resEntity).getBytes());
} catch (IOException e) {
EXCEPTION_LOG.error("process BindException fail.", e);
}
return new ModelAndView();
}
return null;
}
}
2.实现WebMvcConfigurer接口后复写其中的extendHandlerExceptionResolvers()方法
package com.example.demo.web.config;
import com.example.demo.web.validation.SimpleExceptionResolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/**
* @author nanco
* -------------
* color the mvc config
* -------------
* @create 2018/9/5
**/
@Configuration
public class BootWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
// response first
resolvers.add(0, new SimpleExceptionResolver());
}
}
上述简单的代码便会对系统抛出的BindException异常进行针对性的处理,从而返回合乎格式的响应体。当然这只是一小部分,笔者可以稍微从源码的角度来分析下spring的异常机制
源码层
查阅过DispatcherServlet源码的都知道,当出现异常的时候,则会尝试调用HandlerExceptionResolver解析器去根据异常进行视图渲染或者直接返回对应的错误信息。笔者按步骤来进行简单分析,从WebMvcConfigurationSupport入手
1.异常解析器注册
@Bean
public HandlerExceptionResolver handlerExceptionResolver() {
List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
// 优先加载用户自定义的异常解析器,也可通过WebMvcConfigurer来复写
configureHandlerExceptionResolvers(exceptionResolvers);
// 当用户没有复写上述方法后,采取默认的异常解析器
if (exceptionResolvers.isEmpty()) {
addDefaultHandlerExceptionResolvers(exceptionResolvers);
}
// 扩增异常解析器,可见上文中的例子
extendHandlerExceptionResolvers(exceptionResolvers);
HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
composite.setOrder(0);
composite.setExceptionResolvers(exceptionResolvers);
return composite;
}
2.直接看下spring内置的默认异常解析器吧,参考addDefaultHandlerExceptionResolvers()方法
protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
// 1.异常的方法处理,跟@RequestMapping注解的方法调用类似
ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager());
exceptionHandlerResolver.setMessageConverters(getMessageConverters());
exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
exceptionHandlerResolver.setResponseBodyAdvice(
Collections.singletonList(new JsonViewResponseBodyAdvice()));
}
if (this.applicationContext != null) {
exceptionHandlerResolver.setApplicationContext(this.applicationContext);
}
exceptionHandlerResolver.afterPropertiesSet();
exceptionResolvers.add(exceptionHandlerResolver);
// 2.携带@ResponseStatus注解的解析器
ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
responseStatusResolver.setMessageSource(this.applicationContext);
exceptionResolvers.add(responseStatusResolver);
// 3.默认的异常解析器,针对spring的内置异常作下简单的response
exceptionResolvers.add(new DefaultHandlerExceptionResolver());
}
笔者主要关注ExceptionHandlerExceptionResolver和ResponseStatusExceptionResolver解析器,那就分块来简单的讲解把
ExceptionHandlerExceptionResolver
初始化状态的代码就不罗列了,读者直接阅读源码就知道,笔者此处作下初始化的总结
寻找所有的携带
@ControllerAdvice
注解的bean,包装成ExceptionHandlerMethodResolver方法解析器,由此来从中挑选出携带@ExceptionHandler
注解的方法集合对第一条中所得的方法集合,读取其中
@ExceptionHandler
注解的值(Throwable实现类);无则读取对应方法实现了Throwable异常接口的参数集合。即得出exceptionTypes集合对上述的exceptionTypes集合依次与对应的method形成映射,即方便针对指定的异常可以调用相应的方法来返回结果
对上述满足条件的ControllerAdvice ,结合ExceptionHandlerMethodResolver装入exceptionHandlerAdviceCache属性map中
封装参数解析器集合与返回值解析器集合,和处理
@RequestMapping
的操作一样
具体的解析过程,笔者此处点一下,方便与上文对照着看,直接看关键的getExceptionHandlerMethod()方法
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
@Nullable HandlerMethod handlerMethod, Exception exception) {
Class<?> handlerType = null;
if (handlerMethod != null) {
// 获取出现异常类方法的所在类
handlerType = handlerMethod.getBeanType();
// 优先判断如果此类直接返回的是异常类,则尝试寻找解析器
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
if (resolver == null) {
// 查找异常所在类是否有符合的@ExceptionHandler语法方法
resolver = new ExceptionHandlerMethodResolver(handlerType);
this.exceptionHandlerCache.put(handlerType, resolver);
}
// 得到映射的方法
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
}
// For advice applicability check below (involving base packages, assignable types
// and annotation presence), use target class instead of interface-based proxy.
if (Proxy.isProxyClass(handlerType)) {
handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
}
}
// 进入@ControlleAdvice的语法环境了,判断抛异常的所在类,ControllerAdvice是否支持
for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
ControllerAdviceBean advice = entry.getKey();
// 如果@ControllerAdvice注解无任何的属性配置,则默认是支持的
if (advice.isApplicableToBeanType(handlerType)) {
ExceptionHandlerMethodResolver resolver = entry.getValue();
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
}
}
}
return null;
}
最终就是根据Exception的类型找寻符合条件的method,然后按照@RequestMapping
注解的处理方式得到相应的视图对象供视图解析器去渲染
ResponseStatusExceptionResolver
针对携带@ResponseStatus
注解的异常类来返回响应体的,简单的看下代码吧
@Override
@Nullable
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
try {
// 直接返回的是ResponseStatusException类型的异常则直接处理
if (ex instanceof ResponseStatusException) {
return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler);
}
// 读取异常类上携带的@ResponseStatus注解,有则返回结果
ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
if (status != null) {
return resolveResponseStatus(status, request, response, handler, ex);
}
// 递归调用下
if (ex.getCause() instanceof Exception) {
ex = (Exception) ex.getCause();
return doResolveException(request, response, handler, ex);
}
}
catch (Exception resolveEx) {
logger.warn("ResponseStatus handling resulted in exception", resolveEx);
}
// 无符合条件的,直接返回null,调用下一个异常解析器
return null;
}
最终调用的也就是HttpServletResponse#sendError(int statusCode,String reason)方法直接返回结果
DispatcherServlet异常处理逻辑
此处还是贴下重要的代码片段,加深印象,直接查阅processHandlerException()方法
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
....
if (this.handlerExceptionResolvers != null) {
// 对异常解析器集合进行遍历
for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
// ModelAndView对象不为null则直接跳出,否则采取下一个异常解析器
if (exMv != null) {
break;
}
}
}
}
....
温馨提示:
- 根据上述代码的逻辑可见,用户在自定义相应的异常解析器时,需要注意如果满足解析指定的异常,则最后返回不为null的视图对象(return new ModelAndView()),以免其跑至下一个异常解析器,影响服务执行结果。
- 遍历的异常解析器顺序此处提一下,其采取的是简单的ArrayList集合来保持顺序,所以用户如果想自己的异常解析器保持较高的优先级,则可以采取List接口的add(int index, T value)方法添加或者直接实现HandlerExceptionResolver并设置order属性来保持即可
结语
了解异常解析器的加载机制以及运行逻辑,方便我们写出合乎spring逻辑的代码,以此保证代码的整洁性。
springboot情操陶冶-web配置(五)的更多相关文章
- springboot情操陶冶-web配置(七)
参数校验通常是OpenApi必做的操作,其会对不合法的输入做统一的校验以防止恶意的请求.本文则对参数校验这方面作下简单的分析 spring.factories 读者应该对此文件加以深刻的印象,很多sp ...
- springboot情操陶冶-web配置(九)
承接前文springboot情操陶冶-web配置(八),本文在前文的基础上深入了解下WebSecurity类的运作逻辑 WebSecurityConfigurerAdapter 在剖析WebSecur ...
- springboot情操陶冶-web配置(四)
承接前文springboot情操陶冶-web配置(三),本文将在DispatcherServlet应用的基础上谈下websocket的使用 websocket websocket的简单了解可见维基百科 ...
- springboot情操陶冶-web配置(二)
承接前文springboot情操陶冶-web配置(一),在分析mvc的配置之前先了解下其默认的错误界面是如何显示的 404界面 springboot有个比较有趣的配置server.error.whit ...
- springboot情操陶冶-web配置(三)
承接前文springboot情操陶冶-web配置(二),本文将在前文的基础上分析下mvc的相关应用 MVC简单例子 直接编写一个Controller层的代码,返回格式为json package com ...
- springboot情操陶冶-web配置(一)
承接前文springboot情操陶冶-@SpringBootApplication注解解析,在前文讲解的基础上依次看下web方面的相关配置 环境包依赖 在pom.xml文件中引入web依赖,炒鸡简单, ...
- springboot情操陶冶-web配置(八)
本文关注应用的安全方面,涉及校验以及授权方面,以springboot自带的security板块作为讲解的内容 实例 建议用户可直接路由至博主的先前博客spring security整合cas方案.本文 ...
- springboot情操陶冶-web配置(六)
本文则针对数据库的连接配置作下简单的分析,方便笔者理解以及后续的查阅 栗子当先 以我们经常用的mybatis数据库持久框架来操作mysql服务为例 环境依赖 1.JDK v1.8+ 2.springb ...
- springboot情操陶冶-@SpringBootApplication注解解析
承接前文springboot情操陶冶-@Configuration注解解析,本文将在前文的基础上对@SpringBootApplication注解作下简单的分析 @SpringBootApplicat ...
随机推荐
- 在虚拟机中安装Centos系统
1.首先下载VMware 2.然后可以去http://mirrors.aliyun.com下载映像ISO 3.打开VM,点击创建新的虚拟机 4.选择典型模式 5.稍后安装操作系统 6.选择你所要安装的 ...
- 如何查找MySQL中查询慢的SQL语句(转载)
转载自https://www.cnblogs.com/qmfsun/p/4844472.html 如何在mysql查找效率慢的SQL语句呢?这可能是困然很多人的一个问题,MySQL通过慢查询日志定位那 ...
- Java和js操作json
Js中 Json字符串转json对象 //将json格式的字符串转为json对象 var t = JSON.parse('{"name":123}'); alert(t.name) ...
- 基于jQuery的控件:弹框
★页面展示 ★属性 属性 值 说明 默认值 div Object jQuery对象 $('body') width Number 控件的宽度 auto height Number 控件的高度 auto ...
- kali linux 网络渗透测试学习笔记(一)Nmap工具进行端口扫描
一.利用Nmap扫描网站服务器的端口开放情况 首先打开我们的kali linux才做系统,再打开其命令行模式,输入:nmap www.csdn.net 如下图所示: 因此,通过这个结果可以表明csdn ...
- [Swift]LeetCode162. 寻找峰值 | Find Peak Element
A peak element is an element that is greater than its neighbors. Given an input array nums, where nu ...
- bilibili源码泄漏后,程序员们从代码里扒出来的彩蛋
昨天bilibili又上热搜了,被某人在github上开了个账号,传了份整个后端代码到github,这是被人扒光了衣服看个精光啊. 这件事情,作为程序员的我们除了调侃和fork的同时,想一想,造成这个 ...
- Android--Task和BackStack高级
前言 之前的博客讲到了Android下Activity的启动模式,涉及到了Task和BackStack的内容,对这些不熟悉的朋友,可以先去看看:Android--Activity的启动模式.这篇博客主 ...
- PHP_D4_“简易聊天室 ”的具体技术实现
上面已经介绍了系统的关键技术,下面对具体实现进行详解: 1.开发时,经常需要利用一个配置文件来存储系统的参数,例如:数据库连接信息等.这样可以提高系统的可移植性,当系统的配置发生变化时,例如:更改服务 ...
- 知其所以然~redis的原子性
原子性 原子性是数据库的事务中的特性.在数据库事务的情景下,原子性指的是:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节. 对于Redis而言,命 ...