Android常见设计模式——代理模式(Proxy Pattern)

1. 前言

首先看下百度百科对代理模式的介绍:

代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

其实也就是中介模式或者委托模式。在日常生活中代理模式很多,比如叫同事带饭、打官司等。使用代理模式的主要意图为:为其他对象提供一种代理以控制对这个对象的访问。主要的使用场景为:当无法或不想直接访问某个对象时,可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口

Spring-AOP简介一文中曾提到过两种代理方式,即:静态代理和动态代理。

  • 静态代理:是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
  • 动态代理:是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。

这里继续围绕这两种方式进行回顾和展开。

2. 代理模式(Proxy Pattern)

2.1 静态代理模式

比如下面的案例:

interface ISubject {
    void doSomething();
}

// 真正的实现类
class RealSubject_ZhangSan implements ISubject{
    @Override
    public void doSomething() {
        System.out.println("买手机");
    }
}

class ProxySubject_Shop implements ISubject{

    private RealSubject_ZhangSan mSubject;  // 真正做这件事的对象

    public ProxySubject_Shop(RealSubject_ZhangSan subject){
        this.mSubject = subject;
    }

    @Override
    public void doSomething() {  // 只做代理
        mSubject.doSomething();
    }
}


public class Client {
    public static void main(String[] args) {
        RealSubject_ZhangSan subject = new RealSubject_ZhangSan();
        // 构造代理对象
        ProxySubject_Shop proxySubject = new ProxySubject_Shop(subject);
        // 调用代理的方法
        proxySubject.doSomething();
    }
}

对应的类图为:
在这里插入图片描述

Client通过接口知道需要调用什么方法,而因为在代理模式中不能直接访问真正的目标对象,而需要通过代理对象来进行访问。所以在Client中需要使用代理对象,由于代理模式规定了需要保证客户端使用的透明性,所以被代理对象和代理对象都需要事项相同的接口,即这里的doSomething方法。

值得注意的是,按照上面的写法对于每个具体的代理类,我们都需要在其中指定维护(代理)的具体Subject类,那么当有很多具体的RealSubject的时候,就需要定义很多的代理类,当然这是不合理的。所以在代理类中只有的应该是接口,比如:

class ProxySubject_Shop implements ISubject{

    private ISubject mSubject;  // 真正做这件事的对象,面向接口编程而不要面向具体的实现

    public ProxySubject_Shop(ISubject subject){
        this.mSubject = subject;
    }

    @Override
    public void doSomething() {  // 只做代理
        mSubject.doSomething();
    }
}

虽然上面这种方式确实解决了存在多个真实主题类的情况,但是如果代理类需要在原有的真实Subject前或者后做一些自己独有的操作的时候,就需要根据代理类的种类,来定义多个代理类。这显然也是不合理的。所以就需要动态代理。

从前面我们知道使用静态代理其实也就是在源码中直接指定。而动态代理方式相反,是通过反射的机制来动态地生成代理者的对象,也就是说代理谁将会在执行阶段决定。

2.2 动态代理模式

动态代理中所使用的技术也就是反射,在Java中提供了一个便捷的动态代理接口InvocationHandler以及相关的代理类Proxy

  • InvocationHandler:调用处理程序,并返回一个结果的;
  • Proxy:提供了创建动态代理类和实例的静态方法,用于生成动态代理的这个实例;
interface ISubject {
    void doSomething();
}

// 真正的实现类
class RealSubject_ZhangSan implements ISubject{
    @Override
    public void doSomething() {
        System.out.println("买手机");
    }
}

public class Client {
    public static void main(String[] args) {
        RealSubject_ZhangSan realSubject = new RealSubject_ZhangSan();

        // 动态构造一个代理者 ==> 代理realSubject
        ISubject subject = (ISubject) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(),
                new Class[]{ISubject.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        return method.invoke(realSubject, args);
                    }
                });
        // 得到类RealSubject_ZhangSan的实现接口Class对象
        // new Class[]{ISubject.class} 或者:RealSubject_ZhangSan.class.getInterfaces()  

        // 调用代理的方法
        subject.doSomething();
    }
}

当然这里可以抽离出来一个动态代理类:

class DynamicProxy<T extends ISubject> implements InvocationHandler {

    private T mObj;

    public DynamicProxy(T obj){
        this.mObj = obj;
    }

    public ISubject getProxy(){
         return (ISubject) Proxy.newProxyInstance(mObj.getClass().getClassLoader(),
                 mObj.getClass().getInterfaces(), this);
    }

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


public class Client {
    public static void main(String[] args) {
        RealSubject_ZhangSan realSubject = new RealSubject_ZhangSan();
        // 动态代理
        ISubject subject = new DynamicProxy<RealSubject_ZhangSan>(realSubject).getProxy();
        // 调用代理的方法
        subject.doSomething();
    }
}

对应的类图:
在这里插入图片描述

观察上面类图,可以直观的看到就是动态代理类和具体的实现类就没有直接关联关系了。而需要做的是在Client代理类中进行动态确定。可以看到就是这种方式的代码耦合度更低。也就是完成了代理者和被代理者之间的解耦,使得这两者之间没有直接的耦合关系。当然,静态代理类的方式更加符合面向对象原则,开发时具体使用哪种代理方式,就没有什么规定了。

3. Android 中的代理模式

Android中有ActivityManagerProxyActivityManagerService以及Retrofit等均有代理模式的身影。这里以Retrofit中的代理模式为例。

3.1 Retrofit中的代理模式(没有被代理者)

为了了解Retrofit中的代理模式,首先需要先引入相关依赖到项目中:

// https://mvnrepository.com/artifact/com.squareup.retrofit2/retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'

对于怎么使用在Retrofit的使用案例中曾简单的使用过这个框架。这里做一个简单的回顾:

  • 定义指定具体的URL地址,来构造Retrofit对象;
  • 使用第一步得到的Retrofit实例对象调用create方法,当然这个方法中传入一个自定义的接口。在这个接口中完成链接返回数据转换为Java实例对象。
  • 类似的将请求加入到请求队列中,然后接收返回结果即可。

而在Retrofit类中的create方法中就可以看见动态代理模式:

public <T> T create(final Class<T> service) {
    validateServiceInterface(service);
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service},
            new InvocationHandler() {
                private final Platform platform = Platform.get();
                private final Object[] emptyArgs = new Object[0];

                @Override
                public @Nullable
                Object invoke(Object proxy, Method method,
                              @Nullable Object[] args) throws Throwable {
                    // If the method is a method from Object then defer to normal invocation.
                    if (method.getDeclaringClass() == Object.class) {
                        return method.invoke(this, args);
                    }
                    if (platform.isDefaultMethod(method)) {
                        return platform.invokeDefaultMethod(method, service, proxy, args);
                    }
                    return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
                }
            });
}

比如此时的请求接口还是像Retrofit的使用案例文中定义的一样,即:

public interface RequestInterface {

    @GET(value = "/test/1.0/users")
    Call<List<User>> listUsers();   // retrofit2.Call;

    @GET(value = "/test/1.0/users/{userid}")
    Call<User> getUserById(@Path(value = "userid") char userId);

    @FormUrlEncoded
    @POST(value = "/test/1.0/users")
    Call<Void> addUser(@Field(value = "name") String name);
}

那么在构造Retrofit中的请求对象的时候,就在create方法中传入这个接口的Class对象:

RequestInterface request = retrofit.create(RequestInterface.class);

继续回到Retrofit类的create方法,可以发现生成的代理对象其实就是代理这个RequestInterface接口。但是值得注意的是,RequestInterface接口只是代理模式中的Subject,而我们这里需要找到真正的被代理对象类。但是很遗憾确实这里没有找到,那么不妨来仿写一下:

interface ISubject {
    void doSomething();
}

public class Client {
    public static void main(String[] args) {
        ISubject subject = (ISubject) Proxy.newProxyInstance(ISubject.class.getClassLoader(),
                new Class[]{ISubject.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("invoke");
                        return 0;
                    }
                });

        System.out.println("package: \n\t" + subject.getClass().getName());
        System.out.println("implemented interface: ");
        Class<?>[] interfaces = subject.getClass().getInterfaces();
        for (Class<?> anInterface : interfaces) {
            System.out.println("\t"+anInterface.getName());
        }
        System.out.println("fields: ");
        Field[] declaredFields = subject.getClass().getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println("\t"+declaredField.getName());
        }
        System.out.println("methods: ");
        Method[] declaredMethods = subject.getClass().getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println("\t"+declaredMethod.getName());
        }

        subject.doSomething();
    }
}

输出结果为:
在这里插入图片描述
Retrofit2 源码解析之动态代理一文中给出了得到生成的动态代理类的字节码数组并存储为.class文件的方式,即:

private static void storageClassFile(ISubject subject){
    // subject 为上面生成的动态代理对象
    String proxyName = subject.getClass().getName() + ".class";
    byte[] clazz = ProxyGenerator.generateProxyClass(proxyName, new Class[]{ISubject.class});
    try {
        OutputStream out = new FileOutputStream(proxyName);
        InputStream is = new ByteArrayInputStream(clazz);
        byte[] buff = new byte[1024];
        int len;
        while ((len = is.read(buff)) != -1) {
            out.write(buff, 0, len);
        }
        is.close();
        out.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

那么在这个项目的跟目录中可以看到生成的文件:

在这里插入图片描述
直接使用IDEA来进行打开:

public final class class extends Proxy implements ISubject {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public class(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 void doSomething() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    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 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"));
            m3 = Class.forName("com.example.scan.kaoshi.ISubject").getMethod("doSomething");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

可以看到这个类确实是实现类ISubject接口的类,且实现了基本的hashCodetoStringequals方法。但是因为在ProxyGenerator.generateProxyClass方法存在于sun.misc.ProxyGenerator包中,而Android中并不支持这个包所以有必要继续看Retrofit中的源码:

在这里插入图片描述
可以看到其实首先通过loadServiceMethod(method)这个方法将调用的实际方法传递进去,然后在loadServiceMethod方法中进行Map<Method, ServiceMethod<?>>查找看是否已经存在这个方法,存在就直接返回,否则就通过ServiceMethod.parseAnnotations(this, method)方法来对声明接口中的这个方法的注解进行解析,在这个方法中会继续解析注解,最终会使用HttpServiceMethod.parseAnnotations方法来进行解析,由于代码比较多这里就不贴出来了。在这个方法中,首先得到这个方法的返回类型,然后使用Okhttp3来进行请求的封装,并且得到这个方法的所有注解,并进行对应的解析。最终会返回一个HttpServiceMethod对象到Retrofit.javacreate方法。我们之后后续的使用和Okhttp类似,也就是构建Call对象以及请求加入到队列中。

所以在Retrofit中不完全是代理模式,很容易理解,因为在这个过程中确实没有RealSubject对象,而只存在接口和动态代理Proxy的对象。也比较好理解,因为在网络请求过程中,我们定义RESTful风格来进行请求链接和返回参数的定义,中间其实不需要一个真实的被代理类来实现这个请求,而是需要将其注解进行解析,得到实际有意义的请求参数和返回值类型,最终可以动态添加到Okhttp请求和满足动态封装Bean即可。

在这个过程中由程序自己来处理注解,以及完成请求API的解析工作,进而可以在底层使用Okhttp进行网络请求的时候可以对参数以及返回值进行相关的数据封装。所以说其实在Retrofit中不完全是代理模式,因为没有(也不需要)实际上的被代理者。

4. 后记

在《Android源码设计模式》一书中提到:不少设计模式中都存在代理模式的影子,且在日常生活中无处不在。作者花了很大的篇幅来介绍这个模式,且在Binder中也存在代理模式的影子,之后需要再仔细读读。


References

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦否

文章对你有用?不妨打赏一毛两毛

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值