[关闭]
@lxx3061313 2017-08-07T02:24:47.000000Z 字数 2764 阅读 195

浅谈Java代理

代理模式,java代理

背景

代理模式在生活中随处可见,每当过年回家的时候我们总会想起那些奋斗在一线的火车票黄牛,他们是那么的亲切和温暖,可以轻轻松松的帮助你完成回家的梦想。那么同样的,在平常的开发工作中,我们总会遇到为一个对象提供代理对象的需求。因为在某些情况下,一个对象可能不合适或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介作用。
在Java中代理有两种模式:静态代理和动态代理。

静态代理

静态代理的实现方式比较直接,可以描述为:

  1. 创建一个接口,然后创建被代理类来实现该接口,并且实现接口中的抽象方法。之后再创建一个代理类,同是也实现这个接口。在代理类中持有一个被代理对象的引用,而且在代理类方法中调用该对象的方法。

上面这段话比较绕,但是确实说出了静态代理的实现方式。下面按照上面的描述做一个简单的实现:
1.创建一个接口
image_1bmoic3bvsl8f5b1so41mmm1jmfc.png-4.5kB
2.创建被代理类
image_1bmoid0gf1qtv9haf91vmv1ju6p.png-13.2kB
3.创建代理类
image_1bmoifoun77r91q1eqjed11v5m16.png-20.6kB
4.测试调用
image_1bmoikcj21klapsm1g38vss1u342j.png-14.8kB

在静态代理中,一个委托类对应一个代理类,代理类在编译期间就已经确定了。这种方式的缺点非常明显,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。

动态代理

动态代理解决了上述静态代理的问题,所谓动态就是代理类在运行时生成,相比静态代理,动态代理可以很方便的对委托的方法进行统一处理。动态代理有两种实现方式jdk动态代理和cglib动态代理。本文先讲解jdk动态代理的实现。在接下来的内容中同学们会彻底理解jdk动态代理的本质。
同样的为了理解,我们先简单写个小程序。
1.创建接口
image_1bmoic3bvsl8f5b1so41mmm1jmfc.png-4.5kB
2.创建被代理类
image_1bmoid0gf1qtv9haf91vmv1ju6p.png-13.2kB
3.创建统一代理类
image_1bmoj7l3fokk1sb4sk413p019de30.png-54.7kB
4.测试调用
image_1bmoj8nvq11u1tg17u7cfp15tr3d.png-15.8kB

上面例子同静态代理不同的地方在于第三步,统一代理类的实现。可以看到CommonProxy实现了一个接口InvocationHandler,那么什么是InvocationHandler呢?

  1. Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

从上面的官网描述中我们可以抓住一个重点,当proxy instance触发一个调用,这个调用会通过某种方式传递到invocation handler的调用方法中。然后由invocation handler去完成最终被代理类的调用。
那么这里有两个核心的问题必须解决

运行时生成代理实例

在CommonProxy的实现中,通过内部的getProxy()来获得代理实例。而getProxy()内部是通过

  1. Proxy.newProxyInstance(loader, interfaces, this);

来完成代理实例的创建。那我们看一下newProxyInstance完成了什么工作:
image_1bmok9kj2h0815ah19vj1hbuvio3q.png-126.9kB
分析一下上图的代码,我们看到其核心主要完成了两项工作

  1. 1.Look up or generate the designated proxy class.
  2. 2.Invoke its constructor with the designated invocation handler.

我们知道在Java体系中,要想创建一个类实例,首先要有一个类的定义文件(字节码文件)。在上述两个步骤中,第一步即完成了类文件的创建。下面我们重点看下类文件怎么生成的。即

  1. Class<?> cl = getProxyClass0(loader, intfs);

做了啥?
image_1bmokndgj14191avj1gk025snib47.png-45.4kB
proxyClassCache是一个WeekCache,定义如下:
image_1bmokq1jmumc1m1v13a8183dah24k.png-15.2kB
我们暂且不管为什么这个Cache叫WeekCache,从其定义的结构上看,它是一个结构的缓存,缓存的内容从定义来看,就是缓存了每个classLoader所加载的Class。因为动态代理的代理实例是在运行期完成创建,所以肯定是不在这个缓存中的。那么就会通过ProxyClassFactory来创建。由于ProxyClassFactory代码过长,这里我只贴出核心代码,有兴趣的同学可以去一下源码。
image_1bmolf4qi1bg859qamdtvdocs51.png-42kB
在ProxyClassFactory主要做了两个工作:

  1. 1.生成字节码文件,通过ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags)完成。
  2. 2.加载字节码文件并解析成Class对象,通过defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length)完成。

调用传递

字节码的生成方法和解析工作不是本文的重点,在此不表。那么我们接下看一下生成的是一个什么样的Class。上述源代码提供的方法,我们来生成一个代理类:
image_1bmombt8cf3q15t517fm14upjp15e.png-33.5kB
程序运行完会在指定目录下生成Class文件
image_1bmomeskd19en14er1etp1bgbh6d5r.png-6.7kB
代理类名叫1.class.然后我们直接用IDE打开这个Class文件,可以看到反编译后的结果。
image_1bmomi86okjb1huinn8rl91n5p68.png-178.1kB
从反编译后的结果,我们可以得出以下信息:
1.代理类的名称为1
2.代理类继承了Proxy类,并实现了Service(自定义)接口。
3.根据父类信息,代理类定义了四个成员变量,都是Method类型,其中m3为Service中的service方法。
4.在代理类内部依赖了InvocationHandler,也就是完成了代理类跟目标类的绑定。
5.在调用阶段直接调用了InvocationHandler的invoke方法。
6.在代理类内部依赖了InvocationHandler。
7.InvocationHandler后面的内容可以参考上面的解释。

总结

本文主要讲解了Java代理的两种模式:静态代理和动态代理。静态代理是比较简单,缺点也比较突出。为了弥补静态代理的缺点,这里引出了动态代理。在动态代理部分我们主要解释清楚了动态代理的两大核心工作
1.运行时创建代理类
在运行时,jdk为根据目标接口(Service)信息,生成代理类的字节码文件,并通过制定的Classloader来完成字节码文件的加载和解析,并返回一个代理类的Class。最后通过该Class的构造函数完成代理类实例的创建。
2.调用怎么传递到我们自定义的InvocationHandler
从生成的代理类Class可以看出,在代理类内部完成了对InvocationHandler的绑定。那么调用就可以顺利成章的传递到相应的InvocationHandler中。那么InvocationHandler的引用在什么时候传递进来的呢??请同学们自己分析一下。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注