前言
JDK 动态代理可以让我们很容易地实现代理模式。通过解析动态代理的实现机制,我们可以更好地使用它。
一个例子
我们先来写个简单的例子,先定义两个接口 Subject 和 Subject2。
|
|
再定义一个实现类来实现上面的两个接口。
|
|
下面,我们还得实现 InvocationHandler 接口,这个接口有一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。我们在其中加入了处理逻辑,用于在实际调用方法之前打印一句 log,这也是动态代理常用的一个场景。
|
|
最后,我们写一个 main 方法,来测试动态代理。
|
|
运行之后,可以看到控制台的输出如下:
|
|
这里有两个注意点:
- 在 invoke 方法中,不能使用参数中的 proxy 来调用方法,这样会导致循环调用,最终导致栈溢出。
- 在 main 方法中, subject 的对象声明可以是 Subject、Subject2、SubjectImpl中的任意一个,因为这里不是根据对象的声明来调用方法。
附上类图供参考。
原理分析
下面,我们来分析一下动态代理的源码实现,这里我们使用的是 JDK 1.8.0_144 版本。我们先来看下 Proxy.newProxyInstance() 这个方法,这个方法接收一个 ClassLoader,一组接口和一个 InvocationHandler。
|
|
这里的关键是 getProxyClass0() 这个方法,我们看到方法里面是从一个 cache 中获取代理类。这个 cache 的作用是缓存已经生成的代理类以便后续使用,如果没有符合要求的代理类,则会根据该 cache 构造方法传入的 ProxyClassFactory 生成一个我们需要的代理类。下面我们看下 ProxyClassFactory 的实现,它主要实现了一个 apply() 方法,来生成代理类。
|
|
继续看 ProxyGenerator.generateProxyClass() 方法的实现。里面主要调用了 ProxyGenerator 的 generateClassFile() 方法,而该方法主要的作用是为代理类添加代理方法,将他设置为 Proxy 类的子类,然后转成字节码数组并返回。
在获取代理类之后,我们调用构造方法,生成了代理类实例对象。想看下我们生成的代理类长什么样吗?我们可以使用 IntelliJ IDEA 并在 main 方法第一行加一句:
|
|
这样,我们就可以在项目目录下的 com.sun.proxy 包中看到动态生成的代理类了。
|
|
如我们所料,生成的 $Proxy0 类继承了 Proxy 类,并且实现了 Subject、Subject2 接口。而方法则通过调用父类 Proxy 的 InvocationHandler 的 invoke() 方法来实现。