代理模式以及在Android中的使用

作者: EvanZch | 来源:发表于2019-12-10 17:13 被阅读0次

    本文涉及java中的一些反射知识,如果你对反射这部分不太熟悉,建议先去了解一下反射知识,或者看一下我的这篇文章 Java反射以及在Android中的使用

    代理模式

    一、定义

    某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。

    举例说明:

    代理模式从字面上就是我们理解的生活中那个中介代理,比如公司A(原对象)为海外公司,消费者B(某一个对象)直接从公司A购买商品需要各种各样复杂的步骤,这时候就出现了代理人C(代理对象),让他来替我们去处理那些复杂的步骤,我们只需要告诉代理人C我们需要什么商品就可以了,由代理人C去跟公司A进行购买,消费只需要等着收快递,其他的不用关心。

    二、使用代理好处

    1、通过引入代理对象的方式 来间接访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性;

    2、通过代理对象对原有的业务增强;

    三、UML图

    image

    公共接口角色:定义了委托角色代理角色的共同接口或者抽象类。

    委托角色(公司A) :实现或者继承抽象主题角色,定义实现具体业务逻辑的实现。

    代理角色(代理C) : 实现或者继承抽象主题角色,持有委托角色的引用,控制和限制委托角色的实现,并且拥有自己的处理方法(预处理和善后)。

    特点:

    • 委托角色代理角色共同继承同一个接口或者实现抽象类

    • 代理角色持有委托角色对象的引用

    四、静态代理

    根据上面的例子,我们来实现一个静态代理:

    静态代理在使用时,需要先定义接口或者抽象类,委托类代理类一起实现相同的接口或者是继承相同抽象类。一般来说,委托类代理类是一对一的关系,当然一个代理对象对应多个委托类也是可以的。

    步骤 :定义公共接口——>创建委托类(公司A)——>创建代理类(代理C)——>访问(消费者执行)

    1、定义公共接口

    /**
     * @author : EvanZch
     * description: 抽象主题角色,商家和代理销售这个操作
     **/
    public interface IFactoryA {
        void saleManTools(String size);
    }
    

    2、委托类 (公司A)

    /**
     * @author : EvanZch
     *         description: 公司A, 实现IFactory
     **/
    public class FactoryA implements IFactoryA {
        /**
         * 实现具体的业务逻辑
         *
         * @param size
         */
        @Override
        public void saleManTools(String size) {
            System.out.println("公司A——>出货:" + size + "MM");
        }
    }
    

    3、代理类 (代理C)

    /**
     * @author : EvanZch
     * description: 代理C,实现IFactory,并持有真实对象FactoryA引用
     **/
    public class ProxyC implements IFactoryA {
    
        private IFactoryA factory;
        /**
         * 持有被代理角色的引用
         * @param iFactory
         */
        public void setFactory(IFactoryA iFactory) {
            this.factory = iFactory;
        }
    
        /**
         * 对真实对象方法进行增强
         * @param size
         */
        @Override
        public void saleManTools(String size) {
            doBefore();
            factory.saleManTools(size);
            doAfter();
        }
    
        private void doBefore() {
            System.out.println("代理C——>根据客户需求定制方案");
        }
    
        private void doAfter() {
            System.out.println("代理C——>收集使用反馈");
        }
    }
    

    在执行被代理类方法前,可以进行功能拓展,符合开闭原则。

    4、消费者

    /**
     * @author : EvanZch
     * description: 消费者B
     **/
    public class ConsumerB {
        public static void main(String[] args) {
            IFactoryA factoryA = new FactoryA();
            ProxyC proxyC = new ProxyC(factoryA);
            proxyC.saleManTools("36D");
        }
    }
    

    结果:

    image

    如果这个时候出现了专卖女性用品的公司B,我们需要按照下面步骤再走一遍

    定义公共接口——>创建委托类——>创建代理类——>访问

    优点:

    静态代理好处就是能对目标对象方法进行功能拓展。

    上面例子中可以看到,海外公司只负责发货,代理类可以在不改动委托类的情况下对目标人群进行需求方案定制和使用情况反馈收集工作,对委托类的方法进行了功能拓展。

    缺点:

    静态代理,一对一(一个代理只代理一个公司)则会出现代理对象量多、代码量大,从而导致代码复杂,可维护性差的问题,一对多(一个代理代理多个公司)则代理对象会出现扩展能力差的问题。

    可以看到我们公共接口是销售男性用品,如果后续需求增加女性用品是不是又要改动接口或者增加接口?接口一改动,代理类也要跟着改,牵一发而动全身,当需求越来越多越来越复杂的时候,就会使整个代码臃肿,并且维护性变差。

    五、动态代理

    使用动态代理不必要自己在去实现代理类,只需要一个动态代理类 (代理公司) 就可以让程序运行在期间动态的创建接口的实现。

    动态代理实现的关键是需要使用 InvocationHandler接口和通过 Proxy 类动态创建对象。

    步骤 :定义公共接口——>创建委托类(公司A)——>只需要创建一个动态代理类(代理公司)——>访问(消费者执行)

    1、InvocationHandler 接口

    处理动态代理类对象方法的调用,每个动态代理类都会关联一个。

    package java.lang.reflect;
    
    /**
     * {@code InvocationHandler} is the interface implemented by
     * the <i>invocation handler</i> of a proxy instance.
     *
     * <p>Each proxy instance has an associated invocation handler.
     * When a method is invoked on a proxy instance, the method
     * invocation is encoded and dispatched to the {@code invoke}
     * method of its invocation handler.
     *
     * @author      Peter Jones
     * @see         Proxy
     * @since       1.3
     */
    public interface InvocationHandler {
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
    }
    

    2、Proxy类

    用来创建代理对象的类,是所有动态代理类的父类,主要使用 newProxyInstance 方法

    
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
                                              InvocationHandler h)
    

    先贴动态代理类实现的代码:

    /**
     * @author : EvanZch
     *         description: 动态代理类,必须实现InvocationHandler接口
     **/
    public class ProxyCompany implements InvocationHandler {
        /**
         * 依旧持有真实对象
         */
        private Object mFactory;
    
        public void setFactory(Object factory) {
            this.mFactory = factory;
        }
    
        /**
         * 获取动态代理对象
         */
        public Object getDynamicProxy() {
            /**
             * 拿到动态代理对象
             * ClassLoader loader :真实对象的ClassLoader
             * Class<?>[] interfaces : 真实对象实现的接口
             * InvocationHandler h  : InvocationHandler对象
             */
            return Proxy.newProxyInstance(mFactory.getClass().getClassLoader()
                    , mFactory.getClass().getInterfaces(), this);
        }
    
        /**
         * InvocationHandler 接口方法
         *
         * @param proxy  代理类本身
         * @param method 我们所要调用某个对象真实的方法的 Method 对象
         * @param args   method 对象中本身需要传入的参数
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            doBefore();
            // 调用真实对象方法
            Object result = method.invoke(mFactory, args);
            doAfter();
            return result;
        }
    
        private void doBefore() {
            System.out.println("代理公司——>方案制定");
        }
    
        private void doAfter() {
            System.out.println("代理公司——>收集反馈");
        }
    }
    

    调用动态代理

            // 创建动态代理对象
            ProxyCompany proxyCompany = new ProxyCompany();
            // 公司A
            IFactoryA factoryA = new FactoryA();
            // 动态代理引入真实对象
            proxyCompany.setFactory(factoryA);
            // 动态的创建代理类
            IFactoryA proxyA = (IFactoryA) proxyCompany.getDynamicProxy();
            proxyA.saleManTools("F");
    

    结果:

    image

    仍然获取正确结果,纵观整个过程,我们并没有实质的创建一个代理类,整个过程只需要一个动态代理类就能完成,如果这个时候我们出现了公司B,我们只需要执行的步骤 定义公共接口——>创建委托类(公司B)——>访问(消费者执行) 具体代码就不贴了,跟公司A类似,我把调用代码贴在一起,你品,细细的品。

            // 创建动态代理对象
            ProxyCompany proxyCompany = new ProxyCompany();
            // 公司A   IFactoryA:公共接口   FactoryA:委托类(公司A)
            IFactoryA factoryA = new FactoryA();
            // 动态代理引入真实对象
            proxyCompany.setFactory(factoryA);
            // 动态的创建代理类
            IFactoryA proxyA = (IFactoryA) proxyCompany.getDynamicProxy();
            proxyA.saleManTools("F");
    
            // 公司B   IFactoryB:公共接口   FactoryB : 委托类(公司B)
            IFactoryB factoryB = new FactoryB();
            proxyCompany.setFactory(factoryB);
            IFactoryB proxyB = (IFactoryB) proxyCompany.getDynamicProxy();
            proxyB.saleWomanTool(180);
    

    结果:

    image

    不知道各位看官可有一点感悟,我们可以看到,就算这个时候出现了公司B,我们整个过程也没有创建真实的代理对象,而是直接通过一个动态代理类中 proxyCompany.getDynamicProxy() 来动态的获取我们的代理类对象。既然是动态代理,那代理类肯定存在,只是jdk动态的给我们生成,那真实的代理类是谁?怎么创建的?

    我们先来看代理类是谁这个问题,我们对刚刚的代码进行debug调试

    image

    从日志输入信息里面可以看到,jdk为在运行时分别给我们的 IFactoryAIFactoryB 生成了名字为$Proxy0$Proxy1 的代理类,那它在哪产生的?带着这个问题,我们开始深入。

    六、源码分析

    我们是通过 这个方法来获取动态代理对象,那我们从这里切入,先看看newProxyInstance做了啥?

    image image

    如果你对反射熟悉的话,图片中标注的几处你应该很容易知道在干嘛。

    A:获取类的Class对象

    B:反射获取其构造方法

    C:遍历构造赋予权限

    D:返回该类的实例

    这里出现的类,没的说,肯定就是我们需要的那个实际的代理类,我们再看一下 getProxyClass0 方法做了啥?

    image

    感觉贴图比贴代码舒服,就直接贴图了,这个方法很简单,先对接口数目进行判断,65535这个数字搞Android的就很熟悉了吧,方法数不能超过65535,这不是我们本文讨论的关键,关键看 proxyClassCache.get(loader, interfaces)

    我们看到这个方法传入了我们设置的 ClassLoader参数 和 interfaces 参数 进入 get 方法里面

    image

    我们看到标识这里,传入了我们的参数,应该是一行关键性代码,我们再看它做了啥?

    image

    可以看到它是一个接口方法,这个时候我们需要再去找方法的实现类

    image

    可以看到 apply 方法实现类有 KeyFactoryProxyClassFactory 聪明你的看名字也应该知道,我们只需要关注 ProxyClassFactory 这个类

    image

    这里隐隐约约出现了 proxyName, 还记得我们前面debug调试出现的 $Proxy0$Proxy1吗?

    private static final String proxyClassNamePrefix = "$Proxy";
    String proxyName = proxyPkg + proxyClassNamePrefix + num;
    

    看到 $Proxy 了吗? 原来名字就是从这里出来的

    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
    

    这里通过ProxyGenerator类生成指定的代理类字节数组。

    其实jdk生成.class文件的内容就是一串串字节数组

    defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
    // 这是一个 native 方法
    private static native Class<?> defineClass0(ClassLoader loader, String name,
     byte[] b, int off, int len);
    

    再通过 defineClass0方法 通过指定ClassLoader生成代理类的Class对象,到这里,我们前面关于动态产生的代理类怎么产生的问题也就解决了。

    既然 ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags) 方法生成指定代理类的字节数组,那我们可以通过这个方法来看看,具体内容是啥?

    我们通过 generateProxyClass 获取到字节数组,并保存到本地。

        public static void generateProxyClass(String proxyName, Class clazz) {
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces(), 1);
            String paths = clazz.getResource(".").getPath();
            System.out.println(paths);
            FileOutputStream out = null;
            try {
                out = new FileOutputStream(paths + proxyName + ".class");
                out.write(proxyClassFile);
                out.flush();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    

    将我们动态产生的类,用上面的方法保存到本地。

    generateProxyClass(proxyA.getClass().getSimpleName(), factoryA.getClass());
    generateProxyClass(proxyB.getClass().getSimpleName(), factoryB.getClass());
    

    可以看到生成的两个class文件,名字刚好跟我们前面debug看到的一直,我们反编译文件看一下里面是啥。

    idea 打开.class文件自动进行反编译。

    image

    $Proxy0

    public class $Proxy0 extends Proxy implements IFactoryA {
        private static Method m1;
        private static Method m3;
        private static Method m2;
        private static Method m0;
    
        public $Proxy0(InvocationHandler var1) throws  {
            super(var1);
        }
    
        public final boolean equals(Object var1) throws  {
            try {
                return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final void saleManTools(String var1) throws  {
            try {
                super.h.invoke(this, m3, 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 int hashCode() throws  {
            try {
                return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
                m3 = Class.forName("com.evan.proxy.staticProxy.IFactoryA").getMethod("saleManTools", new Class[]{Class.forName("java.lang.String")});
                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }
    

    关键信息:

    1、 $Proxy0 继承至 Proxy

    2、实现了 IFactoryA 接口

    3、实现了我们接口里面的方法 :saleManTools(String var1)

    可以看到我们动态产生的代理类 $Proxy0 继承至 Proxy ,前面也说过,Proxy 是所有动态代理类的父类,所有动态代理类都需要继承它,通过生成的文件,我们可以证实这点。

    前面提到代理模式特点一直就是 代理类和委托类要同时实现一个接口或者实现抽象类,这里可以看到,我们创建的动态代理类同样也实现了我们的 IFactoryA 接口。

    我们再看一下saleManTools方法的实现

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

    关键信息:

    super.h.invoke(this, m3, new Object[]{var1});

    这个 h 是啥?在动态产生的代理类 $Proxy0 没看到这个参数,我们再在其父类 Proxy 中查看

    image

    就是我们前面动态代理类中实现的 InvocationHandler 接口。所以在 saleManTools 方法中,再调用了 InvocationHandler 接口的 invoke 方法,我们再回忆一下前面写动态代理类时候,怎么处理invoke方法的?回忆不起来,我就再贴一次!

        /**
         * InvocationHandler 接口方法
         *
         * @param proxy  代理类本身
         * @param method 我们所要调用某个对象真实的方法的 Method 对象
         * @param args   method 对象中本身需要传入的参数
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            doBefore();
            // 调用真实对象方法
            Object result = method.invoke(mFactory, args);
            doAfter();
            return result;
        }
    

    再看 saleManTools 方法实现中的这行,你对比着看,你品,细细的品。

    super.h.invoke(this, m3, new Object[]{var1});
    

    那这里的m3不就是Method嘛

    m3 = Class.forName("com.evan.proxy.staticProxy.IFactoryA").getMethod("saleManTools", new Class[]{Class.forName("java.lang.String")});
    

    再看m3的值,不就是通过反射拿到 我们自己定义的IFactoryA 接口的 saleManTools 方法???看到这里,再回头去看看我们前面的动态代理类,你对 InvocationHandlerProxy 这两个关键类应该就有了更清晰的认识了,如果没有,就再看一遍??

    好了,以上源码分析内容基本就是jdk对静态代理的实现(这个车是不是刹的有点快,哈哈)。

    七、动态代理在Android中的运用

    retrofit 这个网络请求库我相信搞Android的大哥们应该都用过吧,我们一般怎么操作?

    1、编写 xxxApi 接口

    public interface xxxApi {
        String HOST = "xxxxxxxxxx";
        @POST("app/xxxx")
        @FormUrlEncoded
        Observable<BaseResponse<String>> sendEmailCode(@Field("email") String email);
    }
    

    2、初始化 retrofit :

    Retrofit retrofit = new Retrofit.Builder()
     .baseUrl("https://xxxxxx")
     .build();
    

    3、动态创建 xxxApi 实例

    xxxApi service = retrofit.create(xxxApi.class);
    service.sendEmailCode(xxxx);
    

    有没有很熟悉,我们 create 的时候不是只传了一个 interface 进去吗?怎么就可以直接通过返回的实例调用方法了呢?跟我们前面的动态代理是不是有几分相似?我们去看看Retrofit的源码,看他 Create 到底操作了啥?

    我们在github上可以看到 retrofit 的 create 方法

    image

    看到 Proxy.newProxyInstance 这个方法,就应该很清楚了,证实我们前面的猜测,至于具体怎么操作的跟前面的类似,这里就不再分析了。

    相关文章

      网友评论

        本文标题:代理模式以及在Android中的使用

        本文链接:https://www.haomeiwen.com/subject/hkeggctx.html