Java异常体系、UncaughtExceptionHandler、Spring MVC统一异常处理、Spring Boot统一异常处理

概述

在这里插入图片描述
所有异常都是继承自java.lang.Throwable类,Throwable有两个直接子类,Error和Exception。

Error用来表示程序底层或硬件有关的错误,这种错误和程序本身无关,如常见的NoClassDefFoundError。这种异常和程序本身无关,不需要检查,属于非受检异常。

Exception表示程序异常,可能是由于程序不严谨导致的,如NPE空指针异常。Exception下面派生RuntimeException和其他异常,其中RuntimeException表示运行时异常,也属于非受检异常。在编译时可以不需要强制检查的异常,不需要显式捕捉或抛出。

除Error和RuntimeException及派生类以外,其他异常都属于受检异常,如IOException、SQLException。在编译时强制进行检查的异常,这种异常需要显式的通过try/catch来捕捉,或通过throws抛出去,否则程序无法通过编译。设计强制检查的异常(受检异常),主要原因是考虑到程序的正确性、稳定性和可靠性。
在这里插入图片描述

try…catch…finally语句块

初中级笔试题可能会出现的知识点。这里直接给出一些结论:

  • 受检异常,需要使用try来包裹可能会抛出异常的代码块,catch用于捕获异常并处理异常的代码块,常见的处理策略包括:打印错误日志、抛出自定义业务异常、释放资源、设置局部变量等
  • 受检异常,还可以直接在方法签名上throws Exception,抛给方法调用者来处理。业务开发中,通常在Service层抛出自定义业务异常,然后在Controller层统一捕获异常并返回errCode和errMsg
  • 不管有没有出现异常,finally仍然会执行
  • 当try和catch中有return时,finally仍然会执行
  • finally常用于释放IO资源、(分布式)锁的持有、

常见异常

初中级Java开发工程师面试中,经常会遇到的一个问题:说说你工作中经常遇到的异常?

面试官指的应该包括Exception和Error,回答问题时,不能只列举Exception。

简单列举Exception如下:

  • NullPointerException:简称NPE。多少人栽在NPE上,多少资金损失是因为NPE。减少(无法杜绝)NPE的方法就是不停地空判断,或使用Optional类。可喜的是,升级到JDK 14以上版本,发生NPE时,JVM会打印具体哪个方法抛的空指针异常,避免同一行代码多个函数调用时无法判断具体是哪个函数抛异常的困扰,方便异常排查;
  • ConcurrentModificationException:简称CME。当有多个迭代器同时遍历和修改Java集合(如ArrayList或HashMap),就有可能抛出CME异常。避免出现CME异常的措施如:加锁,使用CopyOnWriteArrayList,ConcurrentHashMap等集合。
  • IndexOutOfBoundsException:索引越界,实现类有两个ArrayIndexOutOfBoundsException和StringIndexOutOfBoundsException。
  • ClassCastException:类型转换失败。
  • ClassNotFoundException:参考Java学习之NoClassDefFoundError、ClassNotFoundException、NoSuchMethodError

简单列举Error如下:

  • OutOfMemoryError:OOM,报错信息为:java.lang.OutOfMemoryError:Java heap spacess。遇到OOM时,需要先分清楚是内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。
  • StackOverflowError:栈溢出。栈溢出的原因:递归调用(如求解斐波那契数列问题时),大量循环或死循环,全局变量过多,数组、List、Map数据过大。
  • NoClassDefFoundError:找不到类定义
  • NoSuchMethodError:找不到方法
  • NoSuchFieldError:找不到字段,上面这三种一般都是三方依赖冲突,通过使用maven工具来排查,如mvn dependency:tree > tmp.txt,或使用IDEA的Maven Helper插件

最佳实践

即所谓的Best Practice:

  • 在finally中清理资源;
  • 坚决要杜绝捕获异常后不做任何处理,即catch语句块为空;
  • 捕获异常后的日志打印规范,如记录错误类和方法,记录详细的错误堆栈stacktrace方便排查问题;
  • 使用Try-With-Resource语句,实现AutoCloseable接口的资源;
  • 优先捕获特定的异常,其次再考虑其父类异常;
  • 多使用自定义业务异常,一个异常对应有一个errCode和一个可读性良好的errMsg

进阶

异常表

在JVM中,异常处理不是由字节码指令(早期使用jsr、ret指令)来实现的,而是异常表。

如果一个方法定义有try-catch或try-finally,则会创建异常表,保存异常处理信息:

  • 起始位置
  • 结束位置
  • 程序计数器记录的代码处理的偏移地址
  • 被捕获的异常类在常量池中的索引

Exception table:

Exception table:from    to  target type0    12    15   Class java/lang/Exception

根据不同的type对应到不同的target上。在操作系统里,这个target也称为异常处理程序。就是特定问题出现时,去异常表查询这个问题对应的是哪个处理程序,然后去执行这个程序,完成异常处理。

面试可能会遇到的问题:finally为什么一定会执行?
查看编译后的字节码,可发现编译器把finally语句块里面的代码分别复制到try和catch语句块里面。

异常throw事件

jvmti中提供两个异常的事件,一个是包含throw和catch,一个是catch。选择功能多的那个方便一点。

void JNICALL Exception(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread, jmethodID method, jlocation location, jobject exception, jmethodID catch_method, jlocation catch_location)

通过方法签名,可以知道异常的线程,出异常的方法,行号,异常对象,catch的方法和行号。这里由于是触发的throw事件,所以如果只是new Exception的操作是不会触发事件的。有些代码通过创建Exception或Error来控制逻辑,只要不是throw,catch的这种逻辑,这里是检测不到的。如果异常只throw没有catch的话,catch的字段就是空的。

拓展

UncaughtExceptionHandler

在虚拟机中,当一个线程没有显式处理(即try catch)异常而抛出时,会将该异常事件报告给该线程对象的java.lang.Thread.UncaughtExceptionHandler进行处理,如果线程没有设置UncaughtExceptionHandler,则默认会把异常栈信息输出到终端而使程序直接崩溃。所以如果想在线程意外崩溃时做一些处理就可以通过实现UncaughtExceptionHandler来满足需求。

public class Thread {/*** 当一个线程因未捕获的异常而即将终止时虚拟机将使用 Thread.getUncaughtExceptionHandler()* 获取已经设置的 UncaughtExceptionHandler 实例,并通过调用其 uncaughtException(...) 方法而传递相关异常信息。* 如果一个线程没有明确设置其 UncaughtExceptionHandler,则将其 ThreadGroup 对象作为其handler,如果 ThreadGroup 对象对异常没有什么特殊的要求,则 ThreadGroup 会将调用转发给默认的未捕获异常处理器(即 Thread 类中定义的静态未捕获异常处理器对象)。*/@FunctionalInterfacepublic interface UncaughtExceptionHandler {/*** 未捕获异常崩溃时回调此方法*/void uncaughtException(Thread t, Throwable e);}/*** 静态方法,用于设置一个默认的全局异常处理器*/public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {SecurityManager sm = System.getSecurityManager();if (sm != null) {sm.checkPermission(new RuntimePermission("setDefaultUncaughtExceptionHandler"));}defaultUncaughtExceptionHandler = eh;}/*** 针对某个Thread对象的方法,用于对特定的线程进行未捕获的异常处理*/public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {checkAccess();uncaughtExceptionHandler = eh;}/*** 当Thread崩溃时会调用该方法获取当前线程的 handler,获取不到就会调用 group(handler 类型)。* group是Thread类的ThreadGroup类型属性,在Thread构造中实例化*/public UncaughtExceptionHandler getUncaughtExceptionHandler() {if (isTerminated()) {// uncaughtExceptionHandler may be set to null after thread terminatesreturn null;} else {UncaughtExceptionHandler ueh = uncaughtExceptionHandler;return (ueh != null) ? ueh : getThreadGroup();}}/*** 线程全局默认handler*/public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() {return defaultUncaughtExceptionHandler;}
}

线程崩溃时异常抛出的顺序:

  • 先调用Thread.getUncaughtExceptionHandler()查看是否有自己对象特有的handler,如果有就直接处理
  • 如果没有就调用ThreadGroup(UncaughtExceptionHandler的默认实现类)
  • 如果ThreadGroup没啥特殊处理就会继续调用Thread.getDefaultUncaughtExceptionHandler()获取handler进行处理
  • 如果默认handler也没有处理就直接执行正常的异常流程使程序崩溃。

ThreadGroup核心实现源码:

// ThreadGroup在Thread对象构造方法中实例化
public class ThreadGroup implements Thread.UncaughtExceptionHandler {public void uncaughtException(Thread t, Throwable e) {// parent默认是nullif (parent != null) {parent.uncaughtException(t, e);} else {// 一般走进来,调用Thread.setDefaultUncaughtExceptionHandler(...)方法设置全局 handler进行处理Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler();if (ueh != null) {ueh.uncaughtException(t, e);} else if (!(e instanceof ThreadDeath)) {// 全局handler也不存在就输出异常栈System.err.print("Exception in thread \"" + t.getName() + "\" ");e.printStackTrace(System.err);}}}
}

Spring MVC异常处理机制

参考Spring MVC系列之九大核心组件中的HandlerExceptionResolver部分。

Spring MVC全局异常处理

每个Controller层里的方法都需要进行异常捕获及处理,显然太繁琐且效率低。

自定义类并实现HandlerExceptionResolver接口并重写resolveException方法进行全局异常处理:

@Slf4j
@Component
public class SimpleExceptionResolver implements HandlerExceptionResolver {@Overridepublic ModelAndView resolveException(HttpServletRequest request, @NonNull HttpServletResponse response, Object object, @NonNull Exception e) {// 业务异常对前端可见,否则统一归为系统异常Map<String, Object> map = new HashMap<>();map.put("success", false);// 自定义业务异常,可多次if判断对应多个异常类型,当然也可使用switch语句if (e instanceof BusinessException) {map.put("errorMsg", e.getMessage());} else {map.put("errorMsg", "system exception");}log.error(e.getMessage(), e);// 此处返回ModelandView对象,如error.jsp页面,也可考虑使用其他的模板引擎,如FreeMarker,Thymeleafreturn new ModelAndView("/error", map);}
}

可以以不同的方式将异常结果返回给调用者(前端或其他后端服务)

  • 返回ModelAndView
  • 返回页面的地址
  • 返回JSON
  • 返回HTTP错误码

当然也可以使用下面Spring Boot全局异常处理方案。

Spring Boot全局异常处理

直接给出配置类:

@Slf4j
// 复合注解 = @ControllerAdvice + @ResponseBody
@RestControllerAdvice
public class GlobalExceptionHandler {// 别的方法都处理不了的异常@ExceptionHandler(Exception.class)public Response<Object> otherExceptionHandler(HttpServletResponse response, Exception ex) {response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());log.error(ex.getMessage(), ex);return Response.error("服务器内部异常!");}// 可捕获自定义异常、JDK或Spring异常,支持数组形式捕获多个不同类型的异常,但推荐一种异常对应一个方法@ExceptionHandler({ForbiddenException.class}) // 自定义业务异常// @ExceptionHandler({IllegalArgumentException.class}) // JDK异常// @ExceptionHandler(HttpMessageNotReadableException.class) // Spring异常// 返回Response Status Code@ResponseStatus(HttpStatus.FORBIDDEN)public Response<Object> forbidden(ForbiddenException e) {// 记录错误日志log.error(e.getMessage(), e);return Response.error(e.getMessage());}// 前端(或接口攻击者)使用非法的@RequestBody请求接口,解析异常字段,并将错误日志降级@ExceptionHandler(MethodArgumentNotValidException.class)public Response<Object> validationBodyException(MethodArgumentNotValidException exception) {BindingResult result = exception.getBindingResult();StringBuilder errorMsg = new StringBuilder();if (result.hasErrors()) {List<ObjectError> errors = result.getAllErrors();errors.forEach(p -> {FieldError fieldError = (FieldError) p;errorMsg.append(fieldError.getDefaultMessage()).append("!");// 设置warn而不是error,日志错误降级log.warn("Data check failure : object{" + fieldError.getObjectName() + "},field{" + fieldError.getField() + "},errorMessage{" + fieldError.getDefaultMessage() + "}");});}return Response.error(errorMsg.toString());}
}

Response是自定义的数据统一返回格式:

@Data
@NoArgsConstructor
public class Response<T> implements Serializable {private int code;private String msg;private T data;// 省略其他包装方法 
}

Dubbo处理异常

分布式调用链

参考

  • 谈谈异常
  • 从JVM角度理解try…catch
  • 利用jvmti查看java异常
  • UncaughtExceptionHandler相关问题解析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/45296.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【算法】二叉树-迭代法实现前后中序遍历

递归的实现就是:每一次递归调用都会把函数的局部变量&#xff0c;参数值和返回地址等压入调用栈中&#xff0c;然后递归返回的时候&#xff0c;从栈顶弹出上一次递归的各项参数&#xff0c;这就是递归为什么可以返回上一层位置的原因 可以用栈实现二叉树的前中后序遍历 1. 前序…

FastAPI 学习之路(四十四)WebSockets

我们之前的分析都是基于http的请求&#xff0c;那么如果是websockets可以支持吗&#xff0c;答案是可以的&#xff0c;我们来看下是如何实现的。 from fastapi import WebSocket, FastAPI from fastapi.responses import HTMLResponseapp FastAPI()html """&…

k8s NetworkPolicy

Namespace 隔离 默认情况下&#xff0c;所有 Pod 之间是全通的。每个 Namespace 可以配置独立的网络策略&#xff0c;来 隔离 Pod 之间的流量。 v1.7 版本通过创建匹配所有 Pod 的 Network Policy 来作为默认的网络策略 默认拒绝所有 Pod 之间 Ingress 通信 apiVersion: …

【趣味数学】求阴影部分面积

题 解法1: 中位线法 既然是中点&#xff0c;就可以用起来&#xff0c;横着不行&#xff0c;竖着来&#xff0c;扩展做辅助线 E是中点S&#xff08;AED) 1/4 S(ABCD) 6 做图中辅助延长线&#xff0c;因为E中点&#xff0c;所以S&#xff08;MEB&#xff09;S(AED) 6 同理E也是…

nfs共享存储配置

目录 一.存储和NFS共享 1.存储的类型分为三种 2.三种存储架构的应用场景 二.NFS共享存储服务 1.NFS简介 2.NFS存储 3.NFS原理 4.软件介绍 三.搭建NFS服务器 1.搭建 2.使用权限&#xff1a; 读写权限 属主&#xff0c;属组权限 客户端创建文件指向同一属主和属组 …

Android使用AndServer在安卓设备上搭建服务端(Java)(Kotlin)两种写法

一直都是通过OkHttp远程服务端进行数据交互&#xff0c;突发奇想能不能也通过OkHttp在局域网的情况下对两个安卓设备或者手机进行数据交互呢&#xff1f; 这样一方安卓设备要当做服务端与另一个安卓设备通过OkHttp进行数据交互即可 当然还可以通过 socket 和 ServerSocket 通…

uniapp中使用uni-ui组件库

src目录下新建components目录从uni-ui引入对应的组件目录&#xff0c;如下图 直接使用组件&#xff0c;demo <template><view id"my" data-name"王五" data-age"18">my页面</view><uni-data-select :localdata"local…

IC后端设计中的shrink系数设置方法

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 在一些成熟的工艺节点通过shrink的方式(光照过程中缩小特征尺寸比例)得到了半节点,比如40nm从45nm shrink得到,28nm从32nm shrink得到,由于半节点的性能更优异,成本又低,漏电等不利因素也可以…

PFH点特征直方图

PFH特征描述子原理 该算法通过参数化查询关键点与其周围邻域点之间的空间差异,形成一个多维度直方图,从而实现对该点的邻域几何属性的描述。 该方法具有以下三个优势: (1)刚性变换不变性,即不受旋转、平移变换的影响; (2)采样一致性,即改变采样密度,特征保…

【数据分享】2021-2100年中国1km分辨率多情景多模式逐月降水量数据集

今天我们给大家分享一份根据IPCC耦合模式比较计划第六阶段&#xff08;CMIP6&#xff09;发布的全球>100 km气候模式数据集以及WorldClim发布的全球高分辨率气候数据集&#xff0c;通过空间降尺度方法得到的2021-2100年中国1km分辨率多情景多模式逐月降水量数据集。 数据来…

04:定时器

定时器 1、定时器怎么定时2、怎样实现计数&#xff1f;2.1、控制寄存器TCON2.2、工作模式寄存器TCOM2.3、定时器T0 3、案例&#xff1a;通过定时器T0控制LED间隔1s亮灭 当定时器用的时候&#xff0c;靠内部震荡电路数数。当配置为定时器使用时&#xff0c;每经过1个机器周期&am…

WPS打开PDF文件的目录

WPS打开PDF文件的目录 其实WPS中PDF文件并没有像Word那样标准的目录&#xff0c;但是倒是有书签&#xff0c;和目录一个效果 点击左上角书签选项&#xff0c;或者使用Alt Shift 1快捷键即可

下载动画人物

1、网址&#xff1a;动画 2、点击Characters 3、搜索人物 4、点击弹出的人物&#xff0c;弹出对话框选择USE THIS CHARACTER 5、下载 6、点击Animations&#xff0c;搜索walk 7、点击UPLOAD CHARACTER&#xff0c;看到男孩步行&#xff0c;选择In Place&#xff0c;点击下载&…

【安全设备】APT攻击预警平台

一、什么是APT 高级持续性威胁&#xff08;APT&#xff09;是一种高度复杂和长期的网络攻击&#xff0c;旨在通过持续监视和访问特定目标来窃取敏感信息或进行其他恶意活动。这种攻击结合了多种先进的技术手段和社会工程学方法&#xff0c;以极高的隐蔽性实现长期潜伏和信息窃…

基于RHCE基础搭建简单服务

目录 项目标题与需求一 配置IP地址server机node02机 二 配置web服务三 搭建dns服务器四 开启防火墙server firewalld 五 配置nfs服务器node02 nfsserver autofs 六 开启SELinux七 验证是否能访问www.rhce.com 项目标题与需求 项目标题&#xff1a; 项目需求&#xff1a; 现有…

c++ 建造者模式

文章目录 建造者模式为什么使用建造者模式建造者模式实现步骤实现示例建造者模式优缺点 建造者模式 建造者模式&#xff08;Builder Pattern&#xff09;是面向对象设计模式中的一种&#xff0c;主要用于创建复杂对象。这种模式将对象的构建过程与其表示分离&#xff0c;允许用…

python+Selenium自动化之免登录(cookie及token)

目录 cookie免登录 通过接口获取cookie 启用浏览器绕过登录 添加token 使用登录可以减去每次登录的重复操作&#xff0c;直接操作系统登录后的菜单页面&#xff0c;也可以减少安全验证登录&#xff0c;如图像验证登录的操作。注意&#xff1a;cookie和token都有有效期。 c…

京东.Vision首登苹果Vision Pro 背后的技术探索

去年6月&#xff0c;苹果正式发布首款头显设备Apple Vision Pro&#xff0c;今年6月28号&#xff0c;Apple Vision Pro正式在中国发售。京东.Vision作为首批原生应用登陆Vision Pro平台&#xff0c;首期以家电家居与潮流数码产品作为切入口&#xff0c;未来将逐步拓展至全品类&…

第11章 规划过程组(三)(11.11规划成本管理)

第11章 规划过程组&#xff08;三&#xff09;11.11规划成本管理&#xff0c;在第三版教材第403~404页&#xff1b; 文字图片音频方式 第一个知识点&#xff1a;成本管理概述 1、成本的类型&#xff08;重要知识点&#xff09; 直接成本 如项目团队差旅费、工资、项目使用的…

【简历】西安某211大学研究生:Java简历面试通过率低

注&#xff1a;为保证用户信息安全&#xff0c;姓名和学校等信息已经进行同层次变更&#xff0c;内容部分细节也进行了部分隐藏 简历说明 这个同学是211研究生的一份Java简历,这个简历版面没有问题,但是因为主项目重复度过大,所以导致这个简历的简历通过率会大大降低,面试通过…