代理模式

简介

代理模式(Proxy Pattern)也称为委托模式,一般就是自己直接访问某个对象有一定困难,需要委托别人(代理对象)来间接访问的一种设计模式。代理模式其实在日常生活中很常见,比如你发现国外的名牌比较便宜,但是自己又不想花钱出国,于是你委托你国外的朋友帮你把名牌买回来。又或者比如你的老板不给你发工资,你得请一个律师帮你打官司。还有比如你想租房,你通过中介租房子,这也是代理。

代码实现

下面我们通过代码来实现租客(Tenant)通过中介(TenantProxy)租房的例子。

  1. 代码的最终目的是要通过中介(TenantProxy)把房子租下来,那么代码里面肯定要调用 TenantProxy 实例的 rent() 方法,代码如下:
java 复制代码
public class Test {
    public static void main(String[] args) {
        TenantProxy tenantProxy = new TenantProxy();
        tenantProxy.rent();
    }
}

public class TenantProxy {
    public void rent(){
        System.out.println("租房子");
    }
}
  1. 但是真实的情况是 Tenant 想租房,不是 TenantProxy 想租, 对 TenantProxy 中的代码进行改造后代码如下:
java 复制代码
public class TenantProxy {

    private Tenant tenant;

    public TenantProxy(Tenant tenant){
        this.tenant = tenant;
    }

    public void rent(){
        tenant.rent();
    }
}

public class Tenant {

    public void rent(){
        System.out.println("租房子");
    }
}

public class Test {
    public static void main(String[] args) {
        Tenant tenant = new Tenant();
        TenantProxy tenantProxy = new TenantProxy(tenant);
        tenantProxy.rent();
    }
}

3.又因为 Tenant 和 TenantProxy 实现的是同一个操作(租房子),将这个操作抽象成接口或者抽象类,最终代码如下:

java 复制代码
public interface ITenant {
    void rent();
}

public class TenantProxy implements ITenant{

    private ITenant tenant;

    public TenantProxy(ITenant tenant){
        this.tenant = tenant;
    }

    public void rent(){
        tenant.rent();
    }
}

public class Tenant implements ITenant{

    public void rent(){
        System.out.println("租房子");
    }
}

public class Test {
    public static void main(String[] args) {
        Tenant tenant = new Tenant();
        TenantProxy tenantProxy = new TenantProxy(tenant);
        tenantProxy.rent();
    }
}

类图如下:

classDiagram class ITenant { <> +rent() } ITenant <|.. TenantProxy ITenant <|.. Tenant class Tenant{ +rent() } class TenantProxy{ -ITenant tenant +TenantProxy(ITenant tenant) +rent() } TenantProxy --> Tenant : delegates

动态代理

如果现在租客想要通过这个中介买房,这时候就需要在 ITenant 接口中添加买房的方法了:

java 复制代码
public interface ITenant {
    
    void rent();
    
    void buy();
}

代理类也要跟着修改:

java 复制代码
public class TenantProxy implements ITenant{

    private ITenant tenant;

    public TenantProxy(ITenant tenant){
        this.tenant = tenant;
    }

    public void rent(){
        tenant.rent();
    }
  
    public void buy(){
        tenant.buy();
    }
    
}

随着业务变得越来越复杂,维护代理类和被代理类之间的关系也会变得很麻烦,这时候动态代理应运而生。使用动态代理的好处是可以通过一个代理类来代理所有的被代理类,动态代理不需要提前创建代理类,它会在代码运行时创建代理类的实例

下面我们使用动态代理让租户可以通过中介租房和买房,代码如下:

java 复制代码
public interface ITenant {

    void rent();

    void buy();
}

public class Tenant implements ITenant{

    public void rent(){
        System.out.println("动态代理租房子");
    }

    @Override
    public void buy() {
        System.out.println("动态代理买房子");
    }
}

public class Test {
    public static void main(String[] args) {
        Tenant tenant = new Tenant();
        ITenant iTenant = (ITenant) Proxy.newProxyInstance(
                ITenant.class.getClassLoader(),
                new Class<?>[] {ITenant.class},
                new InvocationHandler() {

                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        return method.invoke(tenant, args);
                    }
                });
        iTenant.rent();
        iTenant.buy();
    }
}

可以看到使用动态代理后,代理类 TenantProxy 直接不用写了,但是需要调用 java.lang.reflect.Proxy 的 newProxyInstance() 方法。

源码分析

来看看 Proxy 的 newProxyInstance() 方法做了什么(本文源码基于 Android API 33):

java 复制代码
public class Proxy implements java.io.Serializable {

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        ...
        final Class<?>[] intfs = interfaces.clone();
        ...
        // 1. 通过 loader 和 intfs 创建代理类 
        Class<?> cl = getProxyClass0(loader, intfs);
        ...
        // 2. 获取代理类的构造函数
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        ...
        // 3. 创建代理类的实例返回
        return cons.newInstance(new Object[]{h});
        ...
}    

可以看到这里主要目的就是创建代理类的实例并返回,这样就可以调用代理类的实例的 rent() 了。

getProxyClass0() 方法的代码如下:

java 复制代码
private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    ...
    // If the proxy class defined by the given loader implementing
    // the given interfaces exists, this will simply return the cached copy;
    // otherwise, it will create the proxy class via the ProxyClassFactory 
    return proxyClassCache.get(loader, interfaces);
}

从这里的注释可以看到:如果实现该接口的代理类已经通过 loader 加载过,这个方法会直接返回缓存的副本;否则,将通过 ProxyClassFactory 来创建代理类。

proxyClassCache 是一个定义在 Proxy 中的数据结构:

java 复制代码
public class Proxy implements java.io.Serializable {

    // 用来缓存代理类 
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
        
}        

proxyClassCache 是 WeakCache<ClassLoader, Class<?>[], Class<?>> 类型的,其 get() 方法如下:

java 复制代码
final class WeakCache<K, P, V> {
    
    public V get(K key, P parameter) {
        ...
        // 这里 subKeyFactory 即 new ProxyClassFactory(),
        // subKeyFactory.apply() 会调用 ProxyClassFactory 实例的 apply() 方法
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        ...
    }
}

这里的 get() 方法中会调用 ProxyClassFactory 的 apply() 方法:

java 复制代码
// 工厂类:通过 ClassLoader 和 接口组成的数组 来创建代理类的
private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{

    // 生成的代理 Class 的前缀
    private static final String proxyClassNamePrefix = "$Proxy";

    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
            ...
            // 代理 Class 名由 proxyPkg + proxyClassNamePrefix + num 拼接而成
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            return generateProxy(proxyName, interfaces, loader, methodsArray,
                                 exceptionsArray);
        }
    }
}

generateProxy() 是一个 native 方法

java 复制代码
private static native Class<?> generateProxy(String name, Class<?>[] interfaces,
                                             ClassLoader loader, Method[] methods,
                                             Class<?>[][] exceptions);

在 Android 中最终调用了 native 方法来生成代理类,由于动态代理是在运行时生成的,生成的代理类仅存在于内存中,并不会生成 .class 文件,因此你无法在 Android Studio 中找到。

在静态代理中,调用代理类的 rent() 方法会调用被代理类的 rent() 方法,在动态代理中,我们看到通过 Proxy 的 newProxyInstance() 方法创建了代理类的实例,但是里面是怎么把代理类的 rent() 方法传递给被代理类的 rent() 方法的呢?还是得通过生成的代理类的的代码来分析才行。

Java 中有生成代理类的 .class 文件的方法,调用 ProxyGenerator 的 generateProxyClass() 方法即可,但是最近发现这个方法在 Java 中不开放了,使用 ProxyGenerator 会直接报错说找不到,可能是因为安全问题不让用了。

以前这个方法是可行的。在项目中新建一个 module ,类型选择 java library ,命名为 lib , 代码如下:

java 复制代码
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import sun.misc.ProxyGenerator;

public class Test2 {

    public static void main(String[] args){
        String name = ITenant.class.getName() + "$Proxy0";
        //生成代理指定接口的Class数据
        byte[] bytes = ProxyGenerator.generateProxyClass(name, new Class[]{ITenant.class});
        FileOutputStream fos;

        try {
            fos = new FileOutputStream("lib/" + name + ".class");
            fos.write(bytes);
            fos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行后会在当前项目的 lib 模块下生成 .class 文件:ITenant$Proxy0.class,代码如下:

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class ITenant$Proxy0 extends Proxy implements ITenant {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public ITenant$Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void rent() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.example.lib.ITenant").getMethod("rent");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

可以看到生成的代理类会继承 java.lang.reflect.Proxy 并实现 ITenant 接口,里面也会有一个 rent() 方法,这样当你调用代理类的 rent() 方法时,就会调用 super.h 的 invoke() 方法, 传入的参数 m3 是: Class.forName("com.example.lib.ITenant").getMethod("rent"),super.h 就是 InvocationHandler 接口,也就是调用 InvocationHandler 的 invoke() 方法,也就会调用 tenant 的 rent() 方法。

所以其实动态代理跟静态代理做的事情是一样的,最大的区别是动态代理会在运行时动态创建代理类的实例,一个动态代理类还可以代理多个被代理类,更加灵活。

相关推荐
weixin_478433322 小时前
iluwatar 设计模式
java·开发语言·设计模式
郝学胜-神的一滴4 小时前
Python面向对象编程:解耦、多态与魔法艺术
java·开发语言·c++·python·设计模式·软件工程
__万波__4 小时前
二十三种设计模式(十六)--迭代器模式
java·设计模式·迭代器模式
范纹杉想快点毕业16 小时前
返璞归真还是拥抱现代?——嵌入式研发中的“裸机开发”与RTOS全景解析
c语言·数据库·mongodb·设计模式·nosql
代码笔耕1 天前
面向对象开发实践之消息中心设计(四)--- 面向变化的定力
java·设计模式·架构
程序员泠零澪回家种桔子1 天前
ReAct Agent 后端架构解析
后端·spring·设计模式·架构
阿闽ooo1 天前
深入浅出享元模式:从图形编辑器看对象复用的艺术
c++·设计模式·编辑器·享元模式
阿闽ooo1 天前
组合模式(Composite Pattern)深度解析:从原理到企业级实践
c++·笔记·设计模式·组合模式
山风wind1 天前
设计模式-策略模式详解
设计模式·策略模式