赞
踩
所有异常都是继承自java.lang.Throwable
类,Throwable有两个直接子类,Error和Exception。
Error用来表示程序底层或硬件有关的错误,这种错误和程序本身无关,如常见的NoClassDefFoundError。这种异常和程序本身无关,不需要检查,属于非受检异常。
Exception表示程序异常,可能是由于程序不严谨导致的,如NPE空指针异常。Exception下面派生RuntimeException和其他异常,其中RuntimeException表示运行时异常,也属于非受检异常。在编译时可以不需要强制检查的异常,不需要显式捕捉或抛出。
除Error和RuntimeException及派生类以外,其他异常都属于受检异常,如IOException、SQLException。在编译时强制进行检查的异常,这种异常需要显式的通过try/catch来捕捉,或通过throws抛出去,否则程序无法通过编译。设计强制检查的异常(受检异常),主要原因是考虑到程序的正确性、稳定性和可靠性。
初中级笔试题可能会出现的知识点。这里直接给出一些结论:
初中级Java开发工程师面试中,经常会遇到的一个问题:说说你工作中经常遇到的异常?
面试官指的应该包括Exception和Error,回答问题时,不能只列举Exception。
简单列举Exception如下:
简单列举Error如下:
java.lang.OutOfMemoryError:Java heap spacess
。遇到OOM时,需要先分清楚是内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。mvn dependency:tree > tmp.txt
,或使用IDEA的Maven Helper插件即所谓的Best Practice:
在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语句块里面。
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的字段就是空的。
在虚拟机中,当一个线程没有显式处理(即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,如果有就直接处理Thread.getDefaultUncaughtExceptionHandler()
获取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系列之九大核心组件中的HandlerExceptionResolver部分。
每个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); } }
可以以不同的方式将异常结果返回给调用者(前端或其他后端服务)
当然也可以使用下面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;
// 省略其他包装方法
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。