实用 AI

可在线运行 AI 集合,涵盖 AI 文案生成、写作辅助、AI 绘图与照片修复、AI 配音、字幕生成、语音转录以及 AI 视频创作和数字人等多种 AI 服务

查看详情

动态代理

深入学习Java基础知识
2021-05-17 14:29 · 阅读时长9分钟
小课

代理模式中,我们可以通过实现与代理对象相同的接口,来实现扩展代理对象的功能,这种代理模式是静态代理,它有以下几点不足。

  • 有很多模版代码,比如代理接口方法有很多,而我们关心的可能只有一两个,但是也必须对代理所有的方法。
  • 可维护性差,当接口新增方法时,代理也必须补充完善,相当于同时维护多份代码。
  • 需要在编译期就完成所有代码,无法在运行时添加。

而动态代理就弥补了这些不足,它是在运行时动态生成代理类的字节码,然后加载并使用,而且无需实现代理接口的所有方法,而是统一在invoke方法中,我们可以通过方法和参数等进行区分处理。

下面通过一段示例代码看看动态代理最简单的用法,假设我们有一个接口ISearchEngine,它有一个search方法,我们想在search方法调用前后分别加上日志。

1import java.lang.reflect.InvocationHandler;
2import java.lang.reflect.Method;
3import java.lang.reflect.Proxy;
4
5interface ISearchEngine {
6    String search(String text);
7}
8
9class SearchEngine implements ISearchEngine {
10    @Override
11    public String search(String text) {
12        System.out.println("search: " + text);
13        return "The result of the searching";
14    }
15}
16
17class LogInvocationHandler implements  InvocationHandler {
18    private final Object target;
19
20    public LogInvocationHandler(Object target) {
21        this.target = target;
22    }
23
24    @Override
25    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
26        System.out.println("before invoke: " + method.getName());
27        Object result = method.invoke(target, args);
28        System.out.println("after  invoke: " + method.getName());
29        return result;
30    }
31}
32
33public class Main {
34
35    public static void main(String[] args) {
36        ISearchEngine target = new SearchEngine();
37        ISearchEngine proxy = (ISearchEngine) Proxy.newProxyInstance(
38                Main.class.getClassLoader(),
39                new Class[]{ISearchEngine.class},
40                new LogInvocationHandler(target)
41        );
42        proxy.search("知行");
43    }
44}
注意: 这个Java运行环境不支持自定义包名,并且public class name必须是Main

动态代理是通过Proxy.newProxyInstance方法创建的,它有三个参数,一是用来加载动态生成的代理类的ClassLoader,二是需要动态代理的接口数组,三是添加扩展逻辑的InvocationHandler。

另外从上面示例可以看出InvocationHandler和代理的接口没有关联,也就是说这个InvocationHandler可以复用,任何其它需要加日志记录的动态代理都可以用它。

为什么调用代理类的方法时最终会回调到InvocationHandler呢,其实生成的代理类会持有InvocationHandler的引用,在调用代理类的方法时,会直接调用InvocationHandler.invoke方法,并填充对应的参数。

我们可以通过两种方法来保存动态生成的代理类。

  • 通过设置环境变量,System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true")。
  • 通过设置运行参数,-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true。

以上示例代码中生成的ISearchEngine代理类参考如下。

1import java.lang.reflect.InvocationHandler;
2import java.lang.reflect.Method;
3import java.lang.reflect.Proxy;
4import java.lang.reflect.UndeclaredThrowableException;
5
6final class $Proxy0 extends Proxy implements ISearchEngine {
7    private static Method m1;
8    private static Method m2;
9    private static Method m3;
10    private static Method m0;
11
12    public $Proxy0(InvocationHandler var1) throws  {
13        super(var1);
14    }
15
16    public final boolean equals(Object var1) throws  {
17        try {
18            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
19        } catch (RuntimeException | Error var3) {
20            throw var3;
21        } catch (Throwable var4) {
22            throw new UndeclaredThrowableException(var4);
23        }
24    }
25
26    public final String toString() throws  {
27        try {
28            return (String)super.h.invoke(this, m2, (Object[])null);
29        } catch (RuntimeException | Error var2) {
30            throw var2;
31        } catch (Throwable var3) {
32            throw new UndeclaredThrowableException(var3);
33        }
34    }
35
36    public final String search(String var1) throws  {
37        try {
38            return (String)super.h.invoke(this, m3, new Object[]{var1});
39        } catch (RuntimeException | Error var3) {
40            throw var3;
41        } catch (Throwable var4) {
42            throw new UndeclaredThrowableException(var4);
43        }
44    }
45
46    public final int hashCode() throws  {
47        try {
48            return (Integer)super.h.invoke(this, m0, (Object[])null);
49        } catch (RuntimeException | Error var2) {
50            throw var2;
51        } catch (Throwable var3) {
52            throw new UndeclaredThrowableException(var3);
53        }
54    }
55
56    static {
57        try {
58            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
59            m2 = Class.forName("java.lang.Object").getMethod("toString");
60            m3 = Class.forName("ISearchEngine").getMethod("search", Class.forName("java.lang.String"));
61            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
62        } catch (NoSuchMethodException var2) {
63            throw new NoSuchMethodError(var2.getMessage());
64        } catch (ClassNotFoundException var3) {
65            throw new NoClassDefFoundError(var3.getMessage());
66        }
67    }
68}
总结

动态代理相对于静态代理有很多优势,但是它也不是完美的,比如说它只能代理接口,还有它内部使用反射,这会导致性能有所下降。

动态代理javadynamic proxyInvocationHandlernewProxyInstance