一、代理模式定义
给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。
通俗的来讲代理模式就是我们生活中常见的中介。
二、 代理模式的结构
代理模式的主要角色如下:
- 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
其通用类图如下:
三、代理模式优缺点
3.1 代理模式的主要优点有:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
3.1 其主要缺点是:
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度;
四、静态代理和动态代理
我们有多种不同的方式来实现代理。如果按照代理创建的时期来进行分类的话, 可以分为两种:静态代理、动态代理。
静态代理是由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class文件就已经被创建了。
动态代理是在程序运行时通过反射机制动态创建的。现在有一个非常流行的名称叫做:面向横切面编程,也就是AOP(Aspect Oriented Programming),其核心就是采用了动态代理机制。Spring AOP实现动态代理采用了两种方式,即JDK动态代理和CGLIB动态代理。
JDK动态代理是通过实现被代理类共同接口来完成代理,所以是基于接口的动态代理;CGLIB动态代理是通过继承并实现被代理类来完成代理,所以是基于继承的代理
五、静态代理实现
5.1 抽象主题类:Sleep
/**
* 抽象主题角色
*/
public interface Sleep {
void sleep();
}
5.2 真实主题类:SleepImpl
/**
* 真实主题角色
*/
public class SleepImpl implements Sleep {
@Override
public void sleep() {
System.out.println("熟睡中");
}
}
5.3 代理类:SleepProxy
/**
* 代理角色
*/
public class SleepProxy implements Sleep {
private Sleep sleep;
public SleepProxy(Sleep sleep) {
this.sleep = sleep;
}
@Override
public void sleep() {
System.out.println("睡觉前要刷牙");
sleep.sleep();
System.out.println("睡醒后要吃早饭");
}
}
5.4 客户端测试类
public class Client {
public static void main(String[] args) {
Sleep sleep = new SleepImpl();
Sleep sleepProxy = new SleepProxy(sleep);
sleepProxy.sleep();
}
}
打印结果:
睡觉前要刷牙
熟睡中
睡醒后要吃早饭
六、动态代理实现(JDK)
静态代理有一个明显的缺点:一个主题类和一个代理类一一对应,所以我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。 在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK在运行时为我们动态的来创建。
6.1 抽象主题类:Sleep、真实主题类:SleepImpl不变
/**
* 抽象主题角色
*/
public interface Sleep {
void sleep();
}
/**
* 真实主题角色
*/
public class SleepImpl implements Sleep {
@Override
public void sleep() {
System.out.println("熟睡中");
}
}
6.2 新增动态处理器
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 动态处理器
*/
public class DynamicProxyHandler implements InvocationHandler {
private Object obj ;
public DynamicProxyHandler(final Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("睡觉前要刷牙");
Object result = method.invoke(obj, args);
System.out.println("睡醒后要吃早饭");
return null;
}
}
6.3 客户端测试类
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
Sleep sleep = new SleepImpl();
DynamicProxyHandler dph = new DynamicProxyHandler(sleep);
Sleep sleepProxy = (Sleep) Proxy.newProxyInstance(sleep.getClass().getClassLoader(),
sleep.getClass().getInterfaces(), dph);
sleepProxy.sleep();
}
}
打印结果:
睡觉前要刷牙
熟睡中
睡醒后要吃早饭
注意:Proxy.newProxyInstance()方法需要3个参数:类加载器(要进行代理的类)、被代理类实现的接口,动态处理器 。
JDK动态代理的一般步骤:
1、创建动态处理器实现InvocationHandler接口,实现invoke()方法
2、创建被代理的类及接口
3、调用Proxy的静态方法,创建代理类
4、通过代理类调用方法
拓展:查看JDK动态生成的代理类源码
用ProxyGenerator.generateProxyClass生成代理类,并生成.class文件写入本地磁盘,我们再通过javap -c 命令查看JDK动态生成的代理类源码
import java.io.File;
import java.io.FileOutputStream;
import sun.misc.ProxyGenerator;
public class ShowDynamicProxTest {
public static void main(String[] args) {
Sleep sleep = new SleepImpl();
byte[] bts = ProxyGenerator.generateProxyClass("$GameProxy", sleep.getClass().getInterfaces());
try {
FileOutputStream fos = new FileOutputStream(new File("/Users/AC/temp/$DynamicSleepProxy.class"));
fos.write(bts);
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
当你尝试拷贝我这段代码在你本地运行时,你会发现你无法导入import sun.misc.ProxyGenerator;你可以这样操作来解决:
1、打开项目属性》Java Build Path》选择JRE System Library》remove;
2、重新添加JRE system library:点击Add library》Next》选择相应的JDK版本(建议与刚开始的一样的版本)》finish。
3、再来尝试导入sun.misc.ProxyGenerator,发现已经可以导入了。
更多方式可参考:导入某些特殊Jar包中的类
现在我们用javap -c来查看一下我们生成在本地的$DynamicSleepProxy.class源码(当然也可以用其他方式来查看源码,如安装插件)
AC:temp AC$ javap -c \$DynamicSleepProxy.class
public final class $SleepProxy extends java.lang.reflect.Proxy implements com.Sleep {
public $SleepProxy(java.lang.reflect.InvocationHandler) throws ;
Code:
0: aload_0
1: aload_1
2: invokespecial #8 // Method java/lang/reflect/Proxy."<init>":(Ljava/lang/reflect/InvocationHandler;)V
5: return
public final boolean equals(java.lang.Object) throws ;
Code:
0: aload_0
1: getfield #16 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
4: aload_0
5: getstatic #20 // Field m1:Ljava/lang/reflect/Method;
8: iconst_1
9: anewarray #22 // class java/lang/Object
12: dup
13: iconst_0
14: aload_1
15: aastore
16: invokeinterface #28, 4 // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
21: checkcast #30 // class java/lang/Boolean
24: invokevirtual #34 // Method java/lang/Boolean.booleanValue:()Z
27: ireturn
28: athrow
29: astore_2
30: new #42 // class java/lang/reflect/UndeclaredThrowableException
33: dup
34: aload_2
35: invokespecial #45 // Method java/lang/reflect/UndeclaredThrowableException."<init>":(Ljava/lang/Throwable;)V
38: athrow
Exception table:
from to target type
0 28 28 Class java/lang/Error
0 28 28 Class java/lang/RuntimeException
0 28 29 Class java/lang/Throwable
public final java.lang.String toString() throws ;
Code:
0: aload_0
1: getfield #16 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
4: aload_0
5: getstatic #50 // Field m2:Ljava/lang/reflect/Method;
8: aconst_null
9: invokeinterface #28, 4 // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
14: checkcast #52 // class java/lang/String
17: areturn
18: athrow
19: astore_1
20: new #42 // class java/lang/reflect/UndeclaredThrowableException
23: dup
24: aload_1
25: invokespecial #45 // Method java/lang/reflect/UndeclaredThrowableException."<init>":(Ljava/lang/Throwable;)V
28: athrow
Exception table:
from to target type
0 18 18 Class java/lang/Error
0 18 18 Class java/lang/RuntimeException
0 18 19 Class java/lang/Throwable
public final void sleep() throws ;
Code:
0: aload_0
1: getfield #16 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
4: aload_0
5: getstatic #57 // Field m3:Ljava/lang/reflect/Method;
8: aconst_null
9: invokeinterface #28, 4 // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
14: pop
15: return
16: athrow
17: astore_1
18: new #42 // class java/lang/reflect/UndeclaredThrowableException
21: dup
22: aload_1
23: invokespecial #45 // Method java/lang/reflect/UndeclaredThrowableException."<init>":(Ljava/lang/Throwable;)V
26: athrow
Exception table:
from to target type
0 16 16 Class java/lang/Error
0 16 16 Class java/lang/RuntimeException
0 16 17 Class java/lang/Throwable
public final int hashCode() throws ;
Code:
0: aload_0
1: getfield #16 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
4: aload_0
5: getstatic #62 // Field m0:Ljava/lang/reflect/Method;
8: aconst_null
9: invokeinterface #28, 4 // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
14: checkcast #64 // class java/lang/Integer
17: invokevirtual #67 // Method java/lang/Integer.intValue:()I
20: ireturn
21: athrow
22: astore_1
23: new #42 // class java/lang/reflect/UndeclaredThrowableException
26: dup
27: aload_1
28: invokespecial #45 // Method java/lang/reflect/UndeclaredThrowableException."<init>":(Ljava/lang/Throwable;)V
31: athrow
Exception table:
from to target type
0 21 21 Class java/lang/Error
0 21 21 Class java/lang/RuntimeException
0 21 22 Class java/lang/Throwable
static {} throws ;
Code:
0: ldc #70 // String java.lang.Object
2: invokestatic #76 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
5: ldc #77 // String equals
7: iconst_1
8: anewarray #72 // class java/lang/Class
11: dup
12: iconst_0
13: ldc #70 // String java.lang.Object
15: invokestatic #76 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
18: aastore
19: invokevirtual #81 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
22: putstatic #20 // Field m1:Ljava/lang/reflect/Method;
25: ldc #70 // String java.lang.Object
27: invokestatic #76 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
30: ldc #82 // String toString
32: iconst_0
33: anewarray #72 // class java/lang/Class
36: invokevirtual #81 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
39: putstatic #50 // Field m2:Ljava/lang/reflect/Method;
42: ldc #84 // String com.Sleep
44: invokestatic #76 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
47: ldc #85 // String sleep
49: iconst_0
50: anewarray #72 // class java/lang/Class
53: invokevirtual #81 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
56: putstatic #57 // Field m3:Ljava/lang/reflect/Method;
59: ldc #70 // String java.lang.Object
61: invokestatic #76 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
64: ldc #86 // String hashCode
66: iconst_0
67: anewarray #72 // class java/lang/Class
70: invokevirtual #81 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
73: putstatic #62 // Field m0:Ljava/lang/reflect/Method;
76: return
77: astore_1
78: new #90 // class java/lang/NoSuchMethodError
81: dup
82: aload_1
83: invokevirtual #93 // Method java/lang/Throwable.getMessage:()Ljava/lang/String;
86: invokespecial #96 // Method java/lang/NoSuchMethodError."<init>":(Ljava/lang/String;)V
89: athrow
90: astore_1
91: new #100 // class java/lang/NoClassDefFoundError
94: dup
95: aload_1
96: invokevirtual #93 // Method java/lang/Throwable.getMessage:()Ljava/lang/String;
99: invokespecial #101 // Method java/lang/NoClassDefFoundError."<init>":(Ljava/lang/String;)V
102: athrow
Exception table:
from to target type
0 77 77 Class java/lang/NoSuchMethodException
0 77 90 Class java/lang/ClassNotFoundException
}
通过看源码我们发现,JDK动态生成的代理类SleepProxy继承了java.lang.reflect.Proxy类,同时实现了我们的Sleep类。
问题1:为什么JDK动态代理只能代理实现了接口的类?
因为JDK动态代理生成的代理类需要去继承Proxy类,而Java是单继承的,因此它只能去实现被代理类的接口(实现的接口)
问题2:为什么JDK动态代理生成的代理类要去继承Proxy类?
不仔细看的话,生成的代理类会给人一种继承Proxy类没有用的感觉,因为生成的代理类并没有用什么Proxy类的东西,很有迷惑性,我也是将生成的代理类代码亲自拿来试了一下才发现它的用处。
事实上,在代理类$Proxy中有许多处方法的调用,都是通过使用super关键字去拿父类也就是proxy类中定义的InvocationHandler实例然后完成调用的。Super关键字正是在调用父类Prioxy类的方法。这就是用到proxy类的地方。
在代理类Proxy中调用父类的方法都是使用了InvocationHandler类的实例,在代理类$Proxy中通过构造方法传入进来,而我们应该还记得,在Proxy的newProxyInstance方法中的第三个参数,正是InvocationHandler的一个实例。
因此这里调用方法时,自然就是使用这个实例去调用invoke方法,同时传入对应的方法参数。这下,所有的东西都串联起来了,问题三的答案事实上也已经有了。
重点:
JDK生成的代理类本身就继承了Proxy类,Java只允许单继承,所以JDK的动态代理不能完成继承式动态代理(只能完成接口的动态代理,不能实现类的动态代理),但是我们可以用CGLIB的方式实现继承式的动态代理
七、动态代理实现(CGLIB)
7.1 具体主题(不需要实现接口)
/**
* 具体主题
*/
public class Sleep {
public void sleep() {
System.out.println("熟睡中");
}
}
7.2 CGLibProxy
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CGLibProxy implements MethodInterceptor {
private Object obj;
//创建代理类
public Object getInstance(Object obj) {
this.obj = obj;//主题对象
Enhancer enhancer = new Enhancer();//创建加强器,用来创建动态代理类
enhancer.setSuperclass(this.obj.getClass());
//设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
enhancer.setCallback(this);
//返回代理类
return enhancer.create();
}
//回调方法实现
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
System.out.println("睡觉前要刷牙");
Object result = methodProxy.invokeSuper(obj, args);
System.out.println("睡醒后要吃早饭");
return result;
}
}
7.3 测试类
public class Client {
public static void main(String[] args) {
Sleep sleep = new Sleep();
CGLibProxy proxy = new CGLibProxy();
Sleep sleepProxy = (Sleep) proxy.getInstance(sleep);
sleepProxy.sleep();
}
}
打印结果
睡觉前要刷牙
熟睡中
睡醒后要吃早饭
注意:使用CGLIB需要下载两个jar包:cglib.jar和asm.jar
CGLib » 2.2.2 jar下载
使用CGlib出现java.lang.NoClassDefFoundError: org/objectweb/asm/Type异常
asm jar下载
八、总结
静态代理虽然能完成代理功能,但我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
JDK动态代理很美很强大,但由于Java的单继承,只能实现接口的动态代理,不能实现类的动态代理。
CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。
参考: