千家信息网

Spring MVC异常解析器的原理是什么

发表于:2025-01-17 作者:千家信息网编辑
千家信息网最后更新 2025年01月17日,本篇内容主要讲解"Spring MVC异常解析器的原理是什么",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"Spring MVC异常解析器的原理是什么"吧!
千家信息网最后更新 2025年01月17日Spring MVC异常解析器的原理是什么

本篇内容主要讲解"Spring MVC异常解析器的原理是什么",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"Spring MVC异常解析器的原理是什么"吧!

使用介绍

一般自定义异常处理策略有两种方式

  1. 使用@ExceptionHandler注解

  2. 实现HandlerExceptionResolver接口

因为@ExceptionHandler注解的方式已经足够强大,所以我们一般也很少通过实现HandlerExceptionResolver来自定义异常处理策略。

简单介绍一下@ExceptionHandler的使用,后面会结合这些例子进行源码分析

@RestController @RequestMapping("location") public class LocationController {   @RequestMapping("getLocationInfo")  public String index() {   int sum = 10 / 0;   return "locationInfo";  }   @ExceptionHandler(RuntimeException.class)  public String processRuntimeException() {   return "LocationController -> 发生RuntimeException";  }   @ExceptionHandler(Exception.class)  public String processException() {   return "LocationController -> 发生Exception";  } }

访问如下链接,返回结果为

http://localhost:8080/location/getLocationInfo LocationController -> 发生RuntimeException

把processRuntimeException方法注释掉以后,再次访问上面的链接,结果为

LocationController -> 发生Exception

如果在每个Controller里面都写异常解析器还是很麻烦的,能不能在一个地方统一处理异常呢?当然可以,这时候就不得不用到@RestControllerAdvice或者@ControllerAdvice

写如下的全局异常解析器

@RestControllerAdvice public class MyExceptionHandler {   @ExceptionHandler(RuntimeException.class)  public String processRuntimeException() {   return "MyExceptionHandler -> 发生RuntimeException";  }   @ExceptionHandler(Exception.class)  public String processException() {   return "MyExceptionHandler -> 发生RuntimeException";  } }

访问上面的链接,返回结果为

LocationController -> 发生Exception

我们把LocationController类的processException方法也注释掉,此时LocationController类里面已经没有被@ExceptionHandler注解标记的方法了

访问上面的链接,返回结果为

MyExceptionHandler -> 发生RuntimeException

把MyExceptionHandler中的processRuntimeException方法注释掉访问上面的链接,返回结果为

MyExceptionHandler -> 发生Exception

通过以上的例子,我们可以得出如下结论

  1. @RestControllerAdvice或者@ControllerAdvice类内的解析器的优先级低于@RequestMapping类的解析器的优先级

  2. 如果一个异常能被多个解析器所处理,则选择继承关系最近的解析器

假设BizException继承自NullPointException A方法解析BizException B方法解析NullPointException C方法解析Exception

BizException会被A方法解析 NullPointException会被B方法解析 如果没有A方法,则BizException会被B方法解析,如果B方法也没有,则被C方法解析,不难理解哈

@RestControllerAdvice和@ControllerAdvice有什么区别呢?

名字上就可以猜出@RestControllerAdvice只是在@ControllerAdvice的基础上加了@ResponseBody注解,看一波源码也确实如此。所以@RestControllerAdvice类最终返回的是JSON,@ControllerAdvice最终返回的是视图。如果你不明白为什么加了@ResponseBody注解最终返回的内容为JSON,建议看一下返回值处理器相关的内容

源码分析

异常解析器接口定义如下

public interface HandlerExceptionResolver {   // 将异常封装为ModelAndView后返回  @Nullable  ModelAndView resolveException(    HttpServletRequest request, HttpServletResponse response,     @Nullable Object handler, Exception ex);  }

Spring MVC默认的异常解析器存放在如下属性中

@Nullable private List handlerExceptionResolvers;

顺序依次为

  • ExceptionHandlerExceptionResolver

  • ResponseStatusExceptionResolver

  • DefaultHandlerExceptionResolver

UML图如下

Order接口是用来排序的哈,Spring MVC默认的解析器不是通过Order接口来控制顺序的,因为默认的解析器都继承自AbstractHandlerExceptionResolver,并且都没有重写getOrder方法

对Spring MVC比较清楚的小伙伴应该都知道DispatcherServlet属性的默认实现都定义在源码包的DispatcherServlet.properties文件中,List的顺序也是按这个来的。放一部分内容

org.springframework.web.servlet.HandlerAdapter=     org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\  org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\  org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter  org.springframework.web.servlet.HandlerExceptionResolver=     org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\  org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\  org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionReso

接下来分析这3个默认的HandlerExceptionResolver

ExceptionHandlerExceptionResolver

ExceptionHandlerExceptionResolver用于支持@ExceptionHandler,而@ExceptionHandler应该是我们最常的,方便我们自定义异常处理策略,比通过实现HandlerExceptionResolver接口的方式简单

从AbstractHandlerMethodExceptionResolver#shouldApplyTo可以看到

@Override protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {  if (handler == null) {   // handler为空,交给父类去判断   // 默认该逻辑返回true   return super.shouldApplyTo(request, null);  }  else if (handler instanceof HandlerMethod) {   HandlerMethod handlerMethod = (HandlerMethod) handler;   handler = handlerMethod.getBean();   // 交给父类判断   return super.shouldApplyTo(request, handler);  }  else {   // 不支持   return false;  } }

只有当handler为空或者handler的类型为HandlerMethod时(@RequestMapping返回的类型为HandlerMethod)才会执行后面的异常解析逻辑。所以你通过实现Controller接口或者实现HttpRequestHandler接口定义的Handler,这个注解是不起作用的

@ExceptionHandler的处理过程主要和下面2个类有关系ExceptionHandlerExceptionResolver,ExceptionHandlerMethodResolver

用几个成员变量说一下处理过程,就不贴过多的代码了

ExceptionHandlerExceptionResolver

// 省略了继承和实现关系 public class ExceptionHandlerExceptionResolver {   @Nullable  private HandlerMethodArgumentResolverComposite argumentResolvers;   @Nullable  private HandlerMethodReturnValueHandlerComposite returnValueHandlers;   private List> messageConverters;    // 被@RequestMapping标记的类 -> ExceptionHandlerMethodResolver  private final Map, ExceptionHandlerMethodResolver>   exceptionHandlerCache = new ConcurrentHashMap<>(64);   // 被@ControllerAdvice注解标记的类 -> ExceptionHandlerMethodResolver  private final Map  exceptionHandlerAdviceCache = new LinkedHashMap<>(); }

可以看到ExceptionHandlerExceptionResolver类定义了自己的参数处理器,返回值处理器,消息转换器。所以你可以通过这些组件反向知道@ExceptionHandler方法支持的参数类型

例如从如下方法可以知道,支持的参数类型为@SessionAttribute,@RequestAttribute等 如果你写个@RequestParam是肯定不会注入进来的

protected List getDefaultArgumentResolvers() {  List resolvers = new ArrayList<>();   // Annotation-based argument resolution  resolvers.add(new SessionAttributeMethodArgumentResolver());  resolvers.add(new RequestAttributeMethodArgumentResolver());   // Type-based argument resolution  resolvers.add(new ServletRequestMethodArgumentResolver());  resolvers.add(new ServletResponseMethodArgumentResolver());  resolvers.add(new RedirectAttributesMethodArgumentResolver());  resolvers.add(new ModelMethodProcessor());   // Custom arguments  if (getCustomArgumentResolvers() != null) {   resolvers.addAll(getCustomArgumentResolvers());  }   return resolvers; }

最重要的4个map来了,ExceptionHandlerExceptionResolver的工作过程主要就是操作这4个map

// 省略了继承和实现关系 public class ExceptionHandlerExceptionResolver {   // 被@RequestMapping标记的类 -> ExceptionHandlerMethodResolver  private final Map, ExceptionHandlerMethodResolver>  exceptionHandlerCache = new ConcurrentHashMap<>(64);   // 被@ControllerAdvice注解标记的类 -> ExceptionHandlerMethodResolver  private final Map  exceptionHandlerAdviceCache = new LinkedHashMap<>();     }

exceptionHandlerCache保存了@RequestMapping对应的ExceptionHandlerMethodResolver,是在执行异常解析的过程中被赋值的

exceptionHandlerAdviceCache保存了@ControllerAdvice对应的 ExceptionHandlerMethodResolver,是在ExceptionHandlerExceptionResolver被初始化的过程中赋值的

而ExceptionHandlerMethodResolver你可以认为只是封装了一下Exception及其对应的Method

以最开始的例子演示,ExceptionHandlerExceptionResolver初始化后

此时exceptionHandlerCache是没有值的 访问如下链接后

http://localhost:8080/location/getLocationInfo

exceptionHandlerCache中的值如下,LocationController及其对应的ExceptionHandlerMethodResolver被放了进来追一下以下方法的执行 ExceptionHandlerExceptionResolver#doResolveHandlerMethodException ExceptionHandlerExceptionResolver#getExceptionHandlerMethod

可以得出我们测试的结论@RestControllerAdvice或者@ControllerAdvice类内的解析器的优先级低于@RequestMapping类的解析器的优先级

总体实现也不难,从exceptionHandlerCache中能找到解析器就返回执行,找不到就从exceptionHandlerAdviceCache中找,这不是就实现了优先级了吗?

接着来看剩下的2个Map

public class ExceptionHandlerMethodResolver {    // 异常 -> 对应的处理方法  private final Map, Method>  mappedMethods = new HashMap<>(16);   // 异常 -> 对应的处理方法  // 这个是基于mappedMethods又做了一次缓存  // 为什么要再做一次缓存呢?  // 是因为根据异常类型获取处理方法的时候,一个异常可能有多个处理方法,即一个异常会从mappedMethods中查出多个处理方法  // 最后返回的是继承关系最近的异常对应的处理方法,所以在查找的时候又做了一次缓存,避免每次查mappedMethods然后取最优值  // 从exceptionLookupCache中就可以直接查到最优的处理方法  private final Map, Method>  exceptionLookupCache = new ConcurrentReferenceHashMap<>(16);   }

@ControllerAdvice的mappedMethods是在ExceptionHandlerExceptionResolver初始化的过程中赋值的

@RequestMapping的mappedMethods是在执行异常解析的过程中被赋值的

而exceptionLookupCache是在异常解析过程中,通过Exception查找Method的过程中基于mappedMethods做的缓存

为什么在查找过程中要再做一次缓存呢?

是因为根据异常类型获取处理方法的时候,一个异常可能有多个处理方法,即一个异常会从mappedMethods中查出多个处理方法,最后返回的是继承关系最近的异常对应的处理方法,所以在查找的时候又做了一次缓存,避免每次查mappedMethods然后取最优值。从exceptionLookupCache中就可以直接查到最优的处理方法

以LocationController为例,查找一次异常后,exceptionLookupCache的值如下

这样当再次发生ArithmeticException异常时就能从exceptionLookupCache找到对应的处理方法

ResponseStatusExceptionResolver

ResponseStatusExceptionResolver和DefaultHandlerExceptionResolver的实现都不是很难,就不进行过多的分析了

ResponseStatusExceptionResolver主要用来处理如下异常

抛出的异常类型继承自ResponseStatusException

抛出的异常类型被@ResponseStatus标记

以一个例子来演示这个处理器的功能

@ResponseStatus(HttpStatus.UNAUTHORIZED) public class UnauthorizedException extends RuntimeException { } @RestController @RequestMapping("shoppingCar") public class ShoppingCarController {   @RequestMapping("getCarInfo")  public String index() {   throw new UnauthorizedException();  } }

访问

http://localhost:8080/shoppingCar/getCarInfo

显示如下

DefaultHandlerExceptionResolver

用来处理一些常见的Http异常,如

400:请求无效 405:请求方法不支持 500:内部服务器错误

执行入口

# DispatcherServlet#processDispatchResult的部分方法 // 处理过程发生了异常 if (exception != null) {  if (exception instanceof ModelAndViewDefiningException) {   logger.debug("ModelAndViewDefiningException encountered", exception);   // 直接使用异常中封装的ModelAndView作为最终的ModelAndView结果   mv = ((ModelAndViewDefiningException) exception).getModelAndView();  }  else {   // 其他异常类型,先获取解析器   Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);   // 通过异常解析器将异常解析为一个错误视图   mv = processHandlerException(request, response, handler, exception);   errorView = (mv != null);  } }

如果整个处理过程发生异常,依次调用DispatcherServlet的成员变量handlerExceptionResolvers的resolveException方法,找到第一个不为null的ModelAndView,然后返回

@Nullable private List handlerExceptionResolvers;

到此,相信大家对"Spring MVC异常解析器的原理是什么"有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

方法 处理 过程 类型 注解 接口 标记 结果 缓存 链接 优先级 内容 多个 是在 支持 例子 处理器 时候 源码 面的 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 服务器远程装系统 从事网络安全需要会代码吗 是否正确认识网络安全 建立客户数据库 勤哲excel服务器权限 天台喜购多网络技术 大数据软件开发工程师要学多久 数据库的术语有哪些 服务器跟机架靠什么固定 保定市莲池区新工软件开发 数据库工程师怎么样 江苏常规软件开发厂家批发价 现如今网络技术专业好就业吗 大学网络安全教育800字 计算机软件开发学习多少钱 迪普网络安全工程师面试问题 导入导出时连接数据库失败 计算机网络技术的卷子 虚拟服务器资源怎么配置 怀柔区咨询网络技术服务介绍 无锡集中式分布式存储数据库 判断数据库是否有重复数据 软件开发项目转包合法 人行联网核查数据库电话 服务器迁移风险 星玛电梯更改服务器时间 数据库开发实验的实验总结 数据库系统恢复策略和方法 田园科技农业互联网 apple新ID登录连接服务器
0