在上一篇文章中,详细介绍了 Java 中的反射机制,反射为 Java 提供了运行时修改程序的能力。
与之相对的,Java 中有另外一个技术也经常被提及,动态代理。
这个技术在 Java 中同样在很多框架中同样得到了大量的应用。
那么动态代理到底是什么,和反射又有什么关系?
💡本文基于 OpenJDK11
1. 代理模式
在讲动态代理之前,我们需要先了解一下代理模式。
假设现在有一个 RPC 接口,需要统计每个 RPC 接口的调用时间,但是这些统计执行时间的代码于业务逻辑没有关系,这些代码适合独立出来。
public interface Hello {
void sayHello(String name);
}
public class HelloImpl implements Hello {
@Override
public void sayHello(String name) {
System.out.println("Hello " + name);
}
}
复制代码
使用代理模式就是一个比较好的办法,代理模式通常用于为现有的类添加额外的功能,而且不用修改现有的代码。
代理模式可以分成静态代理和动态代理。
需要注意,如果使用动态代理,最好现有的类是基于接口来实现的。如果不是基于接口,那么这个类只能实现静态代理,而无法实现基于反射的动态代理。
2. 静态代理实现
静态代理的实现比较简单,核心就是实现这个接口,然后就可以在这个实现中调用目标对象的方法,并且可以做一些额外的事情。
public class HelloStaticProxy implements Hello {
private Hello helloImpl;
public HelloStaticProxy(Hello helloImpl) {
this.helloImpl = helloImpl;
}
@Override
public void sayHello(String name) {
long begin = System.currentTimeMillis();
helloImpl.sayHello(name);
System.out.println("Invoke time: " + (System.currentTimeMillis() - begin) + " ms");
}
}
复制代码
上面的代码就可以统计出 sayHello 这个 rpc 接口的调用时间。
但是静态代理有一个很大的问题,假设有 n 个 rpc 接口,就需要把上面的逻辑重复实现很多遍。这对于大型系统或者通用框架中肯定是不能接受的。
但是静态代理的优点是性能好,在只有个别类需要被代理的时候,静态代理还是首选。
3. 动态代理实现
在 Java 中,动态代理有多种实现。
最直接的一种就是通过反射来实现。代码也不复杂,与上面静态代理不同的地方在于,使用动态代理可以生成任何类的代理。
如果选择使用反射来实现动态代理,那么就要求这个被代理的类是基于接口实现的。
下面代码中 InvocationHandler
和 Proxy
都在 java.lang.reflect
中:
Hello hello = new HelloImpl();
HelloDynamicProxyHandler handler = new HelloDynamicProxyHandler(hello);
// 生成的动态代理对象
Hello helloProxy = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), HelloImpl.class.getInterfaces(), handler);
// 调用方法
helloProxy.sayHello("ray");
public class HelloDynamicProxyHandler implements InvocationHandler {
private Object target;
public HelloDynamicProxyHandler(Object object) {
this.target = object;
}
@Override
public Object invoke(Object o, Method method, Object[] params) throws Throwable {
long begin = System.currentTimeMillis();
Object result = method.invoke(target, params);
System.out.println("Invoke time: " + (System.currentTimeMillis() - begin) + " ms");
return result;
}
}
复制代码
其实这种实现很容易理解,我把上篇文章中利用反射来动态执行方法的代码贴在这里:
// 利用反射动态执行方法
public Object methodInvoke(Object o, Method method, Object[] params) {
try {
return method.invoke(o, params);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
复制代码
与上面动态代理的实现基本一样,这是动态代理最直观,最简单的一种实现方式。
相比于静态代理,动态的性能就要差很多,但是更灵活。
其他的实现就需要引入额外的依赖,比如 cglib 等。cglib 是通过生成被代理类的子类对象来作为动态代理,所有就不要求被代理类是基于接口实现的。
这些具体的实现后续再介绍,这篇文章重点还是放在动态代理本身。
4. 动态代理的应用
动态代理在 Java 最常用的场景就是 AOP 编程和解耦。
在 Spring 中 AOP 的实现有两种,反射和 cglib,可以自由选择。
spring.aop.proxy-target-class=true
复制代码
在 Java8 以前,选择使用 cglib 更多是因为性能的原因,但是在之后,就没必要了。
除了 AOP,动态代理也可以用于解耦,典型的情况就是在 RPC 中,使用动态代理,可以让调用远程的接口和本地方法一样简单。
同时动态代理还可以将很多于业务无关的细节屏蔽,比如权限,统计等等。
文 / Rayjun
本文首发于微信公众号
欢迎关注我的微信公众号
评论