JDK动态代理
在JDK1.3以后提供了动态代理的技术,允许开发者在运行期创建接口的代理实例。在Sun刚推出动态代理时,还很难想象它有多大的实际用途,现在动态代理是实现AOP的绝好底层技术。
JDK的动态代理主要涉及Java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。而Proxy为InvocationHandler实现类动态创建一个复合某一接口的代理的实例。
JDK动态代理的使用
要想模拟JDK动态代理的实现,首先要明白它的内在机理,知道它对外开放的接口方法。借用之前文章中JDK动态代理时的简单示例,仅包括两个类:TimeHandler类和Test类,来回顾下JDK动态代理的使用。
1 | package com.jdkproxy; |
1 | package com.jdkproxy; |
运行结果:
1 | 汽车开始行驶 |
JDK动态代理实现思路
如上Test类中,通过Proxy的newProxyInstance()方法动态产生一个代理。模拟JDK动态代理实现流程,同样要自定义一个Proxy类并在类中实现产生动态代理的方法,这也是实现JDK动态代理的关键点和主要功能。
动态代理实现步骤如下:
- 声明一段源码(动态产生代理)
- 编译源码(JDK Compiler API),产生代理类
- 将新产生的代理类load到内存当中,产生一个新的对象即代理对象
- 在newProxyInstance方法中返回这个代理对象
声明一段源码(动态产生代理)
将源码定义为字符串,Proxy类中代码如下:
1 | package com.wy.proxy.myproxy; |
由于JDK动态代理产生的类名为$Proxy0,所以在此模仿此类名。在接下来的工作中要对源码进行编译,所以要将源码写入一个java文件。
在Proxy类中增加以下代码:
1 | String filename = System.getProperty("user.dir") +"/bin/com/wy/proxy/myproxy/$Proxy0.java"; |
其中FileUtils类需要导入包commons-io.jar,运行以上代码后可在Navigator视窗看到对应包下生成了$Proxy.java文件。
由于动态代理要实现对任意接口的任意方法进行代理,所以要对以上的源码字符串进行更改,更改思路即将接口当作参数传入newProxyInstance方法,更改之后的Proxy类如下所示:
1 | package com.wy.proxy.myproxy; |
Test测试类中将接口参数传入:
1 | package com.wy.proxy.myproxy; |
运行后检查生成的$Proxy.java文件是否正确。
编译源码(JDK Compiler API),产生代理类
编译即相当于我们在黑窗口时用的javac命令,在程序中编译的代码如下:
1 | //拿到编译器 |
测试运行成功后可发现在新生成的代理类java文件同目录下生成了对应的class文件,即$Proxy0.class。
将新产生的代理类load到内存当中,产生一个新的对象即代理对象
关键代码如下:
1 | //load 到内存 |
在Test类中测试是否创建成功:
1 | package com.wy.proxy.myproxy; |
运行结果:
1 | 汽车开始行驶 |
可以看到通过使用自己定义的Proxy.newProxyInstance方法成功返回了代理对象,与一开始的时候使用JDK动态代理得到同样的结果。以下为自定义Proxy类的全部代码:
1 | package com.wy.proxy.myproxy; |
目前这个Proxy类实现了对任意接口任意方法的代理,但是这只不过完成了一半,因为其中代理的逻辑是写死的,只能实现时间记录。这就需要继续改进,在JDK动态代理中是通过InvocationHandler来实现灵活改变代理逻辑的,继续模仿,我们自定义一个接口也叫做InvocationHandler。
1 | package com.wy.proxy.myproxy; |
这个接口需要的功能就是要实现对方法代理逻辑的灵活增改,传入参数为需要代理的对象和方法。然后创建一个具体的代理类实现InvocationHandler 接口,在其中具体实现代理逻辑:
1 | package com.wy.proxy.myproxy; |
定义好对方法的事务处理器后,在Proxy类中引入使用。Proxy类修改后完整代码如下:
1 | package com.wy.proxy.myproxy; |
测试如下:
1 | 汽车开始行驶 |
上述代码中并没有贴出Car类的代码,下面补出:
1 | package com.wy.proxy.myproxy; |
到此就完成了对JDK动态代理的简单模拟,大致思路就是首先声明一段源码,这段源码用来动态产生代理类。然后把源码转换生成为Java文件,然后对这个Java文件进行编译,生成对应的class文件。之后再用类加载器加载到内存中,并生成代理对象,返回之。
在使用时需要先创建代理的具体逻辑即事务处理器InvocationHandler,事务处理器可以实现时间记录功能或日志功能。然后将写好的事务处理作为参数传入Proxy的newProxyInstance的方法,这样返回的就是我们需要的代理对象。
代码详见 PatternTest
参考资料:
王英豪 模拟JDK动态代理实现
David 模式的秘密——代理模式