设计模式-代理模式

代理模式介绍

代理模式(Proxy Pattern)也称为委托模式,是结构型设计模式的一种。在现实生活中用到代理的场景有很多,如:加盟商,去代售点买票,代理上网等。

代理模式定义

为其他对象提供一种代理以控制这个对象的访问。

代理模式使用场景

当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过代理类对象来间接访问。

代理模式 UML 类图


角色介绍:

  • Subject:抽象主题类,声明真实主题与代理的共同接口方法。
  • RealSubject:真实主题类,该类定义了代理所表示的真实对象,是负责执行系统真正的逻辑业务对象。
  • Proxy:代理类,其内部持有 RealSubject 的引用,因此具备对 RealSubject 的代理权。

代理模式简单实现

这里以邮递物品为例,邮递公司看做代理(其不负责邮递),邮递员为实际邮递者。

抽象主题类(Subject)

抽象主题类具有真实主题类和代理的共同接口方法,需要邮递物品,则就是邮递

public interface IPost {
    void post(String goods);
}

真实主题类(RealSubject)

真正负责邮递物品的是快递员 Postman

public class Postman implements IPost {

    @Override
    public void post(String goods) {
        System.out.println("邮递员:邮寄物品为" + goods);
    }
}

代理类(ProxySubject)

起代理作用的是邮递公司,邮递公司并不进行实际邮递工作,其会让邮递员进行邮递,为了避免物品给邮递员带来危险,其负责对物品进行安全检查。

public class PostProxy implements IPost {

    private Postman mPostman;

    public PostProxy(Postman postman) {
        mPostman = postman;
    }

    @Override
    public void post(String goods) {
        System.out.println("货物安检");
        if (goods.contains("handgun")) {
            System.out.println("货物存在违禁品,无法邮递");
            return;
        }
        System.out.println("货物正常,开始邮递");
        mPostman.post(goods);
    }
}

客户端

public class Client {
    public static void main(String[] args) {
        Postman postman = new Postman();
        PostProxy postProxy = new PostProxy(postman);
        // 邮递手机
        postProxy.post("handset");
        // 邮递手枪
        postProxy.post("handgun");
    }
}

输出结果如下:

货物安检
货物正常,开始邮递
邮递员:邮寄货物为handset

货物安检
货物存在违禁品,无法邮递

代理 PostProxy 完成了物品的安检和邮递。这里先说下我的疑问:已经有Postman 对象了,为什么又去调用 PostProxy?
我们的代理类可以不只为一个真实主题类做代理,我们可以为多个具有邮递能力的机构做代理,那么我们就可以将 PostProxy 持有 Postman 对象改为 IPost 接口。

代理模式的扩展

设计模式中有普通代理强制代理的概念。

  • 普通代理:我们需要知道代理的存在,而不能去访问真实主题角色。
  • 强制代理:调用者直接调用真实角色,而不用关心代理是否存在,其代理的产生是由真实角色决定。

普通代理示例

基于上述示例稍作修改:

public class Postman implements IPost {
    public Postman(IPost post) throws Exception {
        // 通过是否传入代理类来验证创建方
        if (post == null) {
            throw new Exception("不能创建真实主题角色");
        }
    }

    @Override
    public void post(String goods) {
        System.out.println("邮递员:邮寄货物为" + goods);
    }
}
public class PostProxy implements IPost {

    private Postman mPostman;

    public PostProxy() {
        try {
            // 有权创建 Postman
            mPostman = new Postman(this);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void post(String goods) {
        System.out.println("货物安检");
        if (goods.contains("handgun")) {
            System.out.println("货物存在违禁品,无法邮递");
            return;
        }
        System.out.println("货物正常,开始邮递");
        mPostman.post(goods);
    }
}
public class Client {
    public static void main(String[] args) {
        IPost post = new PostProxy();
        post.post("handset");
    }
}

运行结果完全相同。在该模式下,调用者只知代理而不用知道真实的角色是谁,屏蔽了真实角色的变更对高层模块的影响。当然,在实际的项目中,一般都是通过约定来禁止new一个真实的角色,这也是一个非常好的方案。

强制代理示例

该种代理要求必须通过真实角色查找到代理角色,否则你无法访问。无论你是通过代理类还是通过直接new一个主题角色类,都无法访问,只有通过真实角色指定的代理类才可以访问,也就是说由真实角色管理代理角色。以明星和他的经纪人为例,明星相当于真实角色,经纪人相当于代理,而你和该明星关系非常好,找他办事肯定直接给他打电话,明星最后就找他指定的经纪人完成你求的事。

public interface IPost {
    void post(String goods);
    // 都可以有自己指定的代理
    IPost getProxy();
}
public class Postman implements IPost {
    private IPost mProxy;

    @Override
    public void post(String goods) {
        // 是我的代理邮递,不是则提示
        if (isProxy()) {
            System.out.println("邮递员:邮寄货物为" + goods);
        } else {
            System.out.println("请找我的代理进行邮寄");
        }
    }
    
    // 返回我指定的代理
    @Override
    public IPost getProxy() {
        mProxy = new PostProxy(this);
        return mProxy;
    }
    // 判断是不是我的代理
    private boolean isProxy() {
        return mProxy != null;
    }
}
public class PostProxy implements IPost {
    // 被代理的对象
    private IPost mPoster;

    public PostProxy(IPost post) {
        mPoster = post;
    }
    @Override
    public void post(String goods) {
        mPoster.post(goods);
    }
    // 自己也可以被代理,我为自己代理。
    @Override
    public IPost getProxy() {
        return this;
    }
}

客户端代码

public class Client {
    public static void main(String[] args) {
        // 1. 直接通过真实对象邮递,邮递失败
        IPost post = new Postman();
        post.post("handset");
        // 2. 自己寻找邮递代理,邮递失败
        IPost proxy = new PostProxy(post);
        proxy.post("handset");
        // 3. 访问邮递员指定的代理,邮递成功
        post.getProxy().post("handset");
    }
}

输出结果:

请找我的代理进行邮寄
请找我的代理进行邮寄
邮递员:邮寄货物为handset

强制代理的概念就是要从真实角色查找到代理角色,不允许直接访问真实角色。高层模块只要调用getProxy就可以访问真实角色的所有方法,它根本就不需要自己创建一个代理出来,代理的管理已经由真实角色自己完成。

代理类不仅仅可以实现主题接口,也可以实现其他接口完成不同的任务,而且代理的目的是在目标对象方法的基础上作增强,这种增强的本质通常就是对目标对象的方法进行拦截和过滤。

动态代理模式简单实现

从编码的角度来说,代理模式分为静态代理动态代理,上面的例子是静态代理,在代码运行前就已经存在了代理类的class编译文件,而动态代理则是在代码运行时通过反射来动态的生成代理类的对象,并确定到底来代理谁。也就是我们在编码阶段不需要知道代理谁,代理谁我们将会在代码运行时决定。Java 给我们提供了一个便捷的动态代理接口 InvocationHandler,实现该接口需要重写invoke()方法。下面我们在上面静态代理的例子上做修改:

创建动态代理类:

public class DynamicProxy implements InvocationHandler {

    // 被代理的类引用
    private Object obj;

    public DynamicProxy(Object obj) {
        this.obj = obj;
    }

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

当调用代理类的接口方法时,会调用到 invoke 方法,该方法中负责反射调用被代理者的真实逻辑实现的方法。

修改客户端代码

public class Client {
    public static void main(String[] args) {
        IPost postman = new Postman();
        // 创建动态代理
        DynamicProxy dynamicProxy = new DynamicProxy(postman);
        // 创建被代理者的 ClassLoader
        ClassLoader loader = postman.getClass().getClassLoader();
        // 创建代理者
        IPost poster = (IPost) Proxy.newProxyInstance(loader, new Class[]{IPost.class}, dynamicProxy);
        poster.post("handgun");
    }
}

输出结果:

邮递员:邮寄货物为handgun

动态代理通过一个代理类来代理 N 多个被代理者,其实质是对代理者和被代理者解耦,使两者没有直接的耦合关系。 而静态代理则只对给定的接口下的实现类做代理,如果接口不同就需要重新定义代理类。

代理模式类型

从编码角度区分

  • 静态代理
  • 动态代理

从适用范围区分

  • 远程代理(Remote Proxy):为某个对象在不同的内存地址空间提供局部代理。是系统可以将 Server 部分的实现隐藏,以便 Client 可以不必考虑 Server 的存在。Android 中 aidl 生成的 Java 代码 中存在个 Proxy,该代码运行在客户端,而实际逻辑运行在服务端,该Proxy 就是远程代理。
  • 虚拟代理(Virtual Proxy):使用一个代理对象表示一个十分耗资源的对象并在真正需要时才创建。
  • 保护代理(Protection Proxy):使用代理控制对真实对象的访问。该类型的代理常被用于对原始对象有不同的访问权限的情况。上面的静态代理示例就属于该种。
  • 智能引用(Smart Reference):在访问原始对象时执行一些自己的附加操作并对指向原始对象的引用计数。

静态代理和动态代理都可以应用于上述 4 中情形。

总结

代理模式优点
1.职责清晰:真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务
2.高扩展性:代理类可以完全控制真实主题,可以控制访问,扩展功能,而不用修改原真实主题。
代理模式缺点
1.由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
2.实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

Android 源码中代理模式实现

Android 源码中有很多代理模式的实现,比如 AIDL 生成的Java文件中 Proxy类,这里直接以 Android 7.0 上 ActivityManagerProxy 为例介绍,ActivityManagerProxy 就相当于 AIDL 生成的 Proxy 类,只不过 Framework 中直接以 Java 代码的形式书写,在Android 8.0 上已经变化为 AIDL 形式,请知悉。

为了方便,下面对ActivityManagerProxy,ActivityManagerNative 和 ActivityManagerService 会以简写方式书写,分别为: AMP,AMN 和 AMS。
AMP 具体代理的是 AMN 的子类 AMS, AMP与 AMN 在同一文件中。

class ActivityManagerProxy implements IActivityManager {
  ...
}

IActivityManager 为接口类就是代理模式中的抽象主题,其中定义了一些 Activity 相关的接口方法

public interface IActivityManager extends IInterface {
  ...
  public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
            String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags,
            ProfilerInfo profilerInfo, Bundle options) throws RemoteException;
  public Intent registerReceiver(IApplicationThread caller, String callerPackage,
            IIntentReceiver receiver, IntentFilter filter,
            String requiredPermission, int userId) throws RemoteException;
  public int checkPermission(String permission, int pid, int uid)
            throws RemoteException;
  ...
}

对于真正的主题角色是谁呢?就是继承 AMN 的 AMS,这几个类大致关系如下图:


通过 UML 图可以清晰的看出 AMP 和 AMN 都实现了 IActivityManager 接口,严格地说,AMP 就是代理部分,而 AMN 就是真实主题角色,但 AMN 是抽象类,并不处理过多逻辑,大部分是由 AMS 完成。
AMS 是系统级Service,运行在system_server进程中,而 AMP 一般运行在客户端进程也就是 app 进程,他们之间的通信属于 Binder 跨进程通信。对于 AMP 并不是在 app 进程直接使用的,而是通过 ActivityManager 类,该类负责管理和维护 Activity 相关信息,但实际大多数逻辑是通过 AMP 承担。这里以 getMemoryInfo 为例介绍是如何工作的。
ActivityManager.java

public void getMemoryInfo(MemoryInfo outInfo) {
    try {
        ActivityManagerNative.getDefault().getMemoryInfo(outInfo);
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

ActivityManagerNative.java
ActivityManagerNative.getDefault() 该方法实现如下

static public IActivityManager getDefault() {
    return gDefault.get();
}

gDefault 又是什么?

private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
    protected IActivityManager create() {
        // 1. 与ServiceManger 进行 IPC 通信,获取系统级 Service,该Service 实质上是 AMS
        IBinder b = ServiceManager.getService("activity");
        // 2. 返回 AMP
        IActivityManager am = asInterface(b);
        return am;
    }
};
// 跨进程通信,则返回的是 AMP
static public IActivityManager asInterface(IBinder obj) {
    if (obj == null) {
        return null;
    }
    IActivityManager in =
        (IActivityManager)obj.queryLocalInterface(descriptor);
    if (in != null) {
        return in;
    }
    return new ActivityManagerProxy(obj);
}

gDefault.get() 就是单例的 AMP 对象
ActivityManagerProxy.java

public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) throws RemoteException {
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    data.writeInterfaceToken(IActivityManager.descriptor);
    // 1.
    mRemote.transact(GET_MEMORY_INFO_TRANSACTION, data, reply, 0);
    reply.readException();
    outInfo.readFromParcel(reply);
    data.recycle();
    reply.recycle();
}

该段就是 Binder 通信的核心代码,在注释1处,调用客户端Binder的 transact() 方法,服务端 Binder 的 onTransact() 就会回调,这个是由 Binder 驱动进行完成。客户端 transact 方法中传入方法标识为GET_MEMORY_INFO_TRANSACTION,data 用于向服务端写入数据,reply 用于接收服务端的返回数据,当服务端对应方法执行完后, transact 就停止阻塞,继续走到outInfo.readFromParcel(reply);,将返回结果写入outInfo 对象中。接下来我们看服务端是 AMS 是如何执行的?
onTransact 方法在 抽象类 AMN 中

@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
        throws RemoteException {
    switch (code) {
    ...
    case GET_MEMORY_INFO_TRANSACTION: {
        data.enforceInterface(IActivityManager.descriptor);
        ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
        getMemoryInfo(mi);
        reply.writeNoException();
        mi.writeToParcel(reply, 0);
        return true;
    }
    ...
}

实际是通过 getMemoryInfo 方法完成
ActivityManagerService.java

@Override
public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) {
    final long homeAppMem = mProcessList.getMemLevel(ProcessList.HOME_APP_ADJ);
    final long cachedAppMem = mProcessList.getMemLevel(ProcessList.CACHED_APP_MIN_ADJ);
    outInfo.availMem = Process.getFreeMemory();
    outInfo.totalMem = Process.getTotalMemory();
    outInfo.threshold = homeAppMem;
    outInfo.lowMemory = outInfo.availMem < (homeAppMem + ((cachedAppMem-homeAppMem)/2));
    outInfo.hiddenAppThreshold = cachedAppMem;
    outInfo.secondaryServerThreshold = mProcessList.getMemLevel(
            ProcessList.SERVICE_ADJ);
    outInfo.visibleAppThreshold = mProcessList.getMemLevel(
            ProcessList.VISIBLE_APP_ADJ);
    outInfo.foregroundAppThreshold = mProcessList.getMemLevel(
            ProcessList.FOREGROUND_APP_ADJ);
}

AMS 中完成获取 Memory 信息的获取,最后返回到客户端中调用时传入的 MemoryInfo 对象中。

客户端和具体主题角色 AMS 属于不同进程,所以这里的 AMP 就是远程代理,使系统可以将服务端的实现隐藏,客户端使用时无需关心 AMS。对于访问 AMS 都交由 AMP,由于牵扯到跨进程,调用 AMS 代码会比较复杂,这些都有 AMP 完成了处理,简化了客户端的访问。

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

推荐阅读更多精彩内容