代理模式
定义:给某个对象提供一个代理对象,用户通过代理对象控制对原对象的访问。
上图中:
- RealSubject 是原对象(本文把原对象称为”委托对象”),Proxy 是代理对象。
- Subject 是委托对象和代理对象都共同实现的接口。
- Request() 是委托对象和代理对象共同拥有的方法。
JAVA实现如下: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
27interface Subject{
void request();
}
class RealSubject implements Subject{
public void request(){
System.out.println("request");
}
}
class Proxy implements Subject{
private RealSubject realSubject;
public Proxy(){
this.realSubject = new RealSubject();
}
public void request(){
System.out.println("PreProcess");
realSubject.request();
System.out.println("PostProcess");
}
}
public class ProxyDemo {
public static void main(String args[]){
Proxy p = new Proxy();
p.request();
}
}
代理分为两类:
- 静态代理:代理类实现就写好了,在Java编译后是一个实际存在的class文件。
- 动态代理:代理类是在程序运行时动态生成的类字节码,并加载到JVM中。
上文中所说的是静态代理。其中动态代理又可以分为JDK动态代理和CGLIB代理。
动态代理
JDK动态代理
JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandle来处理的。它只能对实现了接口的类进行代理。主要代码实现如下:
1 | public class JDKProxy implements InvocationHandler { |
Proxy类的newProxyInstance()方法大致如下:
1 | public static Object newProxyInstance(ClassLoader loader, |
getProxyClass0()方法很长,挑些重点大致如下:
1 | private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) { |
ProxyGenerator是sun.misc包中的类,generateProxyClass()方法如下:
1 | public static byte[] generateProxyClass(final String var0, Class[] var1) { |
saveGeneratedFiles如下:
1 | private static final boolean saveGeneratedFiles = ((Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue(); |
GetBooleanAction实际上是调用Boolean.getBoolean(propName)来获得的,而Boolean.getBoolean(propName)调用了System.getProperty(name),所以我们可以设置sun.misc.ProxyGenerator.saveGeneratedFiles这个系统属性为true来把生成的class保存到本地文件来查看。
这里要注意,当把这个属性设置为true时,生成的class文件及其所在的路径都需要提前创建,否则会抛出FileNotFoundException异常。即我们要在运行当前main方法的路径下创建com/sun/proxy目录,并创建一个\$Proxy0.class文件,才能够正常运行并保存class文件内容。
反编译$Proxy0.class文件,如下所示: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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83package com.sun.proxy;
import com.mikan.proxy.HelloWorld;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements HelloWorld {
private static Method m1;
private static Method m3;
private static Method m0;
private static Method m2;
public $Proxy0(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject) {
try {
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError) {
throw localError;
}
catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void sayHello(String paramString) {
try {
this.h.invoke(this, m3, new Object[] { paramString });
return;
}
catch (Error|RuntimeException localError) {
throw localError;
}
catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode() {
try {
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError) {
throw localError;
}
catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError) {
throw localError;
}
catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("com.mikan.proxy.HelloWorld").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException) {
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException) {
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
可以看到动态生成的代理类有如下特点:
- 继承Proxy,实现代理的接口,由于java是单继承,所以JDK动态代理不支持对实现类的代理,只支持对接口的代理
- 提供一个InvocationHandle作为构造器参数
- 用静态代码块来初始化接口中方法的Method对象,以及Object中equals、hashCode、toString的Method对象
- 重写了Object类的equals、hashCode、toString,它们都只是简单的调用了InvocationHandler的invoke方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法。
- 代理类实现代理接口的sayHello方法中,只是简单的调用了InvocationHandler的invoke方法,我们可以在invoke方法中进行一些特殊操作,甚至不调用实现的方法,直接返回。
CGLIB代理
CGLIB代理是利用asm开源包,通过修改指定的类加载后的字节码生成一个子类实现的。CGLIB是针对类实现代理(无需该类实现接口),主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法不要声明成final的。主要代码实现如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class CGLibProxy implements MethodInterceptor {
private Object targetObject;// CGLib需要代理的目标对象
public Object createProxyObject(Object obj) {
this.targetObject = obj;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());
enhancer.setCallback(this);
Object proxyObj = enhancer.create();
return proxyObj;// 返回代理对象
}
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
Object obj = null;
if ("addUser".equals(method.getName())) {// 过滤方法
checkPopedom();// 检查权限
}
obj = method.invoke(targetObject, args);
return obj;
}
private void checkPopedom() {
System.out.println(".:检查权限 checkPopedom()!");
}
}
动态代理的应用
AOP的实现方式就是通过对目标对象的代理在连接点前加入通知,完成统一的切面操作。Spring提供JDKProxy和CGLIB两种方式来生成代理对象,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。
默认方式是如果目标类实现了接口,则使用JDK动态代理,如果目标类没有实现接口,则采用CGLIB代理。 如果目标对象实现了接口,可以强制使用CGLIB实现代理(添加CGLIB库,并在spring配置中加入<aop:aspectj-autoproxy proxy-target-class=”true”/>)。
代理模式应用
- 远程代理:也就是为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实。
- 虚拟代理:是根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象。这样就可以达到性能的最优化,比如说你打开一个很大的HTML网页时,里面可能有很多的文字和图片,但是你还是能很快的打开它,此时你看到的是所有的文字,但图片确实一张一张加载后才能看到。那些未打开的图片框,就是通过虚拟代理来代替真实的图片,此时代理存储了真实图片的路径和尺寸。
- 安全代理:用来控制真实对象访问时的权限。一般用于对象应该有不同的访问权限的时候。
- 智能指引:是指当调用真实对象的时候,代理处理另外一些事。