为什么需要动态代理?
我们一般使用 代理模式 来增强函数的功能,比如在函数执行前记录 log 信息,进行安全性检测等。
常见的代理有静态代理和动态代理。在静态代理中,一个被代理的对象需要一个相应的代理类,当程序中所需代理的对象数量较多时,代理类的数量也会随之增多,这就导致了程序的体积膨胀,在运行时会占用更多的内存。除此之外,被代理的对象添加新的函数时,代理类也要随之添加对应的函数,这就导致了后期维护的不方便。
所谓动态代理,即通过字节码生成的方式动态的组装一个代理类(.class),无需在源码中单独编写代理类文件(.java)。与静态代理相比,动态代理中的代理类是在运行时产生的,而静态代理是在编译时产生的。
Java 内置的动态代理
首先提供所需代理的接口与其对应的实现类:
Hello.java
public interface Hello {
void sayHola();
void sayHello();
}
HelloImpl.java
public class HelloImpl implements Hello {
@Override
public void sayHola() {
System.out.println("Hola");
}
@Override
public void sayHello() {
System.out.println("Hello");
}
}
之后实现内置的InvocationHandler
接口:
MyInvocationHandler.java
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("sayHola")) {
System.out.println("代理 sayHola()");
} else if (method.getName().equals("sayHello")) {
System.out.printf("代理 sayHello()");
}
System.out.println("方法调用前");
Object o = method.invoke(target, args);
System.out.println("方法调用后");
return o;
}
}
最后通过内置的 Proxy 单例类来生成代理类:
Main.java
public class Main {
public static void main(String[] args) {
Hello target = new HelloImpl();
InvocationHandler handler = new MyInvocationHandler(target);
Hello proxy = (Hello) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
proxy.sayHello();
proxy.sayHola();
}
}
输出:
方法调用前
Hello
方法调用后
方法调用前
Hola
方法调用后
通过程序运行结果可以看出,在生成的代理对象proxy
调用sayHello()
和sayHola()
时,分别触发了我们所编写的对应逻辑,即实现了对Hello
接口的代理。
在生成动态代理字节码时,默认不保存动态生成的字节码文件,想要观察生成的字节码文件需要设置如下参数:
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
此时可以在项目根目录下的com.sun.proxy
路径中找到生成的动态代理类:
$Proxy0.class
public final class $Proxy0 extends Proxy implements Hello {
private static Method m1;
private static Method m4;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void sayHello() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void sayHola() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m4 = Class.forName("DynamicProxyTest.Hello").getMethod("sayHello");
m3 = Class.forName("DynamicProxyTest.Hello").getMethod("sayHola");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
可以看到生成的动态代理类继承了Proxy
类并通过反射的方式在静态代码块中获取了相关接口中的方法。该类内部的函数调用均通过传入的InvocationHandler
对象来间接触发。
为什么 Java 内置的动态代理只能代理接口实现类?
Java 内置的动态代理存在限制,即使用动态代理的对象必须实现一个或多个接口,如果想要突破上述限制,只能通过第三方的库,比如 cglib 来实现。
为什么要这样设计?个人认为,主要原因是代理接口比代理具体类更加灵活,所以 jdk 的开发者设计了这样的动态代理实现方式,即通过自定义的InvocationHandler
和相应的代理类信息(ClassLoader
和实现的Interface
等)作为模板来生成相应的Proxy
对象,此时生成的对象已经继承了Proxy
类,考虑到 Java 单继承机制的限制,也自然无法进一步代理继承类。除此之外,如果要判断某个对象是否是通过动态代理获得的,可以通过该对象是否继承Proxy
类来判断(当然也可以通过在生成的代理对象中额外实现一个标记性质的空接口来达到相同的效果)。