15 代理模式

15.1 代理模式概述

Proxy Pattern: 给某一个对象提供一个代理或占位符,由代理对象来控制对原对象的访问。

代理对象是客户端和目标对象的之前的桥梁,它接收来自客户端的请求并转发给目标对象,去掉客户端不能看到的内容或添加额外的服务。为了保证客户端使用的透明性,代理对象和原对象需要实现相同的接口。

代理模式结构图如下所示:

15.2 代理模式实现

15.2.1 抽象主题接口

代理对象和原访问对象都需要实现该接口,使得任何使用真实主题对象的地方都可以使用代理对象,客户端也可以针对抽象层编程。

public interface Subject {
public void request();
}

15.2.2 真实主题类

public class RealSubject implements Subject {
public void request() {
// 业务逻辑代码
}
}

15.2.3 代理类

代理类中包含对真实主题的引用,可以把客户端请求转发给真实主题并将请求结果返回给客户端。同时,代理类在将请求转发给真实主题之前和之后还可以执行一些其他操作,增加客户端需要的额外服务。

public class Proxy implements Subject {
private Subject realSubject = new RealSubject(); public void request() {
preRequest(); // 请求转发给真实主题
realSubject.request(); postRequest();
} public void preRequest() {
// 转发前业务代码
} public void postRequest() {
// 转发后业务代码
}
}

15.2.4 客户端调用

public class Client {
public static void main(String[] args) {
// 针对抽象层编程, 具体类名 Proxy 可以在配置文件写入
// 该代码等价于 Subject subject = new Proxy();
Subject subject = (Subject) XMLUtil.getBean(); // 通过代理类访问真实主题
subject.request();
}
}

15.3 远程代理

Remote Proxy: 远程代理使得客户端可以访问远程主机上的对象并调用其方法。

远程代理示意图如下所示:代理对象负责接收客户端请求,并通过网络通信访问远程对象,代理对象对于客户端是透明的。

15.3.1 Java RMI

Method Invocation是Java对象间通信最基本的方法,对于同一虚拟机内的对象通信,简单的方法调用即可实现。而对于位于不同主机上的虚拟机中的对象之间通信,则需要一套远程方法调用的机制,帮助我们获得远程主机上对象的引用,并在自己的虚拟机中像使用自己的对象一样使用它。

RMI 允许我们调用远程对象上的方法,可以将 Java 对象作为参数传递,并获得 Java 对象作为返回值。因此 RMI 需要使用对象序列化,在网络上传输对象,必要时还可以使用动态类加载和安全管理器安全地传输 Java 对象。

15.3.2 Stubs and skeletons

Java RMI 主要工作流程如下所示:

Stub: 被称为 ”存根“,保存在客户端,即代理模式中出现的代理类,其负责与远程对象通信

Skeletons: 被称为 ”骨架“,保存在服务端,用于接收 Stub 发送过来的请求,调用服务端对象的方法,并将返回值发送给 Stub

15.3.3 Java RMI Server

Java RMI 服务端主要是构建一个可以通过网络传输的结果类和一个可以被远程访问的目标类,同时需要将该目标类的实例对象注册到 RMI Registry

// 自定义远程接口, 需要继承自 Remote
public interface RemoteObject extends Remote {
public String remoteHello() throws RemoteException;
} /**
* 这个例子没用构建结果类,直接使用 String 类(可序列化)
* 如果要自定义对象结果类,只需要实现 Serializable 接口即可
*/
public class RemoteObjectImpl extends UnicastRemoteObject implements RemoteObject {
// 空的构造方法,不能使用默认构造,因为需要抛出异常
protected RemoteObjectImpl() throws RemoteException {} public String remoteHello() throws RemoteException() {
// 业务逻辑代码
// return String 对象
}
} // 注册
public class RMIServer {
public static void main(String[] args) throws RemoteException, AlreadyBoundException, MalformedURLException {
try {
Registry registry = LocateRegistry.createRegistry(1900); //实例化远程对象类
RemoteObject remoteObject = new RemoteObjectImpl();
//通过 Naming.bind()方法绑定别名与远程对象
Naming.bind("rmi://127.0.0.1:1900/remoteService", remoteObject);
}
catch(Exception e) {
e.printStackTrace();
}
}
}

15.3.4 Java RMI Client

public class RMIClient {
public static void main(String[] args) throws RemoteException, NotBoundException {
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1900); //打印注册中心的远程对象列表
System.out.println(Arrays.toString(registry.list())); //通过别名获取远程对象代理 stub 并调用远程对象的方法
RemoteObject stub = (RemoteObject) registry.lookup("remoteService");
System.out.println(stub.remoteHello());
}
}

15.4 动态代理

在传统的代理模式中,客户端通过代理对象调用真实主题中的方法。这种情况下,代理类和真实主题类都事先存在,且代理方法也明确指定。每个代理类经过编译之后生成字节码文件,其实现的接口和代理的方法均被固定,这种代理模式被称为静态代理

静态代理扩展非常不便,无论是需要给不同的主题增加代理还是给同一主题代理不同方法,都需要增加新的代理类,或者修改原代码(不符合开闭原则)。

动态代理(Dynamic Proxy)技术可以让系统在运行时根据需要动态创建代理类,使同一个代理类能够代理多个不同的真实主题类和不同方法。

15.4.1 动态代理实现

  1. 构建两个不同的真实主题接口

    public interface SubjectA {
    public Boolean proxyMethod1(String s); public String proxyMethod2(String s);
    } public interface SubjectB {
    public String proxyMethod1(String s);
    }
  2. 构建具体主题类

    public class ConcreteSubjectA implements SubjectA{
    @Override
    public Boolean proxyMethod1(String s) {
    // 业务逻辑代码
    return true;
    } @Override
    public String proxyMethod2(String s) {
    String res = s + "Call SubjectA proxy method2 success!";
    return res;
    }
    } public class ConcreteSubjectB implements SubjectB{
    @Override
    public String proxyMethod1(String s) {
    String res = s + "Call SubjectB proxy method1 success!";
    return res;
    }
    }
  3. 构建请求处理程序,需要实现 InvocationHandler 接口

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method; public class DynamicProxyHandler implements InvocationHandler{
    // object: 需要动态创建的真实主题对象
    private Object object; public DynamicProxyHandler() {} public DynamicProxyHandler(Object object) {
    this.object = object;
    } /**
    * 实现 InvocationHandler 的 invoke() 方法
    * @param proxy: 代理对象
    * @param method: 代理方法
    * @param args: 代理方法参数列表
    */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    beforeInvoke(); 调用真实主题中定义的方法
    Object res = method.invoke(object, args); afterInvoke();
    return res;
    } // 织入一些调用前和调用后的处理逻辑, 可以为客户端提供额外的服务
    public void beforeInvoke() {
    // 真实主题代理方法调用前
    // 逻辑代码
    } public void afterInvoke() {
    // 真实主题代理方法调用后
    // 逻辑代码
    }
    }
  4. 客户端调用

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy; public class Client {
    public static void main(String[] args) {
    InvocationHandler invocationHandler = null; SubjectA subjectA = new ConcreteSubjectA();
    SubjectB subjectB = new ConcreteSubjectB(); // 创建请求转发到的处理程序
    invocationHandler = new DynamicProxyHandler(subjectA);
    SubjectA proxyA = null; /**
    * 通过反射创建代理类实例对象
    * @param loader: 代理类的加载器
    * @param interfaces: 代理类所实现的接口列表
    * @param handler: 客户端请求转发到的处理程序
    */
    proxyA = (SubjectA)Proxy.newProxyInstance(SubjectA.class.getClassLoader(), new Class[]{SubjectA.class}, invocationHandler);
    String res = proxyA.proxyMethod2("Hi ");
    System.out.println(res); invocationHandler = new DynamicProxyHandler(subjectB);
    SubjectB proxyB = null;
    proxyB = (SubjectB)Proxy.newProxyInstance(SubjectB.class.getClassLoader(), new Class[]{SubjectB.class}, invocationHandler);
    res = proxyB.proxyMethod1("Hi ");
    System.out.println(res);
    }
    }

15.5 代理模式优/缺点

代理模式使用场景:Java RMI (远程代理), Spring AOP(动态代理)

代理模式的优点主要如下:

  • 客户端可以针对抽象层编程,增加和更换代理类不需要修改原代码,符合开闭原则
  • 远程代理为位于不同地址空间对象的访问提供实现机制,将耗时耗资源的操作移至性能更好的远程主机,提高系统的运行效率
  • 动态代理让系统根据需求动态创建代理类,且可以让同一个代理类代理多种不同的方法,相较于静态代理,显著降低了系统中类的个数

代理模式的缺点主要如下:

  • 一些代理模式可能会需要复杂的实现过程,如远程代理
  • 一些代理模式可能会造成请求的处理速度变慢,如保护代理

Java设计模式 —— 代理模式的更多相关文章

  1. Java设计模式-代理模式之动态代理(附源代码分析)

    Java设计模式-代理模式之动态代理(附源代码分析) 动态代理概念及类图 上一篇中介绍了静态代理,动态代理跟静态代理一个最大的差别就是:动态代理是在执行时刻动态的创建出代理类及其对象. 上篇中的静态代 ...

  2. JAVA 设计模式 代理模式

    用途 代理模式 (Proxy) 为其他对象提供一种代理以控制对这个对象的访问. 代理模式是一种结构型模式. 结构

  3. Java设计模式の代理模式

    目录  代理模式 1.1.静态代理   1.2.动态代理 1.3.Cglib代理 代理模式 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是 ...

  4. Java设计模式 - 代理模式

    1.什么是代理模式: 为另一个对象提供一个替身或占位符以访问这个对象. 2.代理模式有什么好处: (1)延迟加载 当你需要从网络上面查看一张很大的图片时,你可以使用代理模式先查看它的缩略图看是否是自己 ...

  5. Java设计模式—代理模式

    代理模式(Proxy Pattern)也叫做委托模式,是一个使用率非常高的模式. 定义如下:     为其他对象提供一种代理以控制对这个对象的访问. 个人理解:        代理模式将原类进行封装, ...

  6. Java设计模式——代理模式实现及原理

    简介 Java编程的目标是实现现实不能完成的,优化现实能够完成的,是一种虚拟技术.生活中的方方面面都可以虚拟到代码中.代理模式所讲的就是现实生活中的这么一个概念:中介. 代理模式的定义:给某一个对象提 ...

  7. Java设计模式-代理模式(Proxy)

    其实每个模式名称就表明了该模式的作用,代理模式就是多一个代理类出来,替原对象进行一些操作,比如我们在租房子的时候回去找中介,为什么呢?因为你对该地区房屋的信息掌握的不够全面,希望找一个更熟悉的人去帮你 ...

  8. Java设计模式--代理模式+动态代理+CGLib代理

    静态代理 抽象主题角色:声明真实主题和代理主题的共同接口. 代理主题角色:代理主题内部含有对真实主题的引用,从而在任何时候操作真实主题对象:代理主题提供一个与真实主题相同的接口,以便在任何时候都可以代 ...

  9. Java设计模式——代理模式

    public interface People { public void work(); } public class RealPeople implements People { public v ...

  10. Java 之 设计模式——代理模式

    设计模式——代理模式 一.概述 1.代理模式 (1)真实对象:被代理的对象 (2)代理对象:代理真实对象的 (3)代理模式:代理对象代理真实对象,达到增强真实对象功能的目的 二.实现方式 1.静态代理 ...

随机推荐

  1. 2023.1.21 app后端pyinstaller启动

    1.打包后会在dist文件夹中暂时生成一个新的文件目录,点击app.exe后也是在这个暂时的文件目录下读取文件的,所以需要以下代码拷贝添加原始项目中的文件 pyinstaller -D app.py ...

  2. 在Jupyter Notebook 中输出 HTML

    在刚开始使用 Jupyter Notebook 时,我总想使输出结果更使人满意,而不是只把结果打印出来.在我知道可以用 HTML 输出之前,我是这样输出一个表格的(数据来源:软科中国大学排名). 中国 ...

  3. 11.30linux学习第十一天

    今天老刘上课,第7章收尾,第8章开了个头. 7.1.3  磁盘阵列+备份盘 RAID 10磁盘阵列中最多允许50%的硬盘设备发生故障,但是存在这样一种极端情况,即同一RAID 1磁盘阵列中的硬盘设备若 ...

  4. 【cs231n】knn作业笔记

    完成了assignment-1中knn相关内容的作业,记录一下遇到的知识点和问题 knn.ipynb的内容大致包括: 1.数据集的建立 主要是通过切片函数,如下图选取前5000张图片和其标记作为训练数 ...

  5. zsh以及oh-my-zsh的安装配置

    Oh My Zsh是一款社区驱动的命令行工具,正如它的主页上说的,Oh My Zsh 是一种生活方式.它基于zsh命令行,提供了主题配置,插件机制,已经内置的便捷操作.给我们一种全新的方式使用命令行. ...

  6. 怎样清空 DNS 缓存?

    在 Windows 下命令行执行:ipconfig /flushdns 在 macOS 下执行命令:sudo killall -HUP mDNSResponder

  7. EVE如何提升名望值

    目录 背景介绍 简介 名望值划分 军团名望值 利弊 背景介绍 ​ 玩eve将近3个星期,开着毒蜥级刷1级代理人任务感觉没有一点难度,想尽快刷3.4级代理任务,而我目前能够接到的最高代理任务也就才1级. ...

  8. dotNetCore创建Windows服务程序并安装服务

    一.创建控制台程序 二.在项目中添加新建项,选择Windows服务类型. 此时会出现一个错误提示,这是因为尚未添加windows服务控制引用造成的. 三.添加Nuget包,System.Service ...

  9. Linux 用户密码不能设置问题

    当我们有时候要更改linux账户密码时,有时候会遇到下面这种情况: Password has been already used. Choose another.passwd: Have exhaus ...

  10. 【CS231n assignment 2022】Assignment 2 - Part 2,优化器,批归一化以及层归一化

    前言 博客主页:睡晚不猿序程 首发时间:2022.7.23 最近更新时间:2022.7.23 本文由 睡晚不猿序程 原创 作者是蒻蒟本蒟,如果文章里有任何错误或者表述不清,请 tt 我,万分感谢!or ...