理解Java中三种代理模式

我们前一篇文章已经说明了什么是代理模式,本文就谈谈Java中的三种代理模式。它们分别是静态代理,JDK动态代理,以及Cglib动态代理。

静态代理

所谓静态代理,就是经典的代理模式,一个代理者需要对应一个被代理者,如下图所示:

经典代理模式

不过缺点也很明显,就是不够灵活,如果需要被代理的对象数量一增加,对于编写代理类的同学来说无疑是灾难。下面给出一个具体的例子:

代理接口类

1
2
3
4
5
6
7
8
9
package com.qinjiangbo.spring.proxy;

/**
* @date: 21/12/2016 2:08 PM
* @author: [email protected]
*/
public interface Service {
void fork(String repository);
}

被代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.qinjiangbo.spring.proxy;

/**
* @date: 21/12/2016 2:11 PM
* @author: [email protected]
*/
public class GitService implements Service {

@Override
public void fork(String repository) {
System.out.println("fork " + repository + " successfully!");
}
}

代理类:

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
package com.qinjiangbo.spring.proxy;

/**
* @date: 21/12/2016 2:22 PM
* @author: [email protected]
*/
public class GitStaticProxy implements Service {

private GitService gitService;

public GitStaticProxy(GitService gitService) {
this.gitService = gitService;
}

@Override
public void fork(String repository) {
logBefore();
gitService.fork(repository);
logAfter();
}

private void logBefore() {
System.out.println(this.getClass().getName() + " @@@ "
+ "before fork() invoked!");
}

private void logAfter() {
System.out.println(this.getClass().getName() + " @@@ "
+ "after fork() invoked!");
}

}

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.qinjiangbo.spring.proxy;

import org.junit.Test;

/**
* @date: 21/12/2016 2:33 PM
* @author: [email protected]
*/
public class GitStaticProxyTest {

@Test
public void testFork() {
Service service = new GitStaticProxy(new GitService());
service.fork("tomcat");
}
}

输出结果如下:

1
2
3
com.qinjiangbo.spring.proxy.GitStaticProxy @@@ before fork() invoked! 
fork tomcat successfully!
com.qinjiangbo.spring.proxy.GitStaticProxy @@@ after fork() invoked!

JDK动态代理

针对这一种情况,JDK也提出了对应的方式,那就是JDK动态代理,它允许你代理任何实现了某个接口的具体实现类。其实它基于的底层原理还是经典的代理模式,只不过它通过反射的原理动态地创建出了代理类。本质上还是通过实现一个共同的接口来代理对象的行为。

下面给出一个JDK动态代理模式的例子:

接口类还是不变,同上

1
2
3
4
5
6
7
8
9
package com.qinjiangbo.spring.proxy;

/**
* @date: 21/12/2016 2:08 PM
* @author: [email protected]
*/
public interface Service {
void fork(String repository);
}

JDK动态代理类

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
35
36
37
38
39
40
41
42
43
44
45
package com.qinjiangbo.spring.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
* @date: 22/12/2016 6:30 PM
* @author: [email protected]
*/
public class GitJdkProxy implements InvocationHandler {

private Object target;

public GitJdkProxy(Object target) {
this.target = target;
}

public <T> T getProxy() {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}

@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
logBefore();
Object result = method.invoke(target, args);
logAfter();
return result;
}

private void logBefore() {
System.out.println(this.getClass().getName() + " @@@ "
+ "before fork() invoked!");
}

private void logAfter() {
System.out.println(this.getClass().getName() + " @@@ "
+ "after fork() invoked!");
}
}

可以看到,它实现了一个InvocationHandler接口,这个接口中有一个方法叫做invoke(Object proxy, Method method, Object[] args),它是这个代理类的具体执行方法。但是它是如何获取代理的呢?

1
2
3
4
5
6
7
public <T> T getProxy() {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}

上面的这部分代码给出了答案,就是通过被代理类实现的接口反射地来创建这个代理类,本质上就是我们前面说到的经典的代理模式,基于共同的实现接口类。

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.qinjiangbo.spring.proxy;

import org.junit.Test;

/**
* @date: 22/12/2016 6:39 PM
* @author: [email protected]
*/
public class GitJdkProxyTest {

@Test
public void testFork() {
Service service = new GitJdkProxy(new GitService()).getProxy();
service.fork("tomcat");
}
}

结果输出如下:

1
2
3
com.qinjiangbo.spring.proxy.GitJdkProxy @@@ before fork() invoked! 
fork tomcat successfully!
com.qinjiangbo.spring.proxy.GitJdkProxy @@@ after fork() invoked!

Cglib动态代理

Cglib全称是Code Generator Library,字节码生成库。大家也能猜的到它的原理是什么了吧?它是通过字节码技术动态地生成当前类的子类,从而实现对父类的代理。它的底层原理本上是继承,而不是实现

我们来看看它是怎么实现代理的?不需要实现某个接口,但是为了方便起见,我们还是选择上面提到的GitService来进行这个测试。

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
35
36
37
package com.qinjiangbo.spring.proxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
* @date: 24/12/2016 10:06 PM
* @author: [email protected]
*/
public class GitCglibProxy implements MethodInterceptor {

public <T> T getProxy(Class<T> clazz) {
return (T) Enhancer.create(clazz, this);
}

@Override
public Object intercept(Object o, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
logBefore();
Object result = methodProxy.invokeSuper(o, objects);
logAfter();
return result;
}

private void logBefore() {
System.out.println(this.getClass().getName() + " @@@ "
+ "before fork() invoked!");
}

private void logAfter() {
System.out.println(this.getClass().getName() + " @@@ "
+ "after fork() invoked!");
}
}

在上面的代码中我们可以看到也是存在一个getProxy()方法,抽取出来如下:

1
2
3
public <T> T getProxy(Class<T> clazz) {
return (T) Enhancer.create(clazz, this);
}

可以看出它是通过Enhancer.create(clazz, this)来创建这个clazz的子类的,直接在内存中生成一个当前类的子类,然后在intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)方法中实现对父类行为的代理。

测试类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.qinjiangbo.spring.proxy;

import org.junit.Test;

/**
* @date: 24/12/2016 10:14 PM
* @author: [email protected]
*/
public class GitCglibProxyTest {

@Test
public void testFork() {
Service service = new GitCglibProxy().getProxy(GitService.class);
service.fork("tomcat");
}
}

测试结果如下:

1
2
3
com.qinjiangbo.spring.proxy.GitCglibProxy @@@ before fork() invoked! 
fork tomcat successfully!
com.qinjiangbo.spring.proxy.GitCglibProxy @@@ after fork() invoked!

JDK动态代理 vs Cglib动态代理

关于JDK动态代理和Cglib动态代理两种方式,这里我简单总结一下:

  • [相同] 二者都能够动态地产生某个对象的代理对象,使用起来非常灵活。
  • [不同] JDK动态代理依据的底层原理是实现一个共同的接口,而Cglib动态代理依据的底层原理是创建某个类的子类。
  • [不同] 运行性能上JDK和Cglib其实差不多,没有传说中的Cglib比JDK快10倍的差距,至少在最近的几个JDK版本(6,7,8)是这样的,版本越靠后甚至JDK更快一点。

总结

关于JDK动态代理和Cglib动态代理到底选择哪一种其实要根据自己的实际情况来确定,其实我所了解到的很多RPC框架比如HSF就使用的是JDK动态代理实现的远端服务请求和响应代理。当然Spring AOP中提供了这两种选择,大家可以在配置文件中自己指定对应的动态代理方式。默认情况下,Spring AOP会采用JDK动态代理来代理对象。当然我们也可以使用<aop:aspectj-autoproxy proxy-target-class="true"/>来强制使用Cglib动态代理。

参考文献Cglib 与 JDK动态代理的运行性能比较

分享到