0%

Java之动态代理

在Java中有种动态代理技术,比如JDK,CGLIB,Javassist,ASM,其中最常用的动态代理有两种:一种JDK动态代理,这是JDK自带的功能;另一种CGLIB,这是第三方提供的一个技术。目前,Spring常用JDK和CGLIB,而MyBatis还使用了Javassist,无论哪种代理其技术和理念都是相似的。

下面讲一下常用的JDK动态代理和CGLIB动态代理。

1. JDK动态代理

JDK动态代理必须借助一个接口才能产生代理对象,所以先定义一个接口,如下所示:

1
2
3
4
5
6
7
8
/**
* 定义接口
* @author 郑松涛
*
*/
public interface HelloWorld {
public void sayHelloWorld();
}

然后提供实现类HelloWorldIMpl来实现接口:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 接口实现
* @author 郑松涛
*
*/
public class HelloWorldImpl implements HelloWorld {

@Override
public void sayHelloWorld() {
System.out.println("Hello World!");
}
}

接着要建立代理对象和真实对象HelloWorldImpl的关系,然后实现代理逻辑,通过JdkProxyExample类来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class JdkProxyExample implements InvocationHandler {

//真实对象
private Object target = null;

/**
* 建立代理对象和真实对象的代理关系,并返回代理对象
* @param target
* @return 代理对象
*/
public Object bind(Object target) {
this.target = target;

return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}

/**
* 代理方法逻辑
* @param proxy 代理对象
* @param method 当前调度方法
* @param args 当前方法的参数
* @return 代理结果返回
* @throws Throwable 异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("进入代理逻辑方法");
System.out.println("在调度真实对象之前的服务");
Object obj = method.invoke(target, args); //相当于调用sayHelloWorld方法
System.out.println("在调度真实对象之后的服务");
return obj;
}

}

这里使用了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
2
3
4
5
6
7
8
@Test
public void testJdkProxy() {
JdkProxyExample jdkProxy = new JdkProxyExample();
//绑定关系,因为挂在接口HelloWorld下,所以声明代理对象HelloWorld proxy
HelloWorld proxy = (HelloWorld) jdkProxy.bind(new HelloWorldImpl());
//注意,此时HelloWorld对象proxy已经是一个代理对象,它会进入代理的逻辑方法invoke里
proxy.sayHelloWorld();
}

运行结果:

1
2
3
4
进入代理逻辑方法
在调度真实对象之前的服务
Hello World!
在调度真实对象之后的服务

代码的实际运行是首先调用jdkProxy代理逻辑类对象的bind方法生成真实对象HelloWolrdImpl的代理对象proxy。该代理对象和HelloWolrdImpl一样同属于HelloWorld接口下,我们便可调用HelloWorld接口定义的方法。当我们调用sayHelloWorld时,实际是调用代理对象proxy的invoke方法,具体你步骤如下:
在这里插入图片描述
也就是先打印输出,然后通过method.invoke方法调用真实对象HelloWolrdImpl的sayHelloWorld方法, 然后在打印输出信息,当然我们可以在调用真实的对象的方法前后,可以调用执行我们自定义的方法,这也是代理的作用。

2. CGLIB动态代理

JDK动态代理必须提供接口才能使用,在一些不能提供接口的环境中,只能采用其他第三方技术,比如CGLIB动态代理。它的优势在于不需要提供接口,只要一个非抽象类就可以实现的动态代理。

下面演示一下CGLIB动态代理的使用:

首先需要搭建好环境,使用CGLIB动态代理需要使用使用两个Jar。如下所示:
在这里插入图片描述
接口创建HelloWorld类,也就是需要产生代理对象的真实对象。具体代码如下:

1
2
3
4
5
6
public class HelloWorld {

public void sayHelloWorld() {
System.out.println("Hello World!");
}
}

然后需要创建一个代理逻辑类,用于产生代理对象并实现代理逻辑方法,它需要实现MethodInterceptor接口的intercepter方法。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class CglibProxyExample implements MethodInterceptor {
/**
* 生成CGLIB代理对象
* @param cls Class类
* @return Class类的CGLIB代理对象
*/
public Object getProxy(Class cls) {
//CGLIB enhancer增强类对象
Enhancer enhancer = new Enhancer();
//设置增强类型,这里的cls即为要生成代理对象的真实对象的Class
enhancer.setSuperclass(cls);
//定义代理逻辑对象为当前对象,要求当前对象实现MethodInterceptor方法
enhancer.setCallback(this);
//生成并返回代理对象
return enhancer.create();
}

@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("调用真实对象方法前执行");
//CGLIB反射调用真实对象方法
Object obj = methodProxy.invokeSuper(proxy, args);
System.out.println("调用真实对象方法后执行");
return obj;
}

}

需要说明的是,这里用到了CGLIB的加强者Enhancer,通过设置超类的方法(setSuperclass),然后通过setCallBack方法设置那个类为它的代理类。其他的详见注释。

最后我们测试一下CGLIB动态代理,测试代码如下:

1
2
3
4
5
6
@Test
public void testCglib() {
CglibProxyExample proxyExample = new CglibProxyExample();
HelloWorld proxy = (HelloWorld) proxyExample.getProxy(HelloWorld.class);
proxy.sayHelloWorld();
}

运行结果:

1
2
3
调用真实对象方法前执行
Hello World!
调用真实对象方法后执行
------ 本文结束------