今天来介绍了两个陌生又熟悉的异常类,熟悉是因为我们经常会遇到它们,陌生是好像又从来不知道它们是做什么的
假定读者已经清楚了Java的异常分类:
反射操作过程中,调用目标类的方法可能抛出异常,可以使用Throwable接受,但是太宽泛了
InvocationTargetException就是为解决这个问题而设计的,当反射操作的目标方法中出现异常时,都统一包装成一个必检异常 InvocationTargetException
InvocationTargetException的target属性保存了原始的异常
package exception;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ExceptionTest{
public static void makeInvocationTargetException() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException{
Class<ExceptionTest> exceptionTestClass = ExceptionTest.class;
Method throwExceptionMethod = exceptionTestClass.getMethod( "throwExceptionMethod" );
throwExceptionMethod.invoke( new ExceptionTest() );
}
public void throwExceptionMethod(){
int i = 1 / 0;
}
public static void main( String[] args ) throws Exception{
makeInvocationTargetException();
}
}
输出以下异常,可以看出首先是打印的是InvocationTargetException,后续接着打印了真正异常发生的位置
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at exception.ExceptionTest.makeInvocationTargetException(ExceptionTest.java:12)
at exception.ExceptionTest.main(ExceptionTest.java:22)
Caused by: java.lang.ArithmeticException: / by zero
at exception.ExceptionTest.throwExceptionMethod(ExceptionTest.java:17)
... 6 more
如果子类中要重写父类中的方法,那么子类方法中抛出的必检异常必须是父类方法中声明过的类型。
代理类一般有两种实现方式:实现目标类相同的接口或者继续目标类
可以代理类难免会抛出一些接口或者父类没有声明的受检异常,这时候不抛出,它是受检异常,必须抛出;抛出,没有声明
所以可以把这些受检异常包装为免检异常UndeclaredThrowableException抛出
package exception;
import java.io.IOException;
public interface IPerson{
void sayHello() throws IOException;
}
package exception;
public class Person implements IPerson{
@Override
public void sayHello(){
System.out.println( "Hello" );
}
}
package exception;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
public class PersonRecord implements InvocationHandler{
Object object;
public PersonRecord( Object object ){
this.object = object;
}
@Override
public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable{
System.out.println( "record start" );
Object rs = method.invoke( object, args );
System.out.println( "record end" );
throw new Exception();
}
}
package exception;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class UndeclaredExceptionTest{
public static void makeUndeclaredThrowableException() throws IOException{
IPerson o = (IPerson)Proxy.newProxyInstance( UndeclaredExceptionTest.class.getClassLoader(), new Class[]{ IPerson.class },
new PersonRecord(new Person()) );
o.sayHello();
}
public static void main( String[] args ) throws Exception{
makeUndeclaredThrowableException();
}
}
会有以下输出
record start
Hello
record end
Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
at com.sun.proxy.$Proxy0.sayHello(Unknown Source)
at exception.UndeclaredExceptionTest.makeUndeclaredThrowableException(UndeclaredExceptionTest.java:14)
at exception.UndeclaredExceptionTest.main(UndeclaredExceptionTest.java:21)
Caused by: java.lang.Exception
at exception.PersonRecord.invoke(PersonRecord.java:25)
... 3 more
这两个异常可以同时发现,也就是在代理类中使用了反射操作,然后异常被包装为InvocationTargetException,而InvocationTargetException没有被接口或者父类声明过,于是又被保证为UndeclaredThrowableException
我们将Person的sayHello方法改一下:
package exception;
public class Person implements IPerson{
@Override
public void sayHello(){
System.out.println( "Hello" );
int i = 1/0;
}
}
这样就会同时触发这两种异常
record start
Hello
Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
at com.sun.proxy.$Proxy0.sayHello(Unknown Source)
at exception.UndeclaredExceptionTest.makeUndeclaredThrowableException(UndeclaredExceptionTest.java:14)
at exception.UndeclaredExceptionTest.main(UndeclaredExceptionTest.java:21)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at exception.PersonRecord.invoke(PersonRecord.java:23)
... 3 more
Caused by: java.lang.ArithmeticException: / by zero
at exception.Person.sayHello(Person.java:9)
... 8 more
在工作的过程中,会经常的遇到这两个异常,以前只是选择性的把它忽略了,在我们了解了他们的组成和原理之后可以更好的找到原始异常
下面是一个Mybatis的工具类,目的是从包装异常类中找到原始的异常:
/**
* Copyright 2009-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.reflection;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.UndeclaredThrowableException;
/**
* @author Clinton Begin
*/
public class ExceptionUtil {
private ExceptionUtil() {
// Prevent Instantiation
}
/**
* 拆解InvocationTargetException和UndeclaredThrowableException异常的包装,从而得到被包装的真正异常
* @param wrapped 包装后的异常
* @return 拆解出的被包装异常
*/
public static Throwable unwrapThrowable(Throwable wrapped) {
// 该变量用以存放拆包得到的异常
Throwable unwrapped = wrapped;
while (true) {
if (unwrapped instanceof InvocationTargetException) {
// 拆包获得内部异常
unwrapped = ((InvocationTargetException) unwrapped).getTargetException();
} else if (unwrapped instanceof UndeclaredThrowableException) {
// 拆包获得内部异常
unwrapped = ((UndeclaredThrowableException) unwrapped).getUndeclaredThrowable();
} else {
// 该异常无需拆包
return unwrapped;
}
}
}
}