开头

试想一下我们一般怎么统一处理异常呢,答:切面。但抛开切面不讲,如果对每一个controller方法抛出的异常做专门处理,那么着实太费劲了,有没有更好的方法呢?当然有,就是本篇文章接下来要介绍的springmvc的异常处理机制,用到了ControllerAdvice和ExceptionHandler注解,有点切面的感觉哈哈。

1.ExceptionHandlerExceptionResolver

首先从springmvc的异常处理解析器开始讲,当执行完controller方法后,不管有没有异常产生都会调用DispatcherServlet#doDispatch()方法中的processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 方法,接着会判断是否有异常,若无异常则走正常流程,若有异常则需要进行处理 mv = processHandlerException(request, response, handler, exception);  再接着就是遍历spring已经注册的异常处理解析器直到有处理器返回mav

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception { if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
// 执行处理器产生的异常处理
mv = processHandlerException(request, response, handler, exception);
// 是否有异常视图返回
errorView = (mv != null);
}
} // Did the handler return a view to render? 处理程序是否返回要渲染的视图
if (mv != null && !mv.wasCleared()) {
// 渲染视图
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
}
	@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception { // Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
if (exMv != null) {
// 无视图view
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
} throw ex;
}

其中最重要也是最常使用的一个处理器就是ExceptionHandlerExceptionResolver,下面将着重介绍它,先来看看这个类的继承结构图,实现了InitializingBean接口,在这个bean创建完成之前会调用生命周期初始化方法afterPropertiesSet(),这里面包含了对@ControllerAdvice注解的解析,初始化完后的信息供后续解析异常使用。

@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBodyAdvice beans
// 初始化异常注解 @ControllerAdvice
initExceptionHandlerAdviceCache();
} private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Looking for exception mappings: " + getApplicationContext());
} // 解析有@ControllerAdvice注解的bean,并将这个bean构建成ControllerAdviceBean对象
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
// 将ControllerAdviceBean根据order排序
AnnotationAwareOrderComparator.sort(adviceBeans); for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
// mappedMethods 映射不为空
if (resolver.hasExceptionMappings()) {
// 添加到缓存中
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
if (logger.isInfoEnabled()) {
logger.info("Detected @ExceptionHandler methods in " + adviceBean);
}
}
// 若实现了ResponseBodyAdvice接口(暂不介绍)
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
if (logger.isInfoEnabled()) {
logger.info("Detected ResponseBodyAdvice implementation in " + adviceBean);
}
}
}
}

 ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType); 这行代码会解析拥有@ControllerAdvice 注解的class,并且会遍历class中带有 @ExceptionHandler 注解的方法,获取方法注解带有的异常类型,将异常类型和方法放入到mappedMethods中供后面获取,获取的时候若对应处理此异常类型的method有多个,则需要进行排序,选取一个异常类型与method ExceptionHandler注解异常类型最近的一个(深度最小的那个也即是继承关系最少的那个)具体代码如下:

ExceptionHandlerMethodResolver
public class ExceptionHandlerMethodResolver {

	/**
* A filter for selecting {@code @ExceptionHandler} methods.
*/
public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
(AnnotationUtils.findAnnotation(method, ExceptionHandler.class) != null); /**
* 异常类型与方法的映射map
*/
private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16); /**
* 缓存,用来存储先前碰到过的异常类型与处理方法的映射
*/
private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentReferenceHashMap<>(16); /**
* A constructor that finds {@link ExceptionHandler} methods in the given type.
* @param handlerType the type to introspect
*/
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
// 获取并遍历@ExceptionHandler注解的方法
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
addExceptionMapping(exceptionType, method);
}
}
} /**
* Extract exception mappings from the {@code @ExceptionHandler} annotation first,
* and then as a fallback from the method signature itself.
*/
@SuppressWarnings("unchecked")
private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
List<Class<? extends Throwable>> result = new ArrayList<>();
// 将注解ExceptionHandler value值异常添加到result中
detectAnnotationExceptionMappings(method, result);
// 注解值为空的话再去获取参数的异常类型
if (result.isEmpty()) {
for (Class<?> paramType : method.getParameterTypes()) {
if (Throwable.class.isAssignableFrom(paramType)) {
result.add((Class<? extends Throwable>) paramType);
}
}
}
if (result.isEmpty()) {
throw new IllegalStateException("No exception types mapped to " + method);
}
return result;
} protected void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
ExceptionHandler ann = AnnotationUtils.findAnnotation(method, ExceptionHandler.class);
Assert.state(ann != null, "No ExceptionHandler annotation");
result.addAll(Arrays.asList(ann.value()));
} private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
// 将异常类型以及对应的method添加到map中,且异常类型不能有重复否则会报错
Method oldMethod = this.mappedMethods.put(exceptionType, method);
if (oldMethod != null && !oldMethod.equals(method)) {
throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
exceptionType + "]: {" + oldMethod + ", " + method + "}");
}
} /**
* Whether the contained type has any exception mappings.
*/
public boolean hasExceptionMappings() {
return !this.mappedMethods.isEmpty();
} /**
* Find a {@link Method} to handle the given exception.
* Use {@link ExceptionDepthComparator} if more than one match is found.
* @param exception the exception
* @return a Method to handle the exception, or {@code null} if none found
*/
@Nullable
public Method resolveMethod(Exception exception) {
return resolveMethodByThrowable(exception);
} /**
* Find a {@link Method} to handle the given Throwable.
* Use {@link ExceptionDepthComparator} if more than one match is found.
* @param exception the exception
* @return a Method to handle the exception, or {@code null} if none found
* @since 5.0
*/
@Nullable
public Method resolveMethodByThrowable(Throwable exception) {
Method method = resolveMethodByExceptionType(exception.getClass());
if (method == null) {
Throwable cause = exception.getCause();
if (cause != null) {
method = resolveMethodByExceptionType(cause.getClass());
}
}
return method;
} /**
* Find a {@link Method} to handle the given exception type. This can be
* useful if an {@link Exception} instance is not available (e.g. for tools).
* @param exceptionType the exception type
* @return a Method to handle the exception, or {@code null} if none found
*/
@Nullable
public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
Method method = this.exceptionLookupCache.get(exceptionType);
if (method == null) {
method = getMappedMethod(exceptionType);
this.exceptionLookupCache.put(exceptionType, method);
}
return method;
} /**
* Return the {@link Method} mapped to the given exception type, or {@code null} if none.
*/
@Nullable
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
List<Class<? extends Throwable>> matches = new ArrayList<>();
for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
if (mappedException.isAssignableFrom(exceptionType)) {
matches.add(mappedException);
}
}
if (!matches.isEmpty()) {
// exceptionType 到matchs父类异常类型的深度
matches.sort(new ExceptionDepthComparator(exceptionType));
return this.mappedMethods.get(matches.get(0));
}
else {
return null;
}
} }
@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) { // exception为controller方法抛出的异常
// 根据异常及其类型从上述的mappedMethods中获取对应的方法,再获取方法所在的对象 封装成ServletInvocableHandlerMethod
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
if (exceptionHandlerMethod == null) {
return null;
} // 设置参数解析器,主要用来获取方法的参数值的,供后续反射调用方法
if (this.argumentResolvers != null) {
exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
// 设置返回值解析器,当执行完方法后获取返回值,对返回值进行处理 或返回视图或将结果写入到response
if (this.returnValueHandlers != null) {
exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
} ServletWebRequest webRequest = new ServletWebRequest(request, response);
ModelAndViewContainer mavContainer = new ModelAndViewContainer(); try {
if (logger.isDebugEnabled()) {
logger.debug("Invoking @ExceptionHandler method: " + exceptionHandlerMethod);
}
Throwable cause = exception.getCause();
if (cause != null) {
// Expose cause as provided argument as well
// 执行异常处理方法,也就是我们的自定义的异常处理方法
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
}
else {
// Otherwise, just the given exception as-is
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
}
}
catch (Throwable invocationEx) {
// Any other than the original exception is unintended here,
// probably an accident (e.g. failed assertion or the like).
if (invocationEx != exception && logger.isWarnEnabled()) {
logger.warn("Failed to invoke @ExceptionHandler method: " + exceptionHandlerMethod, invocationEx);
}
// Continue with default processing of the original exception...
return null;
} // 根据后续的返回值解析器设置的,将返回值写入到response中了直接返回空的mav
if (mavContainer.isRequestHandled()) {
return new ModelAndView();
}
else {
ModelMap model = mavContainer.getModel();
HttpStatus status = mavContainer.getStatus();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
mav.setViewName(mavContainer.getViewName());
// (this.view instanceof String)
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
return mav;
}
}

exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod); 此方法执行完成后已经完成了异常处理方法的调用,若方法返回值为视图ModelAndView或其他视图类型,则还需要借助视图解析器如InternalResourceViewResolver对视图进行解析渲染,若为其他类型的值则将值写入到response响应中。

2. demo

Controller类方法:

@Controller
@RequestMapping(value = "test")
public class HelloWorldController{ @Data
public static class User {
private String username; private Integer age; private String address;
} @RequestMapping(value = "user/get", method = RequestMethod.POST)
@ResponseBody
public Object testObject(@RequestBody @Valid User user, @RequestParam String address) {
user.setAddress(address);
// 这里特意抛出RuntimeException异常
throw new RuntimeException("this is a exception");
} }

ExceptionHandlerController异常处理类

@ControllerAdvice
@ResponseBody
public class ExceptionHandlerController { @ExceptionHandler(value = Exception.class)
public Object handleException(Exception e) {
return CommonResult.fail("Exception:" + e.getMessage());
} @ExceptionHandler(value = RuntimeException.class)
public Object handlerRuntimeException(Exception e) {
return CommonResult.fail("handlerRuntimeException:" + e.getMessage());
}
}

ExceptionHandlerController类中定义了两个异常处理方法,一个处理Exception异常,一个处理RuntimeException异常,那个根据controller方法抛出的异常RuntimeException再结合上面的分析(RuntimeException到RuntimeException深度为0,RuntimeException到Exception中间继承了一次深度为1)可以得出抛出异常类型的处理方法为handlerRuntimeException 方法。 运行程序结果如下:

结语

初步解析ExceptionHandlerExceptionResolver源码,若写的有误或者有不理解的地方,欢迎指出讨论~

springmvc异常处理解析#ExceptionHandlerExceptionResolver的更多相关文章

  1. SpringMVC异常处理机制详解[附带源码分析]

    目录 前言 重要接口和类介绍 HandlerExceptionResolver接口 AbstractHandlerExceptionResolver抽象类 AbstractHandlerMethodE ...

  2. SpringMVC异常处理机制

    SpringMVC异常处理机制 springMVC会将所有在doDispatch方法中的异常捕获,然后处理.无法处理的异常会抛出给容器处理. 在doDispatch()中调用processDispat ...

  3. SpringMVC视图解析器

    SpringMVC视图解析器 前言 在前一篇博客中讲了SpringMVC的Controller控制器,在这篇博客中将接着介绍一下SpringMVC视 图解析器.当我们对SpringMVC控制的资源发起 ...

  4. SpringMVC视图解析器(转)

    前言 在前一篇博客中讲了SpringMVC的Controller控制器,在这篇博客中将接着介绍一下SpringMVC视图解析器.当我们对SpringMVC控制的资源发起请求时,这些请求都会被Sprin ...

  5. SpringMVC 视图解析器

    SpringMVC 视图解析器 还记得SpringMVC 快速入门中,dispatcher-servlet.xml 配置的视图解析器么.它是SpringMVC 的核心知识点.本章节比较简单,明白视图解 ...

  6. SpringMvc CharacterEncodingFilter 解析 encoding 参数并初始化参数

    SpringMvc CharacterEncodingFilter 解析 encoding 参数并初始化参数:

  7. Spring Boot实践——SpringMVC视图解析

    一.注解说明 在spring-boot+spring mvc 的项目中,有些时候我们需要自己配置一些项目的设置,就会涉及到这三个,那么,他们之间有什么关系呢? 首先,@EnableWebMvc=Web ...

  8. springmvc视图解析

    SpringMVC 视图解析的几种方式: 在视图解析的过程中,需要知道逻辑view的名字,model的名字以访问model和view. 使用jsp进行解析,InternalResourceViewRe ...

  9. 前台JSON对象传给springmvc,解析为map对象

    前台JSON对象传给springmvc,解析为map对象 javascript: $.ajax({ url : url, method : 'post', contentType : 'applica ...

随机推荐

  1. victoriaMetrics无法获取抓取target的问题

    victoriaMetrics无法获取抓取target的问题 问题描述 最近在新环境中部署了一个服务,其暴露的指标路径为:10299/metrics,配置文件如下(名称字段有修改): apiVersi ...

  2. 2.Docker安装

    CentOS Docker 安装 前提条件 目前,CentOS 仅发行版本中的内核支持 Docker.Docker 运行在CentOS 7 (64-bit)上, 要求系统为64位.Linux系统内核版 ...

  3. CentOS配置epel源

    https://opsx.alibaba.com/mirror epel 配置方法 1.备份(如有配置其他epel源) mv /etc/yum.repos.d/epel.repo /etc/yum.r ...

  4. 137_Power BI 自定义矩阵复刻Beyondsoft Calendar

    博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 一.背景 前两天我们用PBI原生的视觉制作了自定义的热力图,今天我们来复刻一个Beyondsoft Calendar 1. ...

  5. vs 快速定位文件

    在进行web开发时,我们经常需要在文件之间进行切换,每次在VS的解决方案中找文件然后打开 非常浪费时间,有没有比较快捷点的方法呢? 1.使用  ReSharper 插件 ReSharper 插件可以在 ...

  6. Spark 3.x Spark Core详解 & 性能优化

    Spark Core 1. 概述 Spark 是一种基于内存的快速.通用.可扩展的大数据分析计算引擎 1.1 Hadoop vs Spark 上面流程对应Hadoop的处理流程,下面对应着Spark的 ...

  7. 如何用 UDP 实现可靠传输?

    作者:小林coding 计算机八股文刷题网站:https://xiaolincoding.com 大家好,我是小林. 我记得之前在群里看到,有位读者字节一面的时候被问到:「如何基于 UDP 协议实现可 ...

  8. MySQL - 锁的分类

    MySQL - 锁的分类 1. 加锁机制 乐观锁 悲观锁 2. 兼容性 共享锁 排他锁 3. 锁粒度 表锁 页锁 行锁 4. 锁模式 记录锁(record-lock) 间隙锁(gap-lock) ne ...

  9. rabbitMq急速安装教程

    背景 我们在工作中很多时候其实也用过mq.但是仅仅只是会用,所以老猫在此想完整地归纳一下mq相关的知识点,在此分享给大家.首先给大家带来的是mq的单机急速安装.操作系统时centos7.(本来想记录到 ...

  10. dotnet core 也能协调分布式事务啦!

    2022 年 5 月 24 日,我们发布了 DBPack v0.1.0 版本,该版本主要 release 了分布式事务功能.在我们的规划里,DBPack 是要支持所有微服务开发语言协调分布式事务的,但 ...