在Java中有种动态代理技术,比如JDK,CGLIB,Javassist,ASM,其中最常用的动态代理有两种:一种JDK动态代理,这是JDK自带的功能;另一种CGLIB,这是第三方提供的一个技术。目前,Spring常用JDK和CGLIB,而MyBatis还使用了Javassist,无论哪种代理其技术和理念都是相似的。
下面讲一下常用的JDK动态代理和CGLIB动态代理。
1. JDK动态代理
JDK动态代理必须借助一个接口才能产生代理对象,所以先定义一个接口,如下所示:
1 | /** |
然后提供实现类HelloWorldIMpl来实现接口:
1 | /** |
接着要建立代理对象和真实对象HelloWorldImpl的关系,然后实现代理逻辑,通过JdkProxyExample类来实现:
1 | public class JdkProxyExample implements InvocationHandler { |
这里使用了bind方法去完成代理对象和真实对象之间关系的建立。方法里面首先使用类的属性target去保存真实对象,然后通过如下代码建立并生成代理对象。
1 | Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); |
其中newProxyInstance方法包含3个参数。
- 第一个参数为类加载器,我们采用了target本身的类加载器。
- 第二个参数是把生成的动态代理对象下挂在哪些接口下,这个写法就是放在target实现的接口下,getInterfaces可以获取target所实现的所有的接口。
- 第三个是定义实现方法逻辑的代理类,this表示当前对象,它必须实现InvocationHandler接口的invoke方法,它就是代理逻辑方法的现实方法。
另外,使用到了invoke方法来实现代理逻辑方法。invoke方法的三个参数的含义如下:
- proxy,代理对象,就是bind方法生成的对象。
- method,当前调度的方法。
- args,调度方法的参数。
测试JDK动态代理,代码如下:
1 |
|
运行结果:
1 | 进入代理逻辑方法 |
代码的实际运行是首先调用jdkProxy代理逻辑类对象的bind方法生成真实对象HelloWolrdImpl的代理对象proxy。该代理对象和HelloWolrdImpl一样同属于HelloWorld接口下,我们便可调用HelloWorld接口定义的方法。当我们调用sayHelloWorld时,实际是调用代理对象proxy的invoke方法,具体你步骤如下:
也就是先打印输出,然后通过method.invoke方法调用真实对象HelloWolrdImpl的sayHelloWorld方法, 然后在打印输出信息,当然我们可以在调用真实的对象的方法前后,可以调用执行我们自定义的方法,这也是代理的作用。
2. CGLIB动态代理
JDK动态代理必须提供接口才能使用,在一些不能提供接口的环境中,只能采用其他第三方技术,比如CGLIB动态代理。它的优势在于不需要提供接口,只要一个非抽象类就可以实现的动态代理。
下面演示一下CGLIB动态代理的使用:
首先需要搭建好环境,使用CGLIB动态代理需要使用使用两个Jar。如下所示:
接口创建HelloWorld类,也就是需要产生代理对象的真实对象。具体代码如下:
1 | public class HelloWorld { |
然后需要创建一个代理逻辑类,用于产生代理对象并实现代理逻辑方法,它需要实现MethodInterceptor接口的intercepter方法。具体代码如下:
1 | public class CglibProxyExample implements MethodInterceptor { |
需要说明的是,这里用到了CGLIB的加强者Enhancer,通过设置超类的方法(setSuperclass),然后通过setCallBack方法设置那个类为它的代理类。其他的详见注释。
最后我们测试一下CGLIB动态代理,测试代码如下:
1 |
|
运行结果:
1 | 调用真实对象方法前执行 |