動態代理中的 UndeclaredThrowableException 以及其他異常

最近看 Github 發現別人寫的全局異常處理中有用到這個類的,整理學習一下

這裏指的是 JDK 動態代理,就是實現 InvocationHandler 接口的那種情況,直接把代碼貼過來,您可以先自己分析一下可能會出現的異常,再往下看我分析地到位與否。

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.SocketException;
import java.util.Optional;
import java.util.stream.Stream;

/**
 * @description: JDK 動態代理以及可能會出現的異常情況
 * @date: 2019/10/28 下午7:48
 * @version: V1.0
 */
@Slf4j
public class JDKDynamicProxyTest {

    interface CustomInterface {

        void say();

        /**
         * 這裏的返回值,如果是包裝類則不會有空指針異常
         * 如果是基本數據類型,可能會產生 NPE(InvocationHandler#invoke 方法返回 null 的情況下)
         * @param num
         * @return
         */
        Integer getPow(Integer[] num);
    }

    @Slf4j
    static class RealSubject implements CustomInterface {

        @Override
        public void say() {
            log.info("I'm real subject,這是我的 say() 方法.");
        }

        @Override
        public Integer getPow(Integer[] num) {
            log.info("I'm real subject,這是我 getPow() 方法.");
            Optional<Integer> reduce = Stream.of(num).map(i -> i * i).reduce(Integer::sum);
            log.info("reduce.get()= {}", reduce.get());
            say();
            return reduce.get();
        }
    }

    @Slf4j
    static class DynamicProxy implements InvocationHandler {

        /** 真正的對象 **/
        private Object instance;

        DynamicProxy(Object o) {
            instance = o;
        }

        /**
         * 空指針異常:如果這個方法的返回值是 null,而接口的返回類型是基本數據類型,就會產生 NPE
         * ClassCastException:
         * UndeclaredThrowableException:如果該方法拋出了可檢查性異常,就會拋出 UndeclaredThrowableException 包着這個可檢查性異常
         * @param proxy     最終生成的代理對象(就是 Proxy#newProxyInstance 方法生成的對象)
         * @param method    被代理對象的某個具體方法
         * @param args
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            log.info("proxy 類名爲:{}", proxy.getClass().getName());
            log.info("----------->進入代理類的 invoke 方法<--------------");
            Object invoke = method.invoke(instance, args);
            log.info("----------->method.invoke()方法結束<--------------");
            //if (true) {
                //這裏直接拋出檢查性異常,會被包裝成 UndeclaredThrowableException
              //  throw new SocketException("dsadsa");
            //}
            return null;//這裏返回 null,null 在轉化爲 int 類型時,會報空指針異常
        }
    }

    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        Integer [] intList = new Integer[]{1,2,3,4,5,6,7,8};
        InvocationHandler handler = new DynamicProxy(realSubject);

        /**
         * Returns an instance of a proxy class for the specified interfaces
         * that dispatches method invocations to the specified invocation
         * handler.
         */
        CustomInterface proxyInstance  = (CustomInterface) Proxy.newProxyInstance(handler.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(),//代理對象實現的接口列表
                handler);
        try {
            Integer pow = proxyInstance.getPow(intList);
            proxyInstance.say();
        }catch (Exception e) {
            if (e instanceof UndeclaredThrowableException) {
                log.error("未聲明的可檢查性異常", ((UndeclaredThrowableException) e).getUndeclaredThrowable());
            }
            else {
                log.error("some ", e);
            }
        }

        log.info("proxyInstance.getClass().getName() = {}", proxyInstance.getClass().getName());
    }

}

InvocationHandler 接口

這個接口就是 JDK 動態代理的關鍵,其中只包含下面一個方法:

/**
 * Processes a method invocation on a proxy instance and returns
 * the result.  This method will be invoked on an invocation handler
 * when a method is invoked on a proxy instance that it is
 * associated with.
 * 
 * @throws  Throwable the exception to throw from the method
 * invocation on the proxy instance.  The exception's type must be
 * assignable either to any of the exception types declared in the
 * {@code throws} clause of the interface method or to the
 * unchecked exception types {@code java.lang.RuntimeException}
 * or {@code java.lang.Error}.  If a checked exception is
 * thrown by this method that is not assignable to any of the
 * exception types declared in the {@code throws} clause of
 * the interface method, then an
 * {@link UndeclaredThrowableException} containing the
 * exception that was thrown by this method will be thrown by the
 * method invocation on the proxy instance.
 *
 * @see     UndeclaredThrowableException
 */ 
public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

先看方法描述,大致意思就是,當在代理對象上調用某個方法時,這個 invoke 方法會被調用。三個參數分別代表,代理對象、調用的方法以及入參。

注意這個方法拋出的可是所有異常的爹 Throwable,包括 ErrorException,其實我們大部分情況下關心的還是 Exception正常運行時,可預料的意外情況),不僅包含運行時異常 RuntimeException 還包含非運行時異常 IOException。再看這個方法的異常描述,異常的類型必須是運行時異常或 Error。如果拋出的是一個可檢查性異常,就會產生一個 UndeclaredThrowableException 來將這個異常包起來。寫到這裏,也基本沒啥東西了,大家再對照看一下上面的例子 invoke 方法中被我註釋掉的 if(true) 那裏,就可以了。

這個 UndeclaredThrowableException extends RuntimeException 是運行時異常,說白了,他就是用來包裝可檢查性異常的運行時異常,有點繞口。我們知道 Spring 大量運用各種代理,因此在全局異常處理中,如果檢查到拋出的異常類型是 UndeclaredThrowableException,需要我們再調用它的 getUndeclaredThrowable() 方法來獲取這個真正的異常。

參考文章

UndeclaredThrowableException

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章