代理
1. 关于 JDK 动态代理
关于动态代理见博客代理模式。
java.lang.reflect
包下提供了一个 Proxy
类和一个 InvocationHandler
接口,用它们可以生成 JDK 动态代理或动态代理对象。
不过要注意的是,JDK 动态代理只能为接口创建动态代理。
1.1 Proxy
类
Proxy
提供了用于创建动态代理类和代理对象的静态方法,它也是所有动态代理类的父类。如果在程序中为一个或多个接口动态地生成实现类,就可以使用 Proxy
来创建动态代理类;如果需要为一个或多个接口动态地创建实例,也可以使用 Proxy
来创建动态代理实例。
Proxy
提供了两个方法来创建动态代理类和动态代理实例:
static Class<?> getProxyClass(ClassLoader loader, Class<?>… interfaces)
:创建一个动态代理类所对应的Class
对象,该代理类将实现interfaces
所指定的多个接口。- 此时生成动态代理类之后,如果程序需要通过该代理类来创建对象,依然需要传入一个
InvocationHandler
对象。
- 此时生成动态代理类之后,如果程序需要通过该代理类来创建对象,依然需要传入一个
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
:直接创建一个动态代理对象,该代理对象的实现类实现了interfaces
指定的系列接口,执行代理对象的每个方法时都会被替换执行InvocationHandler
对象的invoke
方法。
综上,系统生成的每个代理对象都有一个与之关联的 InvocationHandler
对象。
1.2 InvocationHandler
类
定义 InvocationHandler
接口的实现类时需要重写invoke()
方法——调用代理对象的所有方法时都会被替换成调用该 invoke()
方法,该 invoke()
方法中的三个参数解释如下:
参数 | 作用 |
---|---|
proxy | 代表动态代理对象 |
method | 代表正在执行的方法 |
args | 代表调用目标方法时传入的实参 |
2. 何时使用代理
代理类可以在运行时创建全新的类,这样的代理类能够实现你指定的接口。
调用处理器(Invocation Handler)是实现了InvocationHandler
接口的类的对象,这个接口只有一个方法:
Object invoke(Object proxy, Method method, Object[] args);
无论何时调用代理对象的方法,调用处理器的 invoke
方法都会被调用,并向其传递 Method
对象和原调用的参数,之后调用处理器必须确定如何处理这个调用。
3. 创建代理对象
要想创建一个代理对象,需要使用Proxy
类的newProxyInstance
方法,其有三个参数:
- 一个类加载器;
- 一个
Class
对象数组,每个元素对应需要实现的各个接口; - 一个调用处理器。
如下代码中,我们使用代理对象跟踪一个二分查找:
public class ProxyTest {
public static void main(String[] args) {
var elements = new Ojbect[1000];
for (int i = 0; i < elements.length; i++) {
Integer value = i + 1;
var handler = new TraceHandler(value);
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[] {Comparable.class}, handler);
elements[i] = proxy;
}
// onstruct a random integer
Integer key = new Random().nextInt(elements.length) + 1;
// search for the key
int result = Arrays.binarySearch(elements, key);
// print match if found
if (result >= 0) {
System.out.println(elements[result]);
}
}
}
class TraceHandler implements InvocationHandler {
private Object target;
public TraceHandler(Object t) {
target = t;
}
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
System.out.print(target);
System.out.print("." + m.getName() + "(");
if (args != null) {
for (int i = 0; i < args.length; i++) {
System.out.print(args[i]);
if (i < args.length -1) {
System.out.print(", ");
}
}
}
System.out.println(")");
return m.invoke(target, args);
}
}
4. 代理类的特性
代理类是在程序运行过程中动态创建的。然而,一旦被创建,它们就变成了常规类,与虚拟机中的任何其他类没有什么区别。
所有的代理类都继承了 Proxy
类,一个代理类只有一个实例字段,即调用处理器,它在 Proxy
超类中定义。
所有的代理类都要覆盖 Object
类的 toString, equals, hashCode
方法,如同所有代理方法一样,这些方法只是在调用处理器上调用 invoke
。Object
类中的其他方法,如 clone
和 getClass
没有重新定义。
没有定义代理类的名字,JVM 中的 Proxy
类将生成一个以字符串 $Proxy
开关的类名。
对于一个特定的类加载器和预设的一组接口来说,只能有一个代理类。也就是说,如果使用同一个类加载器和接口数组调用两次 newProxyInstance
方法,将得到同一个类的两个对象。也可以利用 getProxyClass
方法获得这个类:
Class proxyClass = Proxy.getProxyClass(null, interfaces);
代理类总是 public
和 final
。如果代理类实现的所有接口都是public
,这个代理类就不属于任何特定的包;否则,所有非公共的接口都必须属于同一个包,同时,代理类也属于这个包。
可以通过调用 Proxy
类的 isProxyClass
方法检测一个特定的 Class
对象是否表示一个代理类。