一.什么是代理模式?

顾名思义,代理就是第三方,比如明星的经纪人,明星的事务都交给经纪人来处理,明星只要告诉经纪人去做什么,经纪人自然会想办法去做,做完之后再把结果告诉明星就好了

本来是调用者与被调用者之间的直接交互,现在把调用者与被调用者分离开,由代理负责传递信息来完成调用

二.代理模式有什么用?

代理模式是一个很大的模式,所以应用很广泛,从代理的种类就能看出来了:

远程代理:最经典的代理模式之一,远程代理负责与远程JVM通信,以实现本地调用者与远程被调用者之间的正常交互

虚拟代理:用来代替巨大对象,确保它在需要的时候才被创建

保护代理:给被调用者提供访问控制,确认调用者的权限

此外还有防火墙代理,智能引用代理,缓存代理,同步代理,复杂隐藏代理,写入时复制代理等等,都有各自特殊的用途

P.S.远程代理是最基础的代理模式,有必要单独拿出来说说,所以本文对其作以详细解释,其余代理会在补充的博文中详述

三.远程代理

有些事情不用代理也能轻松解决,但有些事情必须得依靠代理来完成,比如要调用另一台机器上的一个方法,我们可能就不得不用代理

远程代理的内部机制是这样的:

解释一下,Stub是“桩”也有人称之为“存根”,代表了Server对象

Skeleton是“骨架”(不知道为什么叫“桩”和“骨架”,当然,也没必要知道),代表了Client

Stub明明在客户那边,为什么不是客户的代理而是服务的代理?因为客户是要与服务器交互,现在服务在远程JVM中,无法交互,所以用Stub来代表Server,调用Stub就等同于调用Server(内部通信机制对Client透明,对Client来说,调用Stub和直接调用Server没什么区别,而这正是代理模式的优点之一)

具体流程是这样的:

  1. Client向Stub发送方法调用请求(Client以为Stub就是Server)
  2. Stub接到请求,通过Socket与服务端的Skeleton通信,把调用请求传递给Skeleton
  3. Skeleton接到请求,调用本地Server(听起来有点奇怪,这里Server相当于Service)
  4. Server作出对应动作,把结果返回给调用者Skeleton
  5. Skeleton接到结果之后通过Socket发送给Stub
  6. Stub把结果传递给Client

P.S.第2步与第5步都需要通过Socket通信,相互传递的东西都必须在发送前序列化,接收后反序列化,这也就解释了为什么Server中的public方法返回值都必须是可序列化的

四.远程代理的实现

有两种方式可以实现远程代理:

  • 自定义Stub与Skeleton(实现其内部通信)
  • 利用Java支持的RMI(Remote Method Invocation)来实现,可以省去很多麻烦,但不容易弄明白内部原理

首先给出自定义方式的例子,有一篇关于这个的博文很不错,就擅自记录下了链接,点我跳转>>

原文给出了一个完整的例子,因此这里不再赘述,给原文补充一个伪类图,方便理解:

(不要问我类图为什么这么画,说了是“伪”类图。。)

仔细看看原文的话不难理解远程代理,Stub和Skeleton负责通信,类似于用Socket编写的聊天程序,除此之外没什么特别的

-------

下面给出利用Java支持的RMI来实现代理模式,能够明显的感受到隐藏了很多细节

首先要定义远程接口:

  1. package ProxyPattern;
  2.  
  3. import java.rmi.RemoteException;
  4.  
  5. /**
  6. * 定义服务接口(扩展自java.rmi.Remote接口)
  7. * @author ayqy
  8. */
  9. public interface Service extends java.rmi.Remote{
  10. /* 1.方法返回类型必须是可序列化的Serializable
  11. * 2.每一个方法都要声明异常throws RemoteException(因为是RMI方式)
  12. * */
  13.  
  14. /**
  15. * @return 完整的问候语句
  16. * @throws RemoteException
  17. */
  18. public String greet(String name) throws RemoteException;
  19. }

注意:服务接口中public方法的返回类型必须是可序列化的(换言之,自定义的返回类型必须实现Serializable接口),而String类型已经实现了Serializable接口

为什么要定义这样一个扩展自java.rmi.Remote的接口?API文档中给出了清晰的解释:

The Remote interface serves to identify interfaces whose methods may be invoked from a non-local virtual machine. Any object that is a remote object must directly or indirectly implement this interface. Only those methods specified in a "remote interface", an interface that extends java.rmi.Remote are available remotely.

说白了就是为了告诉编译器:我们的Service对象可以被远程调用,仅此而已

-------

定义好了远程接口,当然还需要一个具体实现:

  1. package ProxyPattern;
  2.  
  3. import java.rmi.RemoteException;
  4. import java.rmi.server.UnicastRemoteObject;
  5.  
  6. /**
  7. * 实现远程服务(扩展自UnicastRemoteObject并实现自定义远程接口)
  8. * @author ayqy
  9. */
  10. public class MyService extends UnicastRemoteObject implements Service{
  11.  
  12. /**
  13. * 用来校验程序版本(接收端在反序列化是会验证UID,不符则引发异常)
  14. */
  15. private static final long serialVersionUID = 1L;
  16.  
  17. /**
  18. * 空的构造方法,只是为了声明异常(默认的构造方法不会声明异常)
  19. * @throws RemoteException
  20. */
  21. protected MyService() throws RemoteException {
  22. }
  23.  
  24. @Override
  25. public String greet(String name) throws RemoteException {
  26. return "Hey, " + name;
  27. }
  28. }

P.S.服务继承UnicastRemoteObject类是为了自动生成Stub类(UnicastRemoteObject封装了具体生成细节,我们省去了一个类的工作量)

-------

服务端有了服务还不够,我们需要一个Server帮助我们启动RMI注册服务,并注册远程对象,供客户端调用:

  1. package ProxyPattern;
  2.  
  3. import java.net.MalformedURLException;
  4. import java.rmi.Naming;
  5. import java.rmi.RemoteException;
  6. import java.rmi.registry.LocateRegistry;
  7.  
  8. /**
  9. * 实现服务器类,负责开启服务并注册服务对象
  10. * @author ayqy
  11. */
  12. public class Server {
  13. public static void main(String[] args){
  14. try {
  15. //启动RMI注册服务,指定端口为1099 (1099为默认端口)
  16. LocateRegistry.createRegistry(1099);
  17. //创建服务对象
  18. MyService service = new MyService();
  19. //把service注册到RMI注册服务器上,命名为MyService
  20. Naming.rebind("MyService", service);
  21. } catch (RemoteException e) {
  22. // TODO Auto-generated catch block
  23. e.printStackTrace();
  24. } catch (MalformedURLException e) {
  25. // TODO Auto-generated catch block
  26. e.printStackTrace();
  27. }
  28. }
  29. }

注意,除了使用LocateRegistry.createRegistry()方式开启服务外,还可以用命令行方式开启(rmiregistry命令),效果一样

服务端代码到这里就完成了,就像Socket聊天程序一样,我们需要写两部分代码,Server与Client

-------

下面开始做客户端的实现,非常简单,就像一个测试类:

  1. package ProxyPattern;
  2.  
  3. /* 参考资料:
  4. * 1.JAVA RMI怎么用
  5. * http://blog.csdn.net/afterrain/article/details/1819659
  6. * 2.RMI内部原理
  7. * http://www.cnblogs.com/yin-jingyu/archive/2012/06/14/2549361.html
  8. * */
  9. import java.rmi.Naming;
  10.  
  11. /**
  12. * 实现客户类
  13. * @author ayqy
  14. */
  15. public class Client {
  16. /**
  17. * 查找远程对象并调用远程方法
  18. */
  19. public static void main(String[] argv)
  20. {
  21. try
  22. {
  23. //如果要从另一台启动了RMI注册服务的机器上查找MyService对象,修改IP地址即可
  24. Service service = (Service) Naming.lookup("//127.0.0.1:1099/MyService");
  25.  
  26. //调用远程方法
  27. System.out.println(service.greet("SmileStone"));
  28. }
  29. catch (Exception e)
  30. {
  31. System.out.println("Client exception: " + e);
  32. }
  33. }
  34. }

P.S.我们直接用Naming.lookup()来获取Stub对象(没错,是Stub,真正的对象还在另一台机器上呢,当然拿不到,这里得到的只是Service的Stub代理),再调用代理的方法获取结果

注意这个细节:

  1. //如果要从另一台启动了RMI注册服务的机器上查找MyService对象,修改IP地址即可
  2. Service service = (Service) Naming.lookup("//127.0.0.1:1099/MyService");

虽然只是一句话,但隐藏了两个细节:

  1. 客户端必须知道服务接口Service,这里由于是在本地同一个package下,所以不用关心,在真正应用中Client与Server是分离的,所以Client需要拿到一份Service接口的Copy,否则无法调用
  2. 客户端必须知道服务器的IP和端口号(通信嘛,没有这个可不行)

-------

忙活了半天了,看看运行结果(先运行Server,再运行Client):

P.S.利用Java支持的RMI来实现远程代理部分,参考的资料是别人的一篇博文,里面解释的更详细一些

P.S.至于命令行方式启动RMI注册服务,太麻烦了,而且需要先生成Stub类,不建议用这种方式,具体操作细节,上面的链接博文中也有详细介绍,不再赘述

注意:直到我们亲眼看到测试结果为止,始终没有自定义Stub与Skeleton类,不是吗?对,没错,它们确实存在,只是被RMI隐藏起来了(据说Java1.5之后RMI中没有了Skeleton,甚至更高的版本中连Stub都没有了。。不过这都没关系,我们已经清楚了最原始的远程代理)

五.总结

回过头去想一想远程代理做了些什么:

  1. 拦截并控制方法调用(这也是代理模式最大的特点,最典型的,防火墙代理。。)
  2. 远程对象的存在对客户是透明的(客户完全把Stub代理对象当做远程对象了,虽然客户有点好奇为什么可能会出现异常。。)
  3. 远程代理隐藏了通信细节

当我们需要调用另一台机器(JVM)上指定对象的方法时,使用远程代理是一个不错的选择。。

设计模式之代理模式(Proxy Pattern)_远程代理解析的更多相关文章

  1. 设计模式 - 代理模式(proxy pattern) 未使用代理模式 具体解释

    代理模式(proxy pattern) 未使用代理模式 详细解释 本文地址: http://blog.csdn.net/caroline_wendy 部分代码參考: http://blog.csdn. ...

  2. 设计模式系列之代理模式(Proxy Pattern)——对象的间接访问

    说明:设计模式系列文章是读刘伟所著<设计模式的艺术之道(软件开发人员内功修炼之道)>一书的阅读笔记.个人感觉这本书讲的不错,有兴趣推荐读一读.详细内容也可以看看此书作者的博客https:/ ...

  3. 乐在其中设计模式(C#) - 代理模式(Proxy Pattern)

    原文:乐在其中设计模式(C#) - 代理模式(Proxy Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 代理模式(Proxy Pattern) 作者:webabcd 介绍 为 ...

  4. 二十四种设计模式:代理模式(Proxy Pattern)

    代理模式(Proxy Pattern) 介绍为其他对象提供一个代理以控制对这个对象的访问. 示例有一个Message实体类,某对象对它的操作有Insert()和Get()方法,用一个代理来控制对这个对 ...

  5. 代理模式(Proxy pattern)

    代理模式(proxy pattern):作用:为其他对象提供一种代理,以控制对这个对象的访问.代理对象在客户端对象和目标对象之间起中介的作用. 代理模式涉及到的角色: 抽象角色:声明真实对象和代理对象 ...

  6. 设计模式(十三):从“FQ”中来认识代理模式(Proxy Pattern)

    我们知道Google早就被墙了,所以FQ才能访问Google呢,这个“FQ”的过程就是一个代理的过程.“代理模式”在之前的博客中不止一次的提及过,之前的委托回调就是代理模式的具体应用.今天我们就从“F ...

  7. 设计模式——代理模式(Proxy Pattern)

    代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问. UML图: 模型设计: Subject类: package com.cnblog.clarck; /** * Subject 类 ...

  8. 13.代理模式(Proxy Pattern)

    using System; namespace Test { //抽象角色:声明真实对象和代理对象的共同接口. //代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象, //同时代理 ...

  9. 你管这叫代理模式(Proxy Pattern)

    代理模式   代理模式即给一个真实类提供一个代理类,该代理类代替真实类完成其功能,一般还可在代理类上添加一些真实类不具有的附加功能,通俗来讲代理模式就是我们生活中常见的中介,代理模式又可分为静态代理和 ...

  10. C#设计模式——代理模式(Proxy Pattern)

    一.概述在软件开发中,有些对象由于创建成本高.访问时需要与其它进程交互等原因,直接访问会造成系统速度慢.复杂度增大等问题.这时可以使用代理模式,给系统增加一层间接层,通过间接层访问对象,从而达到隐藏系 ...

随机推荐

  1. datagrid数据表格的维护

    想想刚开始学jsp, 用application做一个简单的数据库, 简单的注册页面, 跟这个相比就是过家家 <%@ page language="java" contentT ...

  2. nyoj743-复杂度 【排列组合】

    http://acm.nyist.net/JudgeOnline/problem.php?pid=743 复杂度 时间限制:1000 ms  |  内存限制:65535 KB 难度:3   描述 fo ...

  3. np.random.random()系列函数

    1.np.random.random()函数参数 np.random.random((1000, 20)) 上面这个就代表生成1000行 20列的浮点数,浮点数都是从0-1中随机. 2.numpy.r ...

  4. OC线程操作-GCD介绍

    1. GCD介绍 1.11.2 1.3 异步具备开启能力但是不是 一定可以开启 1.4 1.5 67. 8.

  5. Spring集成Redis使用注解

    转载:http://blog.csdn.net/u013725455/article/details/52129283 使用Maven项目,添加jar文件依赖: <project xmlns=& ...

  6. ORA-22858: 数据类型的变更无效 varchar2类型转换为clob类型

    今天遇到varchar2类型数据不够大,需改为clob类型.Oracle中,如果一个列的类型为varchar2,那么它不能直接转换为clob类型.可以通过间接的方式来修改. 就是把原来的字段删掉,重新 ...

  7. 【转】MEF程序设计指南五:迟延(Lazy)加载导出部件(Export Part)与元数据(Metadata)

    MEF中使用导出与导入,实质上就是对一个对象的实例化的过程,通过MEF的特性降低了对象的直接依赖,从而让系统的设计达到一种高灵活.高扩展性的效果.在具体的设计开发中,存在着某些对象是不需要在系统运行或 ...

  8. 对FPKM/RPKM以及TPM的理解

    对FPKM/RPKM以及TPM的理解 2018年07月03日 16:05:53 sixu_9days 阅读数:559 标签: FPKM/RPKMTPMRNA-Seq 更多 个人分类: RNA-Seq ...

  9. [BAT] 执行xcopy命令后出现Invalid num of parameters错误的解决办法

    如果是Windows下的命令行,对于有空格的文件路径要加引号,对于xcopy命令就是源路径和目标路径都要加引号 xcopy "C:\ppt" "D:\Program do ...

  10. [SoapUI] 通过context获取response并解析里面的某个字段的值

    import com.eviware.soapui.support.GroovyUtils def groovyUtils = new GroovyUtils( context ) def realI ...