Java 动态代理剖析


1. 基础介绍

Java 中通过 Proxy 类提供了动态代理的方式,其提供了一种运行时修改程序的功能,在远程服务调用 (RPC)AOP 切面等等中都涉及到该操作。

2. 示例分析

Talk is cheap,让我们通过示例礼了解下代理的效用。

首先定义一个接口类 ProxyService 并实现 sayHi() 进行简短的信息打印。

public interface ProxyService {
    void sayHi();
}

public class ProxyServiceImpl implements ProxyService {
    @Override
    public void sayHi() {
        System.out.println("Hi");
    }
}

动态代理的重点来了,新建类 MyInvokeHandler 并实现于 InvocationHandler

其中 invoke() 即动态代理的核心,由动态代理申请的对象在执行方法前将触发此处的 invoke(),由此即可实现切面等功能,这里我设置当执行 sayHi() 方法时打印信息将由 Hi 改为 Hello

public class MyInvokeHandler implements InvocationHandler {

    private Object target;

    public MyInvokeHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("sayHi")) {
            System.out.println("Hello");
            return null;
        }

        return method.invoke(target, args);
    }
}

通过 Proxy.newProxyInstance() 方法即可实现声明一个动态对象,该对象在执行方法时将会触发上述定义的 invoke() 方法。

运行下述示例代码即可发现打印信息由 ProxyService 中定义的 Hi 变为 Hello,即通过动态代理我们实现的不修改源程序的情况下更改的程序逻辑实现。

@Test
public void invokeDemo() throws Exception {
    // Create instance
    ProxyService service = new ProxyServiceImpl();
    MyInvokeHandler handler = new MyInvokeHandler(service);

    // Generate a proxy instance
    Class<ProxyService> clazz = ProxyService.class;
    ProxyService proxyService = (ProxyService) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, handler);

    // Call method
    proxyService.sayHi();
}

3. 异常处理

我们都在 Java 中异常分为检查型 (继承自 Exception) 和非检查型 (继承自 RuntimeException)。而在动态代理时,当程序发生异常时默认抛出的异常是 UndeclaredThrowableException,而非实际的异常。

如下述示例中在方法执行时抛出异常,和之前一样创建代理对象并调用方法。

public class ProxyServiceImpl implements ProxyService {
    @Override
    public void goWrong() throws RuntimeException {
        throw new RuntimeException("Wrong");
    }
}

运行程序后这里我提取核心的堆栈,可以看到输出日志信息中顶级异常类型为 UndeclaredThrowableException 而非实际的异常。

java.lang.reflect.UndeclaredThrowableException
    at jdk.proxy2/jdk.proxy2.$Proxy8.goWrong(Unknown Source)
Caused by: java.lang.reflect.InvocationTargetException
    ... 28 more
Caused by: java.lang.RuntimeException: Wrong
    ... 30 more

因此,在捕获代理对象实例方法异常时,需要通过 e.getCause() 获取子异常从而获取真正的异常对象。

@Test
public void invokeDemo() throws Exception {
    // Create instance
    ProxyService service = new ProxyServiceImpl();
    MyInvokeHandler handler = new MyInvokeHandler(service);

    // Generate a proxy instance
    Class<ProxyService> clazz = ProxyService.class;
    ProxyService proxyService = (ProxyService) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, handler);
    
    try {
        proxyService.goWrong();
    } catch (Throwable e) {
        e.getCause().printStackTrace();
    }
}

文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录