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();
}
}