InvocationTargetException和UndeclaredThrowableException异常介绍

发布时间 2023-09-05 17:44:58作者: songtianer

今天来介绍了两个陌生又熟悉的异常类,熟悉是因为我们经常会遇到它们,陌生是好像又从来不知道它们是做什么的

假定读者已经清楚了Java的异常分类:

  1. 一是程序不能处理的错误(Error),
  2. 二是程序应该避免而可以不去捕获的运行时异常(RuntimeException),
  3. 三是必须捕获的非运行时异常,也叫受检异常或必检异常

InvocationTargetException

反射操作过程中,调用目标类的方法可能抛出异常,可以使用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

如果子类中要重写父类中的方法,那么子类方法中抛出的必检异常必须是父类方法中声明过的类型。

代理类一般有两种实现方式:实现目标类相同的接口或者继续目标类

  • 如果代理类和被代理类实现了共同的接口,那代理类方法抛出的受检异常必须是共同接口中声明过的
  • 如果代理类是被代理类的子类,则代理类方法报错的受检异常必须是被代理类的方法中声明过的

可以代理类难免会抛出一些接口或者父类没有声明的受检异常,这时候不抛出,它是受检异常,必须抛出;抛出,没有声明

所以可以把这些受检异常包装为免检异常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

总结

  1. InvocationTargetException是在使用反射中发生的异常,包裹了原始的异常
  2. UndeclaredThrowableException是在代理中发生的异常,包裹了原始的异常

在工作的过程中,会经常的遇到这两个异常,以前只是选择性的把它忽略了,在我们了解了他们的组成和原理之后可以更好的找到原始异常

下面是一个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;
      }
    }
  }

}