浅谈在java程序中如何拦截全局异常
一.原生java 如何拦截?
1. 简单的异常示例
这是一段最简单的异常代码
public static void main(String[] args){
System.out.println(1/0);
}
如果正常的执行这段代码, 那么控制台可能只有简单的错误输出
2.如何改变的原生的输出呢
使用 Thread.setDefaultUncaughtExceptionHandler 设置自定义的全局异常
比如下方例子:
private static class JavaExceptionHandler {
public static void handler() {
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
Console.error("java handle this error!,reason:{},happen in thread:[{}], ", e.getMessage(), t.getName());
}
});
}
}
那么在执行1/0之前使用 JavaExceptionHandler.handler(),那么输出的信息则被自定义处理
3. 那么自定义异常有什么用
可以在程序崩溃时, 调用消息API 进行通知, 调用数据库API进行存储 等等
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
// TODO 记录程序崩溃的时间及异常信息
}
});
4. setDefaultUncaughtExceptionHandler 不可以阻止程序退出
当运行的代码在不想发生异常时错误退出的话, 应该尽可能使用try catch 处理自己的代码,那么程序就可以从始至终的运行.
setDefaultUncaughtExceptionHandler相当于在崩溃前,做最后的一些事情
因为异常发生时在主线程main上, 所以已经不能阻止jvm 退出了 .
所以不应该在setDefaultUncaughtExceptionHandler尝试写一些调用系统api 重启的操作.
二. spring中如何拦截?
1. spring中的简单异常示例
这是一个简单的示例组件, 当调用该组件时, 会爆出IndexOutOfBoundsException 异常
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("cn.note");
ctx.refresh();
ctx.getBean(AopErrorTest.class).execute();
}
@Component
public static class AopErrorTest {
public void execute() {
Console.log(new ArrayList<>().get(0));
}
}
2. 如何拦截spring的异常呢?
可以使用spring 的aop插件,在方法执行时拦截
@Component
@Aspect
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
public static class AopErrorHandler {
@Pointcut("execution(* cn.note..*.*(..))")
public void pointcut() {
}
/**
* 切面日志拦截
*/
@Around("pointcut()")
public Object handle(ProceedingJoinPoint joinPoint) {
Object result = null;
try {
result = joinPoint.proceed();
} catch (Throwable error) {
Console.error("AOP handle this error! reason:{},happen in thread:[{}]", error.getMessage(), Thread.currentThread().getName());
}
return result;
}
}
当实现AOP的异常拦截器后, 当业务方法发生异常时, 可以被统一的进行错误处理 ,可以在catch 中使用自定义的方法进行错误的处理.
因为异常被catch 进行的错误处理, 所以并没有影响main主进程的执行逻辑, 所以如果不主动调用System.exit 那么业务会按照之前预料的顺序执行 ,
但是可能因为被catch 时,影响了方法本应产生的结果, 所以AOP拦截异常后,也应该从正常的逻辑顺序中中断. 所以可以细细回味springboot中的异常拦截中有@ControllerAdvice
3. 在此回想springboot 的@ControllerAdvice 与@ExceptionHandler
为什么使用@ControllerAdvice 注解可以拦截web服务异常
@ControllerAdvice
public class SpringControllerAdvice {
@ExceptionHandler(RuntimeException .class)
public ModelAndView runtimeExceptionHandler(RuntimeException e) {
return new ModelAndView("error");
}
}
三. swing GUI中如何拦截?
1. swing中的简单异常代码
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String str = null;
Console.log("NullException ...", str.length());
}
});
运行这段代码,当按钮点击error 按钮时, 控制台会输出空指针错误
2. 如何改变原生的输出呢?
当在开发过程中, 你可能更直观的想点击error时,弹出一个alert消息告诉自己那里出错了, 在swing中需要使用 Toolkit.getDefaultToolkit().getSystemEventQueue() 在处理事件分发时进行自定义处理
如SwingExceptionHandler ,自定义EventQueue ,重写dispatchEvent 事件:
private static class SwingExceptionHandler {
/**
* 自定义事件分发
*/
private static class EventQueueProxy extends EventQueue {
protected void dispatchEvent(AWTEvent newEvent) {
try {
super.dispatchEvent(newEvent);
} catch (Throwable t) {
String errorMsg = StrUtil.format("swing handle this error!,reason:{},happen in thread:[{}] ", t.getMessage(), Thread.currentThread().getName());
Console.error(errorMsg);
WinUtil.alert(errorMsg);
}
}
}
public static void handler() {
Toolkit.getDefaultToolkit().getSystemEventQueue().push(new EventQueueProxy());
}
}
在运行swing前使用 SwingExceptionHandler.handler(), 那么就可以在点击error时, 可以感知到代码出错了
public static void main(String[] args) {
CoreFlatLaf.install();
SwingExceptionHandler.handler();
FrameUtil.launchTest(new SwingErrorDemo());
}
运行效果如图:
总结
自定义异常应该用作记录, 回溯时排查的一种手段, 而不应该成为程序逃避bug的的一种捷径.