1、 描述: 让我们思考一下下面的代码: //Client 1. class Customer{ 2. public void someMethod(){ 3. //Create the Service Provider Instance 4. FileUtil futilObj=new FileUtil(); 5. //Access the Service 6. futilObj.writeToFile(“Some Data”); 7. } 8. } 作为
2、它实现的一部分,Customer类创建了一个FileUtil类的一个实例并且直接访问它的服务。换句话说,对于客户对象,访问FileUtil对象的方式是很直接的。它的实现可能是客户对象访问服务提供者对象最为普通的方式了。相比较,有些时候客户对象可能不直接访问服务提供者(也就是指目标对象),这种情况是由于下面的原因导致的: (1) 目标对象的位置??目标对象可能存在于同一台或者不同机器的不同地址空间。 (2) 目标对象的存在形式??目标对象可能直到他被请求服务的时候还不存在,或者对象被压缩。 (3) 特殊的行为??目标对象可以根据客户对象的访问权限接受或拒绝服务。在多线程环
3、境,一些服务提供者对象需要特殊的考虑。 在这些情况下,代理模式(Proxy Pattern)建议不要使有特殊需求的客户对象直接访问目标对象,而是使用一个单独的(分离的)对象(也就是指代理对象)为不同的客户提供通常的、直接的访问目标对象的方式。 代理对象提供和目标对象一样的接口。代理对象负责与目标对象交互的细节,代表客户对象与目标对象交互。所以客户对象不再需要处理访问目标对象的服务时的特殊需求。客户对象通过它的接口调用代理对象,代理对象直接把这些调用依次地传递给目标对象。客户对象不需要知道代理的原对象(目标对象)。代理对象隐藏了与客户对象进行交互的对象细节,如:对象是否是远程
4、的、是否初始化、是否需要特殊的权限等。换句话说,代理对象作为客户和不可访问的远程对象或推迟初始化对象之间的透明桥梁。 代理对象因使用的场景不同,代理的种类也不同。让我们来快速的浏览一下一些代理和它们的目标。 注意:表23.1列出了不同种类的代理对象,在一章中,仅讨论远程代理,其他的一些代理会在本书后面的模式中讨论。 Table 23.1: List of Different Proxy Types 代理类型 目的 远程代理 提供对在不同地址空间的远程对象的访问 缓存代理 /服务代理 为了提供能够保存目标操作经常用到的结果,
5、代理对象以存储方式保存这些结果。当客户对象请求同一个操作时,代理不需要直接访问目标对象,而是从存贮介质返回操作结果。 防火墙代理 使用防火墙代理主要是为了保护目标对象以防止有害客户的访问。同时也可以防止客户访问有害的目标对象。 保护代理 提供了不同客户访问不同层次的目标对象的功能。 在创建代理时,定义了一个权限的集合。虽后,这些权限用来限制访问代理的特定部分,如果没有执行方法的权限,客户对象不允许访问特定的方法。 同步代理 提供了允许不同的客户对象安全的同步访问目标对象的功能。 计数代理 在执行目标对象的方法前,提供了一些审计机制。 代理模式和其他模式 从
6、讨论不同的代理对象中可以看出:代理对象有两个主要的特征: (1) 它介于客户对象和目标对象之间。 (2) 它接受客户对象的调用,然后转发调用给目标对象。 在这种情形下,看上去和本书中前面讨论的其他模式有些相似。让我们讨论一下代理模式和一些与它相似的模式之间的相同点和不同点。 代理模式和装饰模式 : 代理模式: (1) 客户对象不能直接访问目标对象 (2) 代理对象提供了对目标对象的访问控制(在保护代理中) (3) 代理对象不能再增加其他的功能。 装饰模式: (1) 如果需要,客户对象不能直接访问目标对象。 (2) 装饰对象不能控
7、制对目标对象的访问。 (3) 装饰对象可以增加额外的功能。 代理模式和外观模式: 代理模式: (1) 代理对象代表一个单一对象。 (2) 客户对象不能直接访问目标对象。 (3) 代理对象提供了对于单一目标对象的访问控制。 外观模式: (1) 外观对象代表了对象的一个子系统。 (2) 如果必要,客户对象可以直接访问子系统中的对象。 (3) 一个外观对象提供了一个对子系统组件的简单的、高层次的接口. 代理模式和责任链模式: 代理模式: (1) 代理对象代表了一个单一的对象。 (2) 克辉请求首先被代理对象所接
8、受,但是不直接被代理对象处理。 (3) 客户请求总是被传递给目标对象。 (4) 假设客户与服务器正常工作,可以保证请求会得到响应, 责任链模式: (1) 责任链包括很多对象。 (2) 接受客户请求的对象首先处理请求。 (3) 近当现在的接收者不能处理请求时,客户请求才被传递给下一个对象。 (4) 不能保证请求会得到响应。也就是请求已经到达责任链尾,担仍然没有被处理。 在Java中,远程方法调用(RMI)充分的利用了远程代理模式,让我们快速的浏览一下远程方法调用(RMI)的概念和远程方法调用(RMI)通信过程应用的组件。 RMI:快速浏览
9、 RMI使客户对象像访问本地对象一样访问远程对象并调用其方法成为可能。(如图23.1) Figure 23.1: Client’s View of Its Communication with a Remote Object Using RMI 下面是为实现RMI功能而一起协作的不同组件。 (1) 远程接口(Remote Interface)??一个远程对象必须实现一个远程接口(这个接口扩展java.rmi.Remote)。远程接口声明可以被客户访问的远程对象的方法。换句话说,远程接口可以看成远程对象对客户的视图。 需求(要求): 1) 扩展java.rmi.R
10、emote 2) 在远程接口中定义的所有方法必须声明抛出java.rmi.RemoteException异常。 (2) 远程对象(Remote Object)??远程对象负责实现在远程接口中定义的方法。 需求(要求): 1) 必须提供远程接口的实现。 2) 必须扩展java.rmi.server.UnicastRemoteObject类。 3) 必须有一个没有参数的构造函数。 4) 必须与一个服务器相关联。通过调用零参数的构造函数,服务器创建远程对象的一个实例。 (3) RMI注册表(RMI Registry)??RMI注册表提供了保持
11、不同远程对象的地址空间。 1) 远程对象需要存储在一个客户可以通过命名引用(Name reference)来访问它的RMI注册表中。 2) 一个给定的命名引用仅可以存储一个对象。 (4) 客户(Client)??客户是一个试图访问远程对象的应用程序。 1) 必须可以感知被远程对象实现的接口。 2) 通过命名引用(Name reference)在RMI注册表中可以查到远程对象。一旦查到远程对象的引用,调用这个引用上的方法。 (5)RMIC (Java RMI 桩编译器)Java RMI stub compiler??一旦远程对象编译成功,RMIC(Jav
12、a RMI 桩编译器)可以生成远程对象的桩类文件(stub)和框架类文件(skeleton)。桩类文件(stub)和框架类文件(skeleton)从编译的远程对象类中产生。这些桩类文件(stub)和框架类文件(skeleton) 使客户对象以无缝的方式访问远程对象成为可能。 下面这部分描述客户对象和远程对象如何通信。 RMI通信机制: 一般地,客户对象不能按通常方式直接访问远程对象。为了使客户对象像访问本地对象一样访问远程对象的服务,RMIC(Java RMI 桩编译器)生成的远程对象的桩文件(stub)和远程接口需要拷贝到客户机器上。 桩文件(stub)负责扮演着
13、远程对象的(远程)代理的角色,负责把方法的调用传递给真实的远程对象实现所在的远程服务器上。任何时候,客户对象引用远程对象,这个引用实际上是远程对象的本地桩文件。也就是,当客户调用远程对象上的方法时,调用首先被本地桩实例所接受,桩再将这个调用传递到远程服务器上。在服务器端,RMIC产生的远程对象的框架文件(skeleton)接受这个调用。 框架文件(skeleton)在服务器端,不需要拷贝到客户机器上。框架文件(skeleton)负责将这些调用转发到真正的远程对象的实现上。一旦远程对象执行了方法,方法返回的结果将按照反方向返回给客户。 图23.2说明了RMI通信的过程
14、 Figure 23.2: The Actual RMI Communication Process 了解更多的关于Java RMI的知识,推荐阅读RMI tutorial RMI和远程代理模式: 从RMI通信的讨论中,可以看到桩类文件扮演着远程对象的远程代理的角色。它使得客户访问远程对象就像访问本地对象一样成为可能。因此,一些使用了RMI技术的应用就已经暗含着代理模式的实现。 例子: 在讨论外观模式时,我们建立了一个简单的客户数据管理应用来验证和保存输入的客户数据。我们的设计由分别代表不同客户数据的三个类组成。 在应用外观模式以前,客户AccountMa
15、nager可以直接与子系统的三个用来验证、保存客户数据的类交互。应用外观模式,我们定义了一个CustomFacade外观对象代表客户与三个子系统类交互(如图23.3)。 Figure 23.3: Customer Data Management Application for the Local Mode of Operation?Class Association 在这个应用中,子系统组件和外观对象对于客户对象AccountManager都是本地的。现在,让我们建立这个应用的不同版本,这个版本已远程的方式运行。在远程方式下,这个应用通过运用JAVA RMI技术,访问远程对象。
16、在使应用运行在远程操作模式下的设计中,我们要把子系统组件(Account、Address和CreditCard)和外观(CustomerFacade)移到远程服务器上。这样会带来以下好处: 1) 在服务器上的对象可以被不同的客户应用所共享。客户不再需要维护这些类的本地版本,因此,成为轻型客户端(light-weighted)。 2) 可以对变化、性能和监控进行统一的集中控制。 Figure 23.4: Customer Data Management Application for the Remote Mode of Operation?Class Associati
17、on 让我们开始运用RMI技术设计远程操作模式下的客户数据管理应用。 第一步,先定义远程接口CustomerIntr:这个借口要满足: 1) 声明外观实现的方法。 2) 所有的方法声明抛出RemoteException异常。 3) 扩展java.rmi.Remote接口。 1. public interface CustomerIntr extends java.rmi. { 2. void setAddress( inAddress) throws RemoteException; 3. void setCity( inCity)
18、throws RemoteException; 4. void setState( inState) throws RemoteException; 5. void setFName( inFName) throws RemoteException; 6. void setLName( inLName) throws RemoteException; 7. void setCardType( inCardType) throws RemoteException; 8. void setCardNumber( inCardNumber) 9
19、 throws RemoteException; 10. void setCardExpDate( inCardExpDate) 11. throws RemoteException; 12. boolean saveCustomerData() throws RemoteException; 13. } 让我们重新定义CustomerFacade外观类,因为它要实现CustomerIntr远程接口。不同的客户对象通过CustomerIntr接口在具体类CustomerFacade上的实现与子系统对象进行交互。图23.5展示了
20、CustomerFacade和它实现的远程接口CustomerIntr之间的结构和关联。 Listing 23.1: CustomerFacade Class?Revised 1. public class CustomerFacade extends UnicastRemoteObject 2. implements CustomerIntr { 3. private address; 4. private city; 5. private state; 6. private cardType; 7. private
21、 cardNumber; 8. private cardExpDate; 9. private fname; 10. private lname; 11. public CustomerFacade() throws RemoteException { 12. super(); 13. .out.println("Server object created"); 14. } 15. public static void main( [] args) throws { 16. port
22、 = "1099"; 17. host = "localhost"; 18. //Check for hostname argument 19. if (args.length == 1) { 20. host = args[0]; 21. } 22. if (args.length == 2) { 23. port = args[1]; 24. } 25. if (.getSecurityManager() == null) { 26. .
23、setSecurityManager(new RMISecurityManager()); 27. } 28. //Create an instance of the server 29. CustomerFacade facade = new CustomerFacade(); 30. //Bind it with the RMI Registry 31. Naming.bind("//" + host + ":" + port + "/CustomerFacade”, 32. fac
24、ade); 33. .out.println("Service Bound…"); 34. } 35. public void setAddress( inAddress) 36. throws RemoteException { 37. address = inAddress; 38. } 39. public void setCity( inCity) 40. throws RemoteException{ city = inCity; 41. } 42. public vo
25、id setState( inState) 43. throws RemoteException{ state = inState; 44. } 45. public void setFName( inFName) 46. throws RemoteException{ fname = inFName; 47. } 48. public void setLName( inLName) 49. throws RemoteException{ lname = inLName; 50. } 51.
26、public void setCardType( inCardType) 52. throws RemoteException { 53. cardType = inCardType; 54. } 55. public void setCardNumber( inCardNumber) 56. throws RemoteException { 57. cardNumber = inCardNumber; 58. } 59. public void setCardExpDate( inCardExpD
27、ate) 60. throws RemoteException { 61. cardExpDate = inCardExpDate; 62. } 63. public boolean saveCustomerData() throws RemoteException{ 64. Address objAddress; 65. Account objAccount; 66. CreditCard objCreditCard; 67. /* 68. client is transp
28、arent from the following 69. set of subsystem related operations. 70. */ 71. boolean validData = true; 72. errorMessage = ""; 73. objAccount = new Account(fname, lname); 74. if (objAccount.isValid() == false) { 75. validData = false; 76.
29、errorMessage = "Invalid FirstName/LastName"; 77. } 78. objAddress = new Address(address, city, state); 79. if (objAddress.isValid() == false) { 80. validData = false; 81. errorMessage = "Invalid Address/City/State"; 82. } 83. objCreditCard = new C
30、reditCard(cardType, cardNumber, 84. cardExpDate); 85. if (objCreditCard.isValid() == false) { 86. validData = false; 87. errorMessage = "Invalid CreditCard Info"; 88. } 89. if (!validData) { 90. .out.println(errorMessage); 91.
31、 return false; 92. } 93. if (objAddress.save() && objAccount.save() && 94. objCreditCard.save()) { 95. return true; 96. } else { 97. return false; 98. } 99. } 100. } Figure 23.5: Façade Design?Remote Mode of Operation
32、 因为子系统组件对于CustomerFacade类是本地的,子系统组件初始化、方法调用的方式上没有任何变化,子系统组件对于CustomerFacade类仍然是本地对象。当执行的时候,CustomerFacade自己创建一个实例并把引用名称(reference name)保存在RMI注册表中。客户对象通过引用名称能取得远程对象的一个拷贝。 因为客户不需要直接访问任何的子系统组件。所以在远程操作模式下的设计中,不需要对子系统的任何组件进行任何的修改。 让我们重新设计客户类AccountManager: Listing 23.2: AccountManager Class?Rev
33、ised 1. … 2. … 3. public void actionPerformed(ActionEvent e) { 4. … 5. … 6. if (e.getActionCommand().equals( 7. AccountManager.VALIDATE_SAVE)) { 8. //get input values 9. firstName = objAccountManager.getFirstName(); 1
34、0. lastName = objAccountManager.getLastName(); 11. address = objAccountManager.getAddress(); 12. … 13. … 14. try { 15. //Call registry for AddOperation 16. facade = (CustomerIntr) Naming.lookup ("rmi://" + 17. objAccount
35、Manager.getRMIHost() + ":" + 18. objAccountManager.getRMIPort() + 19. "/CustomerFacade"); 20. facade.setFName(firstName); 21. facade.setLName(lastName); 22. facade.setAddress(address); 23. … 24. … 25. //C
36、lient is not required to access subsystem components. 26. boolean result = facade.saveCustomerData(); 27. if (result) { 28. validateCheckResult = 29. " Valid Customer Data: Data Saved Successfully "; 30. } else { 31. validateCheckResult
37、 32. " Invalid Customer Data: Data Could Not Be Saved "; 33. } 34. } catch ( ex) { 35. .out.println( 36. "Error: Please check to ensure the " + 37. "remote server is running" + 38. ex.getMessage()); 39. } 40.
38、 objAccountManager.setResultDisplay( 41. validateCheckResult); 42. } 43. } 和本地运行模式相似,AccountManager显示了必要的接受输入客户数据的用户界面,(如图23.6)当用户输入数据点击Validate&Save按钮时,它会在RMI注册表中通过引用名称取得远程对象的引用。 Figure 23.6: The User Interface?Remote Mode of Operation 一旦从RMI注册表中取得了远程
39、对象的引用,客户就像调用本地操作一样调用远程对象上的操作。图23.7解释了这个行为。 Figure 23.7: AccountManager View of Its Communication with the Remote CustomerFacade 注意:在运行程序以前,编译CustomerFacade类产生桩类文件必须拷贝到客户类AccountManager所在的位置。编译CustomerFacade类以后,使用RMIC编译CustomerFacade类文件,产生桩类文件(stub)和框架类文件(skeleton)。编译和部署应用程序不同部分的具体指导在后面的“
40、附加说明”部分。 实际上,当客户调用远程对象CustomerFacade上的类似saveCustomerData方法时,在客户本地的CustomerFacade_stub对象首先接受这个调用,然后CustomerFacade_stub把这个调用传递给等待处理的服务器。 在服务器端的CustomerFacade_skel负责接收通过低层次的网络通信传递的方法的调用。然后,它分发这个调用给服务器上的真实的CustomerFacade对象。例如:saveCustomerData方法,CustomerFacade对象创建必要的子系统的对象,调用这些对象上的验证和保存客户数据的方法。处理的结果以相反的方式带回给客户端。图23.8说明了真实的通信机制。 Figure 23.8: The Actual Flow of Communication 从上面可以看到,CustomerFacade_stub类能使客户对象像调用本地对象一样调用用通常的方式不能调用的远程对象的方法。在这里桩的作用就是远程代理。






