Spring MVC 异常解析器,原理就是这么简单

您所在的位置:网站首页 参数解析器原理图解 Spring MVC 异常解析器,原理就是这么简单

Spring MVC 异常解析器,原理就是这么简单

#Spring MVC 异常解析器,原理就是这么简单| 来源: 网络整理| 查看: 265

使用介绍

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

使用@ExceptionHandler注解实现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/getLocationInfoLocationController -> 发生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

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

@RestControllerAdvice或者@ControllerAdvice类内的解析器的优先级低于@RequestMapping类的解析器的优先级如果一个异常能被多个解析器所处理,则选择继承关系最近的解析器

假设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, 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


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3