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

news2024/9/26 5:14:24

概述

在这里插入图片描述
所有异常都是继承自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 type
        0    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 类中定义的静态未捕获异常处理器对象)。
	 */
	@FunctionalInterface
	public 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 terminates
            return 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默认是null
        if (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 {
    @Override
    public 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,Thymeleaf
        return 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.coloradmin.cn/o/1920238.html

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

相关文章

学习大数据DAY14 PLSQL基础语法3

目录 二重循环 三种循环随便嵌套 exit continue return 作业 数据提取 游标 隐式游标 显示游标 动态游标 游标使用流程 游标属性 游标配合循环使用示例 作业2 参数游标 current of 语句 作业3 PLSQL基础语法&#xff08;三&#xff09; 二重循环 三种循环随便嵌…

threadLocal详细认识(使用场景与局限性)与样例测试

Threadlocal的介绍与使用 1&#xff0c;是什么&#xff1f; ThreadLocal 是 Java 提供的一个工具类&#xff0c;用于在多线程环境中为每个线程提供独立的变量副本。它是 Java 标准库中的一部分&#xff0c;提供了线程局部存储的功能&#xff0c;这意味着每个线程都有自己独立…

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

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

2 文件

2 文件 1、文件系统1.1 文件系统的逻辑结构1.2 文件的访问流程 2、文件类型3、文件的打开与关闭4、文件的内核结构5、文件的读写4.1 顺序与随机读写4.2 文件描述符的复制4.3 访问测试4.4 修改文件大小 5、文件锁5.1 读写冲突5.2 文件锁5.3 文件锁的内核结构 6、文件的元数据7、…

MTK Camera 冷启动、前后摄切换性能优化分析

和你一起终身学习&#xff0c;这里是程序员Android 经典好文推荐&#xff0c;通过阅读本文&#xff0c;您将收获以下知识点: 一、背景二、问题分解三、工具分析四、 traceView教程五、surface create优化六、systrace的教程七、优化方案八、前后切换速度优化九、优化方案十、热…

旷野之间4 - 100 个 Kubernetes 面试问题及答案

100 个 Kubernetes 面试问题及答案 Kubernetes 简介 什么是 Kubernetes&#xff1f; Kubernetes 是一个开源容器编排平台&#xff0c;可自动部署、扩展和管理容器化应用程序。 什么是容器&#xff1f; 容器是一个轻量级、独立的、可执行软件包&#xff0c;其中包含运行应用…

学习笔记——动态路由——IS-IS中间系统到中间系统(特性之路由撤销)

6、路由撤销 ISIS路由协议的路由信息是封装在LSP报文中的TLV中的&#xff0c;但是它对撤销路由的处理和OSPF的处理方式类似。 在ISIS中撤销一条路由实则是将接口下的ISIS关闭&#xff1a; 撤销内部路由&#xff1a; 在ISIS中路由信息是由IP接口TLV和IP内部可达性TLV共同来描…

游戏AI的创造思路-技术基础-决策树(2)

上一篇写了决策树的基础概念和一些简单例子&#xff0c;本篇将着重在实际案例上进行说明 目录 8. 决策树应用的实际例子 8.1. 方法和过程 8.1.1. 定义行为 8.1.2. 确定属性 8.1.3. 构建决策树 8.1.4. 实施行为 8.1.5. 实时更新 8.2. Python代码 8. 决策树应用的实际例子…

hudi数据湖万字全方位教程+应用示例

1、时间轴&#xff08;TimeLine&#xff09; Hudi的核心是维护表上在不同的即时时间&#xff08;instants&#xff09;执行的所有操作的时间轴&#xff08;timeline&#xff09;&#xff0c;这有助于提供表的即时视图 一个instant由以下三个部分组成&#xff1a; 1&#xff09;…

YOLOv10改进 | Conv篇 | RCS-OSA替换C2f实现暴力涨点(减少通道的空间对象注意力机制)

一、本文介绍 本文给大家带来的改进机制是RCS-YOLO提出的RCS-OSA模块&#xff0c;其全称是"Reduced Channel Spatial Object Attention"&#xff0c;意即"减少通道的空间对象注意力"。这个模块的主要功能是通过减少特征图的通道数量&#xff0c;同时关注空…

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

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

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

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

旷野之间5 - AI基础代理决策的范式转变

介绍 让我们来谈谈最近在人工智能领域引起轰动的一件事——基础代理及其彻底改变我们所知的决策的潜力。现在,我知道你可能会想,“另一天,又一个人工智能突破,乏味无趣。”但相信我,这是一个改变游戏规则的突破,值得你关注。 如果您一直在关注人工智能和人工智能代理的…

JupyterNotebook中导出当前环境,并存储为requirements.txt

​使用Anaconda管理Python环境时&#xff0c;可以轻松地导出环境配置&#xff0c;以便在其他机器或环境中重新创建相同的环境。可以通过生成一个environment.yml文件实现的&#xff0c;该文件包含了环境中安装的所有包及其版本。但是&#xff0c;常常在一些课程中JupyterNotebo…

synchronized关键字详解(全面分析)

目录 synchronized关键字详解1、synchronized关键字简介2、synchronized作用和使用场景作用使用场景①、用在代码块上(类级别同步)②、用在代码块上(对象级别同步)③、用在普通方法上(对象级别同步)④、用在静态方法上(类级别同步)总结&#xff1a; 3、synchronized底层原理&am…

记录些Redis题集(1)

为什么Redis要有淘汰机制&#xff1f; 淘汰机制的存在是必要的&#xff0c;因为Redis是一种基于内存的数据库&#xff0c;所有数据都存储在内存中。然而&#xff0c;内存资源是有限的。在Redis的配置文件redis.conf中&#xff0c;有一个关键的配置项&#xff1a; # maxmemory…

vue3<script setup>自定义指令

main.ts // 自定义指令 app.directive(color,(el,binding) > {el.style.color binding.value })这段代码定义了一个名为color的自定义指令&#xff0c;并将其注册到Vue应用实例app上。自定义指令接收两个参数&#xff1a;el和binding。el是绑定指令的元素&#xff0c;而bi…

240711_昇思学习打卡-Day23-LSTM+CRF序列标注(2)

240711_昇思学习打卡-Day23-LSTMCRF序列标注&#xff08;2&#xff09; 今天记录LSTMCRF序列标注的第二部分。仅作简单记录 Score计算 首先计算正确标签序列所对应的得分&#xff0c;这里需要注意&#xff0c;除了转移概率矩阵&#x1d40f;外&#xff0c;还需要维护两个大小…

解决鸿蒙开发中克隆项目无法签名问题

文章目录 问题描述问题分析解决方案 问题描述 在一个风和日丽的早晨&#xff0c;这是我学习鸿蒙开发的第四天&#xff0c;把文档过了一遍的我准备看看别人的项目学习一下&#xff0c;于是就用git去clone了一个大佬的开源项目&#xff0c;在签名的时候遇到了问题&#xff1a; h…

Codeforces Round 957 (Div. 3)(A~E题解)

这次比赛只能用抽象来形容&#xff0c;前五道题都没有什么算法&#xff0c;都是思维加模拟都能过&#xff0c;然后第四题卡住了&#xff0c;第五题不知道为什么做出来的人那么少&#xff0c;就是纯暴力就能过&#xff0c;但是没抓住上分的机会&#xff0c;有些可惜&#xff0c;…