比如你在澡堂洗完澡需要搓澡服务,你会联系大堂经理给你安排比较有名搓澡技师,这个时候大堂经理就会在预备的技师里面选一个给你服务。这种模式就是代理模式。

什么是代理模式

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。它是一种对象结构型模式。

主要解决的是问题是为某些资源的访问、对象的类的易用操作上提供方便使用的代理服务。

UML图

代理模式UML图 代理模式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}

从源码分析,主要是分为五个步骤

  1. null检查,这个不重要
  2. 接口类对象数组clone,将对象数组clone,这里是一个浅拷贝,具体clone用法详见设计模式的原型模式。
  3. 生成一个特定的代理类对象,如果已经存在则返回存在,这个是最重要的一个方法
  4. 从上述的代理类中找InvocationHandler调用构造器
  5. 通过反射,将调用构造器对象作为参数,实例化代理类

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循环里面。

  1. 工厂对象不存在则实例化工厂
  2. 将subkey和实例化的工厂参数保存到valuemap并实例化供给类,在供给类中可获取代理类
  3. 没有实例化成功,则直接将工厂实例赋值给供给实例
  4. 再次循环可从供给类中获取代理类对象,并返回

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看起来非常复杂,实际上只做了四件事

  1. 加载接口类,获得接口类的类对象,第二个参数为false表示不进行实例化
  2. 验证所有非公共代理接口都在同一个包中
  3. 生成代理类的类名
  4. 生成代理类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.

[5] 冀遥, UML类图箭头关系, 2018.

[6] 张橙子, JAVA设计模式-动态代理(Proxy)源码分析, 2018.

[7] 骑小猪看流星, 设计模式之死磕代理模式(原创), 2018.

[7] docjar