设计模式之代理模式
1400 Words|Read in about 7 Min|本文总阅读量次
比如你在澡堂洗完澡需要搓澡服务,你会联系大堂经理给你安排比较有名搓澡技师,这个时候大堂经理就会在预备的技师里面选一个给你服务。这种模式就是代理模式。
什么是代理模式
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。它是一种对象结构型模式。
主要解决的是问题是为某些资源的访问、对象的类的易用操作上提供方便使用的代理服务。
UML图
代理模式一般会有三个角色
**抽象角色:**指代代理角色和真实角色对外提供的公共方法,一般为一个接口
**真实角色:**需要实现抽象角色接口,定义了真实角色所要实现的业务逻辑,一遍提供代理角色调用。
**代理角色:**需要实现抽象角色接口,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并附加自己的操作。将统一的流程控制都放到代理角色中处理。
作为一篇开篇的设计模式,这里有必要讲述一下箭头关系
总共有六种关系,具体可详见这篇文章
泛化(继承):一种继承关系,箭头指向父类
实现:一种接口和实现关系,箭头指向接口
关联:一种拥有关系,指向被拥有者,平级关系
聚合:一种整体与部分关系,指向整体,上下级关系
组合:一种整体与部分关系,指向整体,部分必须存在整体才存在
依赖:一种使用的关系,指向被使用者,如静态方法调用
静态代理
举一个简单的demo,打官司。一般情况下打官司常见的人员原告人,被告人,原告人律师,被告人律师,大法官,陪审人员等。在法庭上我们需要一个拥有法律经验比较丰厚的律师,来替我们打官司,而律师接收原告人的委托就成为了原告人律师,主要是为了替原告打赢官司。
拆解角色
抽象角色:法律诉讼抽象类
1//法律诉讼抽象类
2public interface Ilawsuit
3{
4 void describ();//案情描述,开始举证
5}
代理角色:律师事务所的律师
1//律师事务所的律师
2public class Lawyer implements Ilawsuit{
3 Ilawsuit mIlawsuit = null;
4
5 //实现外部注入
6 public Lawyer(Ilawsuit ilawsuit){
7 mIlawsuit = ilawsuit;
8 }
9
10 @Override
11 public void describ() {
12 System.out.println("Instead of client description");
13 mIlawsuit.describ();
14 }
15}
真实角色:真实诉讼人(原告人)
1//真实诉讼人(原告人)
2public class Plaintiff implements Ilawsuit{
3 @Override
4 public void describ() {
5 System.out.println("Plaintiff describ case, the company boss Wage arrears");
6 }
7}
调用客户端
1public class StaticClient {
2 public static void main(String[] args){
3 //原告人的身份是一名打工人
4 Ilawsuit worker = new Plaintiff();
5 //打工人委托给有经验的律师进行打官司
6 Ilawsuit lawyer = new Lawyer(worker);
7 //律师大法庭上描述案件,开始举证
8 lawyer.describ();
9 }
10}
动态代理
这个时候存在一种情况,这个案件其实需要打三场官司,每一场官司都需要一名律师,如果能做到我打三场官司只用一名律师,我就相当于可以省很多钱。每场官司对应的主体内容是不一样的,相当于这个律师需要对这些主题内容都了解,能够帮助完成代理。
这里边的主题内容就是抽象接口里面的方法,也就是说这个代理对象能够完成不同抽象接口的方法。
第一种情况
只需要打一场官司,对应一个诉讼
抽象角色:法律诉讼抽象类,同上
真实角色:真实诉讼人(原告人),同上
代理角色:动态代理的角色,这里
1//newProxyInstance这里面有三个参数
2//loader: 用哪个类加载器去加载代理对象,一般可用客户端调用的类
3//interfaces:动态代理类需要实现的接口,这个相当于上面提到的代理者的能力,拥有不同抽象接口的方法
4//h:动态代理方法在执行时,会调用h里面的invoke方法去执行,一般需要外部实现或者通过匿名内部类实现
5Ilawsuit proxy = (Ilawsuit) Proxy.newProxyInstance(DynamicClinet.class.getClassLoader(),
6 new Class[]{Ilawsuit.class}, new DynamicHandler(worker));
客户端调用
1//原告人的身份是一名打工人
2Ilawsuit worker = new Plaintiff();
3//这个new Class[]{Ilawsuit.class}里面的还可以添加,主要根据真实对象有多少个实现
4// 这里实现了BaseIlawsuit就只添加Ilawsuit.class即可
5Ilawsuit proxy = (Ilawsuit) Proxy.newProxyInstance(DynamicClinet.class.getClassLoader(),
6 new Class[]{Ilawsuit.class}, new DynamicHandler(worker));
7proxy.describ();
8
9public class DynamicHandler implements InvocationHandler
10{
11 private Object obj;
12
13 public DynamicHandler(final Object obj)
14 {
15 this.obj = obj;
16 }
17
18 @Override
19 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
20 System.out.println("->>>before function");
21 //这里面的proxy是上面Ilawsuit proxy,这是一一对应的
22 //obj是构造时候的注入,一般指的是具体对象(被代理的对象)
23 // args为基类函数中的参数,method为实际的方法,意思就是调用obj真实对象的method方法,且参数为args
24 Object res = method.invoke(obj, args);
25 System.out.println("->>>after function");
26 return res;
27 }
28}
第二种情况
打三场官司,对应三种不同的诉讼,使用一种代理,可以省很多钱
抽象角色:这里有三场官司,有三个法律诉讼抽象类
1//三种不同类型的官司诉讼
2//法律诉讼抽象类
3public interface Ilawsuit
4{
5 void describ();//案情描述,开始举证
6}
7//法律诉讼抽象类
8public interface Ilawsuit2 {
9 //罗列证物
10 void Proof(int exhibit);
11}
12
13//法律诉讼抽象类
14public interface Ilawsuit3 {
15 //录音证据
16 int recording();
17}
真实角色:真实复合诉讼人(原告人),这个原告人实现了三个抽象接口方法
1//复合原告人,表示需要大三场不同类型官司的原告人
2public class PlaintiffComplex implements Ilawsuit,Ilawsuit2,Ilawsuit3{
3
4 @Override
5 public void describ() {
6 System.out.println("PlaintiffComplex describ case, the company boss Wage arrears");
7 }
8
9 @Override
10 public void Proof(int exhibit) {
11 System.out.println("PlaintiffComplex Proof case, the company boss not be human, list exhibit = "+exhibit);
12 }
13
14 @Override
15 public int recording() {
16 System.out.println("PlaintiffComplex recording case, the company boss just Draw flatbread");
17 return 0;
18 }
19}
代理角色:动态代理的角色,这里
1//相当于我有好几个诉讼抽象类
2// 诉讼抽象类A,主要案情描述,开始举证
3// 法律诉讼抽象类B,主要是罗列证物
4// 法律诉讼抽象类,主要是录音证据
5// 最终通过一个原告反馈在一场官司里面,不然需要拆开分成三个律师代理三场官司,太浪费钱了
6final PlaintiffComplex workerA = new PlaintiffComplex();
7//通过一个万能类来赋值,可对其任意强制转化类型
8Object obj = null;
9
10obj = Proxy.newProxyInstance(DynamicClinet.class.getClassLoader(),
11 new Class[]{Ilawsuit.class, Ilawsuit2.class, Ilawsuit3.class},
12 new InvocationHandler(){
13 @Override
14 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
15 return method.invoke(workerA, args);
16 }
17 });
18//这里通过对obj对象的强制转换类型完成对三类对象的调用
19Ilawsuit ilawsuit = (Ilawsuit) obj;
20ilawsuit.describ();
21Ilawsuit2 ilawsuit2 = (Ilawsuit2) obj;
22ilawsuit2.Proof(100);
23Ilawsuit3 ilawsuit3 = (Ilawsuit3) obj;
24int ret = ilawsuit3.recording();
25System.out.println("DynamicClinet workerA recording ret ="+ret);
思考:为什么能够实现动态代理,动态代理的原理是什么?
动态代理的原理
一般的对象是由硬盘中加载,通过jdk中javac工具,将.java文件-编译生成.class文件,通过类加载得到Class对象。而动态代理的对象是内存来的 ,没有真实的class文件。
笔者这里用的是jdk17的源码来分析。
1.Proxy.newProxyInstance
1//proxy.java#newProxyInstance
2public static Object newProxyInstance(ClassLoader loader,
3 Class<?>[] interfaces,
4 InvocationHandler h)
5 throws IllegalArgumentException{
6 // 1.null检查,h为null就抛出NullPointerException
7 Objects.requireNonNull(h);
8 // 2.将接口类对象数组clone一份。
9 final Class<?>[] intfs = interfaces.clone();
10
11 //执行权限检查
12 final SecurityManager sm = System.getSecurityManager();
13 if (sm != null) {
14 checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
15 }
16 // 3.查找或者是生成一个特定的代理类对象
17 Class<?> cl = getProxyClass0(loader, intfs);
18
19 try {
20 if (sm != null) {
21 checkNewProxyPermission(Reflection.getCallerClass(), cl);
22 }
23 // 是static final 修饰的,源码: private static final Class<?>[] constructorParams ={ InvocationHandler.class };
24 // 4.从代理类对象中查找参数为InvocationHandler的构造器
25 final Constructor<?> cons = cl.getConstructor(constructorParams);
26 final InvocationHandler ih = h;
27 // 检测构造器是否是Public修饰,如果不是则强行转换为可以访问的。
28 if (!Modifier.isPublic(cl.getModifiers())) {
29 AccessController.doPrivileged(new PrivilegedAction<Void>() {
30 public Void run() {
31 cons.setAccessible(true);
32 return null;
33 }
34 });
35 }
36 // 5.通过反射,将h作为参数,实例化代理类,返回代理类实例。
37 return cons.newInstance(new Object[]{h});
38 } catch ...
39}
从源码分析,主要是分为五个步骤
- null检查,这个不重要
- 接口类对象数组clone,将对象数组clone,这里是一个浅拷贝,具体clone用法详见设计模式的原型模式。
- 生成一个特定的代理类对象,如果已经存在则返回存在,这个是最重要的一个方法
- 从上述的代理类中找InvocationHandler调用构造器
- 通过反射,将调用构造器对象作为参数,实例化代理类
2.继续分析第三步的生成特定的代理类对象
1//Proxy.java#Proxy.getProxyClass0
2private static Class<?> getProxyClass0(ClassLoader loader,
3 Class<?>... interfaces) {
4 // 接口类对象数组不能大于65535个,否则抛出异常
5 if (interfaces.length > 65535) {
6 throw new IllegalArgumentException("interface limit exceeded");
7 }
8 // 从代理类对象缓存中,根据类加载器和接口类对象数组查找代理类对象,
9 // If the proxy class defined by the given loader implementing
10 // the given interfaces exists, this will simply return the cached copy;
11 // otherwise, it will create the proxy class via the ProxyClassFactory
12 return proxyClassCache.get(loader, interfaces);
13}
从上述源码的注释可以看到,如果能找到缓存中找到则返回缓存内容,如果找不到则通过ProxyClassFactory创建
3.继续获取代理类对象
1//WeakCache.java#get
2private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
3private final BiFunction<K, P, ?> subKeyFactory;
4private final BiFunction<K, P, V> valueFactory;
5
6//构造方法
7public WeakCache(BiFunction<K, P, ?> subKeyFactory,
8 BiFunction<K, P, V> valueFactory) {
9 this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
10 this.valueFactory = Objects.requireNonNull(valueFactory);
11}
12
13// key是类加载器,parameter为接口类对象数组
14public V get(K key, P parameter) {
15 ...
16 // 生成缓存key对象实例,如果key = null,cacheKey = new Object();
17 Object cacheKey = CacheKey.valueOf(key, refQueue);
18 // 从缓存map中读取指定cacheKey的缓存数据valuesMap
19 ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
20 ...
21 // create subKey and retrieve the possible Supplier<V> stored by that
22 // subKey from valuesMap
23 // 获取subKey,这里用到了上面提到的Proxy的静态内部类KeyFactory:subKeyFactory.apply(ket,parameter)
24 Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
25 // 从valuesMap中获取supplier
26 Supplier<V> supplier = valuesMap.get(subKey);
27 Factory factory = null;
28
29 while (true) {
30 if (supplier != null) {
31 // 4,从工厂中获取代理类对象
32 V value = supplier.get();
33 if (value != null) {
34 return value;
35 }
36 }
37
38 if (factory == null) {
39 //1,实例化工厂
40 factory = new Factory(key, parameter, subKey, valuesMap);
41 }
42
43 if (supplier == null) {
44 //2,保存到valuesMap中
45 supplier = valuesMap.putIfAbsent(subKey, factory);
46 if (supplier == null) {
47 // successfully installed Factory
48 // 3,赋值
49 supplier = factory;
50 }
51 // else retry with winning supplier
52 } ...
53 }
54}
可以看到这个weekcache这个类内容比较多,主要内容集中在while循环里面。
- 工厂对象不存在则实例化工厂
- 将subkey和实例化的工厂参数保存到valuemap并实例化供给类,在供给类中可获取代理类
- 没有实例化成功,则直接将工厂实例赋值给供给实例
- 再次循环可从供给类中获取代理类对象,并返回
4.着重来看上述第四点从供给类中获取代理类对象
1//WeakCache.java#Class Factory#get
2//Factory类是WeakCache的内部类,实现了Supplier<V>
3public synchronized V get() { // serialize access
4 Supplier<V> supplier = valuesMap.get(subKey);
5 if (supplier != this) {
6 //CacheValue可能被移除,直接返回上一级的下一个循环
7 return null;
8 }
9 V value = null;
10 try {
11 // valueFactory就是WeakCache的valueFactory属性,
12 //因为Factory是WeakCache的内部类,所以可以直接访问WeakCache的valueFactory属性
13 value = Objects.requireNonNull(valueFactory.apply(key, parameter));
14 } finally {
15 if (value == null) { // remove us on failure
16 valuesMap.remove(subKey, this);
17 }
18 }
19 ...
20 // wrapped by it
21 return value;
22}
23
这里最重要的是valueFactory.apply(key, parameter),其中valueFactory就是Proxy的静态内部类ProxyClassFactory,那么其实就是执行ProxyClassFactory.apply.
5.ProxyClassFactory.apply.
1//Proxy.java#Class ProxyClassFactory#apply
2//ProxyClassFactory是Proxy的内部类,实现了BiFunction<ClassLoader, Class<?>[], Class<?>>
3private static final String proxyClassNamePrefix = "$Proxy";
4// 用于生成唯一代理类名称的下一个数字
5private static final AtomicLong nextUniqueNumber = new AtomicLong();
6
7public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
8 Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
9 for (Class<?> intf : interfaces) {
10 Class<?> interfaceClass = null;
11 try {
12 // 1.加载接口类,获得接口类的类对象,第二个参数为false表示不进行实例化
13 interfaceClass = Class.forName(intf.getName(), false, loader);
14 } catch (ClassNotFoundException e) {
15 }
16 ...
17 }
18 // 代理类的包名
19 String proxyPkg = null;
20 int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
21
22 //2.验证所有非公共代理接口都在同一个包中
23 for (Class<?> intf : interfaces) {
24 int flags = intf.getModifiers();
25 if (!Modifier.isPublic(flags)) {
26 accessFlags = Modifier.FINAL;
27 String name = intf.getName();
28 int n = name.lastIndexOf('.');
29 String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
30 if (proxyPkg == null) {
31 proxyPkg = pkg;
32 } else if ...
33 }
34 }
35 //3.生成代理类的类名
36 long num = nextUniqueNumber.getAndIncrement();
37 String proxyName = proxyPkg + proxyClassNamePrefix + num;
38 //4.生成代理类class文件
39 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
40 try {
41 // 返回代理类对象
42 return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
43 } catch...
44}
这个内部类ProxyClassFactory看起来非常复杂,实际上只做了四件事
- 加载接口类,获得接口类的类对象,第二个参数为false表示不进行实例化
- 验证所有非公共代理接口都在同一个包中
- 生成代理类的类名
- 生成代理类class文件,并返回代理类对象
这里面最重要的就是第四点生成代理类class文件,是通过ProxyGenerator.generateProxyClass生成的。下面就是ndk的内容了,笔者不展开研究了,有兴趣的宝宝们可以在深入看ndk。打不开ProxyGenerator的宝宝们,可以直接点击链接访问ProxyGenerator 源码链接
6.参考骑小猪看流星反编译生成一个com.example.lib.$Proxy0.class
这边笔者生成不了,就直接参考了
1//继承了Proxy类,实现了Ilawsuit接口,包名是com.sun.proxy;
2public final class $Proxy0 extends Proxy implements Ilawsuit{
3 private static Method m1;
4 private static Method m3;
5 private static Method m2;
6 private static Method m0;
7
8 //构造方法,直接调用了父类,也就是Proxy的构造方法
9 public $Proxy0(InvocationHandler paramInvocationHandler)throws {
10 super(paramInvocationHandler);
11 }
12
13 //实现了describ
14 public final void describ()
15 throws {
16 try
17 {
18 // 这里的h就是我们的MyStorInvocationHandler实例化对象handler,原因在下方解释。
19 // 这里直接调用了DynamicHandler的invoke方法
20 this.h.invoke(this, m3, (Object[])null);
21 return;
22 }
23 ...
24 }
25 //静态代码块,做初始化操作
26 static
27 {
28 try
29 {
30 m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
31 //通过反射,获取describ方法对象实例,这里面是基类
32 m3 = Class.forName("com.example.lib.Ilawsuit").getMethod("describ");
33 m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
34 m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
35 return;
36 }
37 ...
38}
代理类实例化的代码是:cons.newInstance(new Object[]{h})。这里是通过反射调用代理类对象的构造方法,传入了参数h
1//Proxy.java#Proxy构造方法
2protected InvocationHandler h;
3protected Proxy(InvocationHandler h) {
4 Objects.requireNonNull(h);
5 this.h = h;
6}
通过父类Proxy的构造函数,可以知道传入了参数h就是开始在newProxyInstance的第三个参数new DynamicHandler(worker);
总结
代理模式的优缺点
特点 | 具体内容 |
---|---|
优点 | 业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。 |
能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。 | |
虚拟代理通过使用一个小对象来代表一个大对象,可以减少系 统资源的消耗,对系统进行优化并提高运行速度。 | |
保护代理可以控制对真实对象的使用权限。 | |
远程代理使得客户端可以访问在远程机器上的对象,远程机器 可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。 | |
缺点 | 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,例如保护代理。 |
实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂,例如远程代理。 |
关于代理模式和中介者模式的区别
代理模式是将原类进行封装,客户端只需要与代理进行交流。代理就是原类的一个替身。简而言之就是用一个对象代表另外一个对象。强调的是个体,代理可以是多个,被代理者只能是一个。
中介者模式定义一个中介对象来封装系列对象之间的交互。中介者使各个对象不需要显示地相互引用,从而使其耦合性松散,而且可以独立地改变他们之间的交互。
参考文献
[1] 付政委. 重学Java设计模式[M]. 电子工业出版社, 2021.
[2] 亚历山大·什韦茨,Refactoring.Guru背后的单人乐队.
[3] 程杰, 大话设计模式[M], 清华大学出版社, 2007.
[4] 刘望舒, Android进阶之光[M], 电子工业出版社, 2017.
[6] 张橙子, JAVA设计模式-动态代理(Proxy)源码分析, 2018.