SpringBoot统一异常怎么处理和剖析
今天就跟大家聊聊有关Spring Boot 统一异常怎么处理和剖析,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。
创新互联建站-专业网站定制、快速模板网站建设、高性价比江达网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式江达网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖江达地区。费用合理售后完善,十余年实体公司更值得信赖。
话说异常
「欲渡黄河冰塞川,将登太行雪满天」
,无论生活还是计算机世界难免发生异常,上一篇文章RESTful API 返回统一JSON数据格式 说明了统一返回的处理,这是请求一切正常的情形;这篇文章将说明如何统一处理异常,以及其背后的实现原理,老套路,先实现,后说明原理,有了上一篇文章的铺底,相信,理解这篇文章就驾轻就熟了
实现
新建业务异常
新建 BusinessException.class 类表示业务异常,注意这是一个 Runtime 异常
@Data @AllArgsConstructor public final class BusinessException extends RuntimeException { private String errorCode; private String errorMsg; }
添加统一异常处理静态方法
在 CommonResult 类中添加静态方法 errorResult 用于接收异常码和异常消息:
public staticCommonResult errorResult(String errorCode, String errorMsg){ CommonResult commonResult = new CommonResult<>(); commonResult.errorCode = errorCode; commonResult.errorMsg = errorMsg; commonResult.status = -1; return commonResult; }
配置
同样要用到 @RestControllerAdvice
注解,将统一异常添加到配置中:
@RestControllerAdvice("com.example.unifiedreturn.api") static class UnifiedExceptionHandler{ @ExceptionHandler(BusinessException.class) public CommonResulthandleBusinessException(BusinessException be){ return CommonResult.errorResult(be.getErrorCode(), be.getErrorMsg()); } }
三部搞定,到这里无论是 Controller 还是 Service 中,只要抛出 BusinessException, 我们都会返回给前端一个统一数据格式
测试
将 UserController 中的方法进行改造,直接抛出异常:
@GetMapping("/{id}") public UserVo getUserById(@PathVariable Long id){ throw new BusinessException("1001", "根据ID查询用户异常"); }
浏览器中输入: http://localhost:8080/users/1
在 Service 中抛出异常:
@Service public class UserServiceImpl implements UserService { /** * 根据用户ID查询用户 * * @param id * @return */ @Override public UserVo getUserById(Long id) { throw new BusinessException("1001", "根据ID查询用户异常"); } }
运行是得到同样的结果,所以我们尽可能的抛出异常吧 (作为一个程序猿这种心理很可拍)
解剖实现过程
解剖这个过程是相当纠结的,为了更好的说(yin)明(wei)问(wo)题(lan),我要说重中之重了,真心希望看该文章的童鞋自己去案发现场发现线索 还是在 WebMvcConfigurationSupport 类中实例化了 HandlerExceptionResolver Bean
@Bean public HandlerExceptionResolver handlerExceptionResolver() { ListexceptionResolvers = new ArrayList<>(); configureHandlerExceptionResolvers(exceptionResolvers); if (exceptionResolvers.isEmpty()) { addDefaultHandlerExceptionResolvers(exceptionResolvers); } extendHandlerExceptionResolvers(exceptionResolvers); HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite(); composite.setOrder(0); composite.setExceptionResolvers(exceptionResolvers); return composite; }
和上一篇文章一毛一样的套路,ExceptionHandlerExceptionResolver 实现了 InitializingBean 接口,重写了 afterPropertiesSet 方法:
@Override public void afterPropertiesSet() { // Do this first, it may add ResponseBodyAdvice beans initExceptionHandlerAdviceCache(); ... } private void initExceptionHandlerAdviceCache() { if (getApplicationContext() == null) { return; } ListadviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); 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); if (resolver.hasExceptionMappings()) { this.exceptionHandlerAdviceCache.put(adviceBean, resolver); } if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) { this.responseBodyAdvice.add(adviceBean); } } }
重点看上面我用注释标记的构造方法,代码很好懂,仔细看看吧,其实就是筛选出我们用 @ExceptionHandler 注解标记的方法并放到集合当中,用于后续全局异常捕获的匹配
/** * A constructor that finds {@link ExceptionHandler} methods in the given type. * @param handlerType the type to introspect */ public ExceptionHandlerMethodResolver(Class> handlerType) { 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> detectExceptionMappings(Method method) { List > result = new ArrayList<>(); 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; } private void detectAnnotationExceptionMappings(Method method, List > result) { ExceptionHandler ann = AnnotatedElementUtils.findMergedAnnotation(method, ExceptionHandler.class); Assert.state(ann != null, "No ExceptionHandler annotation"); result.addAll(Arrays.asList(ann.value())); }
到这里,我们用 @RestControllerAdvice
和 @ExceptionHandler
注解就会被 Spring 扫描到上下文,供我们使用
让我们回到你最熟悉的调用的入口 DispatcherServlet 类的 doDispatch 方法:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { ... try { ModelAndView mv = null; Exception dispatchException = null; try { ... // 当请求发生异常,该方法会通过 catch 捕获异常 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); ... } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } // 调用该方法分析捕获的异常 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } ... }
接下来,我们来看 processDispatchResult 方法,这里只要展示调用栈你就会眼前一亮了,又是为了返回统一格式数据:
总结
上一篇文章的返回统一数据格式是基础,当异常情况发生时,只不过需要将异常信息提取出来。本文主要为了说明问题,剖析原理,好多地方设计方式是不可取,比如我们最好将异常封装在一个 Enum 类,通过 enum 对象抛出异常等,如果你用到这些,去完善你的设计方案吧
回复 「demo」,打开链接,查看文件夹 「unifiedreturn」下内容,获取完整代码,更好阅读体验: https://fraseryu.github.io/2019/08/09/ru-he-tong-yi-chu-li-yi-chang-bing-fan-hui-tong-yi-ge-shi/
附加说明
之前看到的一本书对异常的分类让我印象深刻,在此摘录一小段分享给大家:
结合出国旅行的例子说明异常分类:
机场地震,属于不可抗力,对应异常分类中的
Error
,在制订出行计划时,根本不需要把这个部分的异常考虑进去堵车属于
checked
异常,应对这种异常,我们可以提前出发,或者改签机票。而飞机延误异常,虽然也需要 check,但我们无能为力,只能持续关注航班动态没有带护照,明显属于可
提前预测的异常
,只要出发前检查即可避免;去机场路上车子抛锚,这个异常是突发的,虽然难以预料,但是必须处理,属于需要捕捉的异常
,可以通过更换交通工具;应对检票机器故障属于可透出异常
,交由航空公司处理,我们无须关心
灵魂追问
这两篇文章,你学到了哪些设计模式?
你能熟练的使用反射吗?当看源码是会看到很多反射的应用
你了解 Spring CGLIB 吗?它的工作原理是什么?
提高效率工具
JSON-Viewer
JSON-Viewer 是 Chrome 浏览器的插件,用于快速解析及格式化 json 内容,在 Chrome omnibox(多功能输入框)输入json-viewer + TAB
,将 json 内容拷贝进去,然后输入回车键,将看到结构清晰的 json 数据,同时可以自定义主题
另外,前端人员打开开发者工具,双击请求链接,会自动将 response 中的 json 数据解析出来,非常方便
看完上述内容,你们对Spring Boot 统一异常怎么处理和剖析有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注创新互联行业资讯频道,感谢大家的支持。
文章题目:SpringBoot统一异常怎么处理和剖析
文章网址:http://cdiso.cn/article/jgocep.html