JAVA中异常处理是我们经常接触的,很多时候,我们在排查日志的时候都发现,一个很复杂的函数里面,突然间中断了,从日志打印的堆栈信息中,又很难发现真正的问题所在,因为很多时候异常被掩盖了。
举一个最简单的例子,例如:
public void testThrowFileEx() throws FileNotFoundException{
throw new FileNotFoundException();
}
public void testThrowClassEx() throws ClassNotFoundException{
try {
this.equals("");
testThrowFileEx(); //FileNotFoundException()这个方式是会抛异常的
this.hashCode();
} catch (FileNotFoundException e) {
throw new ClassNotFoundException();
}
}
这里得到的结果是:
java.lang.ClassNotFoundException
at com.test.file.encrypt.SimpleEncryptFile.testThrowClassEx(SimpleEncryptFile.java:90)
at com.test.file.encrypt.SimpleEncryptFile.main(SimpleEncryptFile.java:106)
打印的堆栈结果没有显示FileNotFoundException,只是显示了ClassNotFoundException。
而testThrowClassEx()函数中的ClassNotFoundException是由于testThrowFileEx()里面抛出的FileNotFoundException引起的,不过由于testThrowClassEx()函数内部处理了FileNotFoundException,反而抛出了新的ClassNotFoundException,因此掩盖了真实的错误信息。我们在排查问题的时候就不清楚ClassNotFoundException是testThrowClassEx()中具体那个函数错误引起的。
这里我们有以下思路可以解决:
1、当我们不需要转义异常的,就直接抛出调用函数的异常即可。
2、当我们需要转义异常的,就需要先在抛异常前,先打印堆栈信息。
示例中e.printStackTrace()只是为了说明问题,在具体项目中使用日志工具打印堆栈信息,这种是初级的处理方式,一般应用中不会这样做。
3、真实项目中,当我们需要转义异常的,例如从AException转为BException,这个时候有两个处理方式:
3.1、当BException构造函数可以传递Throwable的时候,直接throw new BException(e);即可。
3.2、当BException构造函数不可以传递Throwable的时候,就需要自己调用initCause()初始化BException异常引起的源异常。
这里就涉及到Exception的cause对象问题了,cause对象是指我们的异常是由于什么异常引起的。
很多时候我们看见的异常,有部分异常会打印:Cause by...,就是抛出异常对象时,传递了cause对象。
Exception in thread "main" java.io.InterruptedIOException: file is interrupted
at com.test.exception.demo.DealFile.deals(DealFile.java:37)
at com.test.exception.demo.DealFile.main(DealFile.java:45)
Caused by: java.io.FileNotFoundException: File is broken
at com.test.exception.demo.DealFile.openFile(DealFile.java:16)
at com.test.exception.demo.DealFile.readFile(DealFile.java:23)
at com.test.exception.demo.DealFile.deals(DealFile.java:31)
... 1 more
--------------------------这里是对于上面的进行举例说明-----------------------------
1、这里先讲不需要转义的情况,就直接抛出原来的异常即可,或者连异常都不需要处理,直接在方法中throws异常,例如:
public void testThrowFileEx() throws FileNotFoundException{
throw new FileNotFoundException();
}
public void testThrowClassEx() throws FileNotFoundException{
try{
this.equals("");
testThrowFileEx(); //FileNotFoundException()这个方式是会抛异常的
this.hashCode();
}catch(FileNotFoundException e){
throw e;
}finally{
System.out.println("意外中断了...");
}
}
或者:
public void testThrowFileEx() throws FileNotFoundException{
throw new FileNotFoundException();
}
public void testThrowClassEx() throws FileNotFoundException{
this.equals("");
testThrowFileEx(); //FileNotFoundException()这个方式是会抛异常的
this.hashCode();
}
打印的日志:
java.io.FileNotFoundException
at com.test.file.encrypt.SimpleEncryptFile.testThrowFileEx(SimpleEncryptFile.java:98)
at com.test.file.encrypt.SimpleEncryptFile.testThrowClassEx(SimpleEncryptFile.java:104)
at com.test.file.encrypt.SimpleEncryptFile.main(SimpleEncryptFile.java:124)
这里就得到了我们想要的结果,准确定位到错误位置是由于testThrowFileEx()引起的。
两种场景都可以,有没有try catch模块,主要是看函数里面有没有finally块要处理。
如果不需要处理finally块的,就直接抛出异常就可以了。
如果需要处理finally块的,就需要使用try catch模块。
2、再来看看需要转义的情况,例如:
public void testThrowFileEx() throws FileNotFoundException{
throw new FileNotFoundException();
}
public void testThrowClassEx() throws ClassNotFoundException{
try {
this.equals("");
testThrowFileEx(); //FileNotFoundException()这个方式是会抛异常的
this.hashCode();
} catch (FileNotFoundException e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
打印的结果:
java.io.FileNotFoundException
at com.test.file.encrypt.SimpleEncryptFile.testThrowFileEx(SimpleEncryptFile.java:98)
at com.test.file.encrypt.SimpleEncryptFile.testThrowClassEx(SimpleEncryptFile.java:104)
at com.test.file.encrypt.SimpleEncryptFile.main(SimpleEncryptFile.java:124)
java.lang.ClassNotFoundException
at com.test.file.encrypt.SimpleEncryptFile.testThrowClassEx(SimpleEncryptFile.java:108)
at com.test.file.encrypt.SimpleEncryptFile.main(SimpleEncryptFile.java:124)
这里打印的结果,打印了两段错误信息,其中第一段是FIleNotFoundException,这个是e.printStackTrace()打印出来的,而ClassNotFoundException才是抛出的异常。
通过这种方式,就可以在需要转义时候,先打印堆栈信息,这样方便排查问题,但这种只是平常Demo可以这样些,真实项目中不会这样处理。
3、真实项目中,一般建议使用异常传递机制,把底层异常一直往外抛。
package com.test.exception.demo;
import java.io.FileNotFoundException;
import java.io.InterruptedIOException;
import java.util.Random;
public class DealFile {
public void openFile() throws FileNotFoundException{
if(new Random().nextBoolean()){
System.out.println("随机True");
throw new FileNotFoundException("File not existed");
}else{
System.out.println("随机False");
throw new FileNotFoundException("File is broken");
}
}
public void readFile() throws FileNotFoundException{
System.out.println("file reading");
try{
openFile();
}catch(FileNotFoundException e){
throw e;
}
}
public void deals() throws InterruptedIOException{
try{
readFile();
}catch(FileNotFoundException e){
/**
* 使用initCause函数,把readFile函数中的FileNotFoundException转为InterruptedIOException
* 并把FileNotFoundException传递给外层。
*/
InterruptedIOException ie = new InterruptedIOException("file is interrupted");
ie.initCause(e);
throw ie;
}
}
public static void main(String[] args) throws Exception {
DealFile df = new DealFile();
df.deals();
}
}
执行结果:
file reading
随机False
Exception in thread "main" java.io.InterruptedIOException: file is interrupted
at com.test.exception.demo.DealFile.deals(DealFile.java:31)
at com.test.exception.demo.DealFile.main(DealFile.java:39)
Caused by: java.io.FileNotFoundException: File is broken
at com.test.exception.demo.DealFile.openFile(DealFile.java:14)
at com.test.exception.demo.DealFile.readFile(DealFile.java:21)
at com.test.exception.demo.DealFile.deals(DealFile.java:29)
... 1 more
打印结果可以看见是deals()函数抛出了InterruptedIOException异常,而这个异常又是因为openFile()函数中的FileNotFoundException异常引起的。
这样就可以轻松的异常信息逐层传递给外层,开发者从日志中就可以清晰看见错误引起的原因,方便排查问题。
另外,还有一个小技巧,我们在调开发的时候,由于程序继承和调用层级很深,我们不清楚当前函数是从给哪个对象调用,而函数又没有异常需要捕获。这时可以使用以下函数来打印当前的堆栈信息,通过打印的堆栈信息,就很清楚整个程序调用这个方法时的逻辑了,非常好用。
new Exception().printStackTrace();
这里说的问题都是非常基础的内容,但很多人都没有特别在意这些细节问题。如果大家还有更好的办法,欢迎可以一起探讨。