Java代理模式与Android的情结

版权声明:本文为博主原创文章,未经博主允许不得转载

前言

Java 代理模式在 Android 中有很多的应用。比如 AndroidBinderClient 部分就是通过代理模式来访问 Server 端的、代理 ActivityFragment 模式、还有很多开源框架也都使用了代理模式 (主要是动态代理)。

概念

简单地说,代理模式就是代理对象为其他真实对象 (也叫被代理对象) 提供代理机制,以便控制对真实对象的访问。此时,如果真实对象不想直接和客户端接触,则可让代理对象充当真实对象与客户端之间的中介来联系二者,完成事务联系。
抽象地说,代理对象和被代理对象一般实现相同的接口,调用者与代理对象进行交互。代理的存在对于调用者来说是透明的,调用者看到的只是接口。代理对象则可以封装一些内部的处理逻辑,如访问控制、远程通信、日志、缓存等。比如一个对象访问代理就可以在普通的访问机制之上添加缓存的支持

举个简单的例子,比如中国移动 (真实对象) 和用户 (客户端) 之间的这种业务关系。首先,我们老百姓不可能直接和中国移动总部接触,因而中国移动会将业务的操作权下放到各个省市乡的代理点 ( 代理对象),而用户到代理店办的业务其实就是总部的业务,这样一来便可大大地扩大业务,也能更方便服务用户。

代理模式的分类

静态代理

UML类图


UML解析:首先,定义了一个 Subject 类型的接口,并声明了接口方法 DoAction();其次,分别定义了两个实现了 Subject 接口的代理类 Proxy 以及 真实对象类 RealSubject ,其中代理类代表了(delegate)真实对象类;最后,客户端 Cient 依赖关联 Subject,再由 Subject 操作 DoAction()

代码实现

public interface Subject {
//做操作
void doAction();
}

public class RealSubject implements Subject{

@Override
public void doAction() {

}
}  

public class Proxy implements Subject {
private RealSubject mRealSubject=null;
private void preDoAction(){
    //do something before doAction
}
@Override
public void doAction() {
   if (mRealSubject==null)
       mRealSubject=new RealSubject();
    preDoAction();//代理类准备事情
    //代理类与真实对象类搞事情了,等于代理类执行了真实类中对应的那个方法了,
    //即代理类隐藏了真实类中方法的实现细节。
    mRealSubject.doAction();
    postDoAction();//代理类善后
}
private void postDoAction(){
    //do something after doAction
}
 }  


public class Client {
public static void main(String[] args) throws Throwable {
    Subject subject=new Proxy();
    subject.doAction();//代理类代替真实类做事情
}
}

以上就是静态代理的代码实现。你可以看见,每个代理类都需要一个真实类来对应,即代理类依赖于真实类的对象。

静态代理如何用

我先举个场景。我们需要拿到数据,而数据分别来源于本地、内存及网络,定义一个方法 getData() 用于获取数据。由于我们数据有三种,我们必然需要分别实现 getData(),此时,我们将此方法抽取到接口中,三个实现类各自实现接口即可。然而,现在我们需要在每次获取数据前后执行一些公共操作,比如缓存,日志处理等。那么,最差的做法就是每个实现类都去实现一样的逻辑后再去调用。而如果我们利用静态代理,就可以将此方法代理出去。在代理方法中完整的执行获取数据及其公共操作部分,而隐藏 getData()
的实现细节,实现细节交由各个实现类处理。这样一来,调用者只需调用这个代理方法,而无需和其他实现类交互。在代码层面,客户端调用获取数据的方法时代码调用趋于固定,而如果以后需要修改 getData() 实现细节,也只需要改动实现类中对应的方法即可,这样便提高了可扩展性。

注:getData()类似上述Subject中的doAction(),代理方法类似Proxy中的doAction()

然而,当每个代理类都对应一个真实类,那么大量使用的话会引起代码量的急剧膨胀,因而我们就需要一种机制--不需要提前知道真实类,而是在动态运行中才指定这样一个类。那么这样就引入了代理模式的另一种方式--动态代理。

动态代理

涉及到的几个类

  1. 接口 InvocationHandler:该接口中仅定义了一个方法 Object:invoke(Object obj,Method method, Object[] args)。在实际使用时,第一个参数 obj 一般是指代理类,method 是被代理的方法,如上例中的 doAction()args 为该方法的参数对象数组。这个抽象方法在代理类中动态实现(反射调用)
  2. Proxy:该类即为生成的动态代理类的父类,作用类似于静态代理中的 Proxy,其中主要包含以下内容:
    • 构造函数 Protected Proxy(InvocationHandler h),其中子类 (生成的动态代理类)继承父类构造方法可获得 h 实例 。
    • Static Class getProxyClass (ClassLoaderloader, Class[] interfaces):获得一个代理类,其中 loader 是类装载器引用,interfaces 是被代理类所拥有的全部接口的数组。
    • Static Object newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在 Subject 接口中声明过的方法),也就是此时代理类已获得被代理类的类似功能,故你只需操作代理类完成所需请求即可。

机制分析

动态代理允许客户端在运行时刻动态地创建出实现了多个接口的代理类及其对象。其中每一个代理类的对象都会关联一个表示内部处理逻辑的接口类 InvocationHandler (继承父类含参构造方法可得到接口引用)实现。当客户端调用了代理对象所代理的接口方法时,信息会被 InvocationHandler 中的 invoke() 方法拦截处理。在 invoke() 方法里可以截取到代理类的对象,代理方法对应的 Method 对象以及实际调用的参数,然后再经过处理而后返回新的对象。

接下来我们来看看代码实现。

代码实现

Subject 以及 RealSubject 同上

  1. 实现了调用处理器监听的动态代理类

    public class DynamicProxyObject implements InvocationHandler {
    private Object mObject;//被代理对象的引用
    
    public DynamicProxyObject(Object object) {
        mObject = object;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before calling " + method);
        Object object=method.invoke(mObject,args);
        System.out.println("after calling " + method);
        return object;
    }
    }
    
  2. 客户端类

     public class Client {
     public static void main(String[] args) throws Throwable {
         RealSubject rs = new RealSubject();
         InvocationHandler handler = new DynamicProxyObject(rs);
         Class cl = rs.getClass();
         //第一种.分解步骤得到代理类对象
         //Class c = Proxy.getProxyClass(cl.getClassLoader(), cl.getInterfaces());
         //Constructor ct = c.getConstructor(new Class[]{InvocationHandler.class});
         //Subject subject1 = (Subject) ct.newInstance(new Object[]{handler});
        //第二种.一次性得到代理类对象
         Subject subject=
         (Subject) Proxy.newProxyInstance(cl.getClassLoader(),cl.getInterfaces(),handler);
          subject.doAction();
     }
     }
    
  3. 总结

通过这种方式,被代理的对象(RealSubject)可以在运行时动态改变,需要控制的接口(Subject 接口)可以在运行时改变,控制的方式(DynamicProxyObject 类)也可以动态改变,从而实现了非常灵活的动态代理机制。

源码分析

主要以 Proxy 类为主 (该类位于java.lang.reflect 包下)

几个重要的静态变量

    private final static String proxyClassNamePrefix = "$Proxy";

    //动态代理类的构造函数参数类数组
    private final static Class[] constructorParams ={ InvocationHandler.class };

    //映射表:用于维护类加载器对象到其对应的代理类缓存
    private static Map<ClassLoader, Map<List<String>, Object>> loaderToCache
            = new WeakHashMap<>();

    //标记动态代理类正在创建中
    private static Object pendingGenerationMarker = new Object();

    //用于创建唯一的动态代理类名而定义的 number,可递增
    private static long nextUniqueNumber = 0;
    private static Object nextUniqueNumberLock = new Object();

    //同步表:记录所有已创建的动态代理类实例类型,可供 isProxyClass() 使用
    private static Map<Class<?>, Void> proxyClasses =
            Collections.synchronizedMap(new WeakHashMap<Class<?>, Void>());

    //动态代理类关联的 InvocationHandler
    protected InvocationHandler h;  

获取动态代理类实例的方法1

    private static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) 
    throws IllegalArgumentException
    { 
     Class<?> proxyClass = null;

    //收集接口名用来作为代理类缓存的key
    String[] interfaceNames = new String[interfaces.length];

    //用于检测重复接口名
    Set<Class<?>> interfaceSet = new HashSet<>();

    for (int i = 0; i < interfaces.length; i++) {
        //验证class loader 解析出来的接口是否与反射出来的类对象相同
        String interfaceName = interfaces[i].getName();
        Class<?> interfaceClass = null;
        try {
            //Java反射得到一个类对象
            interfaceClass = Class.forName(interfaceName, false, loader);
        } catch (ClassNotFoundException e) {
        }
        //当前反射得到的类对象不等于接口数组中的接口类对象
        if (interfaceClass != interfaces[i]) {
            throw new IllegalArgumentException(
                    interfaces[i] + " is not visible from class loader");
        }

        //验证反射出来的类对象是否是接口
        if (!interfaceClass.isInterface()) {
            throw new IllegalArgumentException(
                    interfaceClass.getName() + " is not an interface");
        }

        //验证接口类对象不重复
        if (interfaceSet.contains(interfaceClass)) {
            throw new IllegalArgumentException(
                    "repeated interface: " + interfaceClass.getName());
        }
        //每次反射得到的类对象即接口对象存入 Set 集合
        interfaceSet.add(interfaceClass);
        //数组记录接口类名
        interfaceNames[i] = interfaceName;
    }

    //接口名数组转接口名集合,以作为代理类缓存的 key
    List<String> key = Arrays.asList(interfaceNames);

    //为类加载实例查找或者创建代理类缓存
    Map<List<String>, Object> cache;
    synchronized (loaderToCache) {
        cache = loaderToCache.get(loader);
        if (cache == null) {//无则创建加入缓存映射表
            cache = new HashMap<>();
            loaderToCache.put(loader, cache);
        }
    }
    //接下来就是使用 key 检索代理类缓存。而这次检索将会产生以下三张情况
    //1.空值 null. 意味着当前类加载器中没有该代理类
    //2.正在创建的对象.意味着一个代理类正在创建中.
    //3.类对象的一个弱引用,意味着代理类此时已经创建.

    synchronized (cache) {
        do {
            Object value = cache.get(key);
            if (value instanceof Reference) {
                proxyClass = (Class<?>) ((Reference) value).get();
            }
            if (proxyClass != null) {
                //上述情况3,直接返回代理类实例
                return proxyClass;
            } else if (value == pendingGenerationMarker) {
                // 上述情况2,需要等待创建成功
                try {
                    cache.wait();
                } catch (InterruptedException e) {

                }
                continue;
            } else {

                //上述情况1,标记正在创建.
                cache.put(key, pendingGenerationMarker);
                break;
            }
        } while (true);
    }

    try {
        String proxyPkg = null;     //定义代理类所在的包名

        //记录非公有代理类接口的包名以便代理类都能被定义在相同包名下,
        // 并验证所有的非公有代理类接口都在相同包名下.
        for (int i = 0; i < interfaces.length; i++) {
            int flags = interfaces[i].getModifiers();
            if (!Modifier.isPublic(flags)) {//是否公有
                String name = interfaces[i].getName();
                int n = name.lastIndexOf('.');
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                if (proxyPkg == null) {//接口名作为包名
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {//包名不同
                    throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                }
            }
        }

        if (proxyPkg == null) {
            proxyPkg = "";
        }
         //直接生成代理类引用
        {
            //得到所有接口的方法对象集合
            List<Method> methods = getMethods(interfaces);
            //方法对象集合排序
            Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE);
            //验证每个方法的返回类型
            validateReturnTypes(methods);
            //去掉重复的方法,并收集异常
            List<Class<?>[]> exceptions = deduplicateAndGetExceptions(methods);

            Method[] methodsArray = methods.toArray(new Method[methods.size()]);
            Class<?>[][] exceptionsArray =
            exceptions.toArray(new Class<?>[exceptions.size()][]);

            //定义动态代理类的名字 proxyName
            final long num;
            synchronized (nextUniqueNumberLock) {
                num = nextUniqueNumber++;
            }
            String proxyName = proxyPkg + proxyClassNamePrefix + num;
            //利用 generateProxy() 方法传入所需参数得到动态代理类的引用
            proxyClass = generateProxy(proxyName, interfaces, loader, methodsArray,
                    exceptionsArray);
        }
        //保存数据
        proxyClasses.put(proxyClass, null);

    } finally {
      
        //异常进入处理,如果代理类创建成功则用弱引用处理后加入代理类缓存中,
        // 否则去除该key所对应的正在创键的代理类
        synchronized (cache) {
            if (proxyClass != null) {
                cache.put(key, new WeakReference<Class<?>>(proxyClass));
            } else {
                cache.remove(key);
            }
            //刷新缓存集合
            cache.notifyAll();
        }
    }
    return proxyClass;
    }

动态创建代理类实例的方法2

    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,
    InvocationHandler h) throws IllegalArgumentException
    {
     if (h == null) {
        throw new NullPointerException();
    }
    Class<?> cl = getProxyClass(loader, interfaces);

    /*
     * @param constructorParams 构造器参数类数组
     * 利用构造器反射得到关联了 InvocationHandler 的代理类实例
     *
     */
    try {
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        return newInstance(cons, h);//实际调用了  cons.newInstance(new Object[] {h} );
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString());
    }
    }  

动态创建出实现了接口方法的代理类代码例子

    import com.example.hrx.mvpdemo.bean.Subject;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    public final class DynamicProxyType extends Proxy implements Subject {
    private static Method m1;
    private static Method m0;
    private static Method m3;
    private static Method m2;

    //构造方法,参数就是一开始实例化的InvocationHandler接口的实例 继承自 Proxy
    public $Proxy1(InvocationHandler var1){
        super(var1);
    }

    public final boolean equals(Object var1){
        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 int hashCode() {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    //动态代理类代理的方法实现
    public final void doAction()  {
        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()  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    //static代码块加载Method对象
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            m3 = Class.forName("com.example.hrx.Subject").getMethod("doAction", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
    }

动态代理怎么用

我们仍以静态代理怎么用中的场景为例。当实现类越来越多时,利用动态代理,能够大大简化代码量。而我们在静态代理的代理方法实现类似地被放在了动态代理中invoke()中,但是实现形式有差异。后者主要利用反射来拿到所需要的实现类的被代理方法,其他则和静态代理无异。

与 Android 的情结

Retrofit 中自定义的网络请求到 OkHttp.Call 的适配

我们都知道可以使用类似 GitHub github = retrofit.create(GitHub.class) 来获得接口的引用;其实在内部是创建了一个实现了我们自定义的接口 GitHub 的动态代理类,当我们开始执行网络请求时,代理类会按照 Retrofit 先前配置的逻辑来处理我们发出的网络请求:比如交由 okhttp3.Call 来进行网络请求。Retrofit 完成的是封装客户网络请求的高效工作,而真正的网络请求的工作是委托给了 OkHttp 来完成。

关键代码:

    public final class Retrofit {

      public <T> T create(final Class<T> service) {
        //此处同样应用上述创建动态代理类实例方法
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] 
        { service },new InvocationHandler() {
              private final Platform platform = Platform.get();
               @Override
               public Object invoke(Object proxy, Method method, Object... args)
                  throws Throwable {
                // method来自Object.class则不做自定义处理
                if (method.getDeclaringClass() == Object.class) {
                  return method.invoke(this, args);
                }
                //默认处理,即根据平台类型(Android、Java8、IOS)做处理
                if (platform.isDefaultMethod(method)) {
                  return platform.invokeDefaultMethod(method, service, proxy, args);
                }
                ServiceMethod serviceMethod = loadServiceMethod(method);
                OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
                return serviceMethod.callAdapter.adapt(okHttpCall);
              }
            });
      }
    }  

binder机制

在跨进程通信中,binder 作为通信的媒介,联系了客户端 Client 与服务端 Server 的信息交流。
接下来,我们来看一张图:


注:图中 SM 表示 ServerManager,其在 Android 中是联系这四大组件的关键。

首先,Server 必须先注册到 SM 中,才能够有机会和 Client 通信,然后 Client 发起请求想要调用 Server 中的方法 add(),然而 ClientServer 处在不同的进程当中。如果没有媒介binder 驱动的帮助,进程间就无法完成通信。因此,透过 binder 的作用,Server 可以给 Client 下发一个代理对象,以便能够调用 Server中的方法。这样以来,Client 就无法知道 Server 的逻辑,而只能调用被 Server 代理出去的方法 add()。这一通信过程,便是跨进程通信。

总结

Java 代理模式的应用还是相当广泛的。对于静态代理模式,我们可以用它来代理 Activity 或者 Fragment 的生命周期方法,定义一个不注册的 ActivityDelegate 或者 FragmentDelegate,这个可能会是比较特别的高级技巧。对于动态代理,涉及到的大多是第三方框架,其中思想很多包含有动态代理。

感谢

Java设计模式之代理模式(Proxy)
JAVA 代理模式(Proxy)
代理模式和Java中的动态代理机制
写给Android App开发人员看的Android底层知识

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容