开头

试想一下我们一般怎么统一处理异常呢,答:切面。但抛开切面不讲,如果对每一个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. API 工程化分享

    概要 本文是学习B站毛剑老师的<API 工程化分享>的学习笔记,分享了 gRPC 中的 Proto 管理方式,Proto 分仓源码方式,Proto 独立同步方式,Proto git sub ...

  2. jQuery其他操作与bootstrap框架

    目录 标签操作 样式操作 位置操作 文本值操作 属性操作 文档处理 事件操作 常见事件 克隆案例 悬浮事件hover() 监听input输入值事件 阻止后续事件 事件委托 动画效果 bootstrap ...

  3. Unity实现”对象池管理器“

    前言:警告!这可能是坨屎,空闲时间写成,仅作娱乐 在Unity中生成或销毁一个物体会占用较大的资源,如果是制作FPS射击游戏,子弹生成更是雪上加霜.所以我自己写了一个PoolManager,不能和网上 ...

  4. 对于vjudge在有些网络下无法打开的问题

    因为有些网络会屏蔽vjudge,所以打开 镜像网址 不行再试试这个:最新镜像网址

  5. SPFA 最短路算法

    SPFA算法 1.什么是spfa算法? SPFA 算法是 Bellman-Ford算法 的队列优化算法的别称,通常用于求含负权边的单源最短路径,以及判负权环.SPFA一般情况复杂度是O(m)O(m) ...

  6. Flink使用Pod Template将状态快照(Checkpoint、Savepoint)存储在NFS

    背景 Flink 版本 1.13.3,使用 native k8s 部署模式,原采用 HDFS 作为状态快照(Checkpoint.Savepoint)的存储地址,但是由于仅使用了其 HDFS 作为状态 ...

  7. JAVA - 启动一个线程是用run()还是start()?

    JAVA - 启动一个线程是用run()还是start()? 启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行.这并不意味着线程就会立即运 ...

  8. Linux静默安装Oracle21C

    Linux静默安装Oracle21C 1.修改主机名及配置hosts [root@localhost ~]# hostname # 查看主机名 [root@localhost ~]# hostname ...

  9. 使用FileSystemWatcher监听文件状态

    更新记录 本文迁移自Panda666原博客,原发布时间:2021年7月2日. 一.FileSystemWatcher类型介绍 在.NET中使用 FileSystemWatcher 类型可以进行监视指定 ...

  10. 第二章、DHCP原理与配置

    目录 一.了解DHCP服务 1DHCP概述: 2DHCP好处 3DHCP的分配方式 二.DHCP工作过程 DHCP租约过程 三.使用 DHCP动态配置主机地址 1DHCP服务优点 2可分配的地址信息主 ...