本文是翻譯《SoftwareArchitectureDesignPatternsinJava》一書,我將陸續為大家翻譯其他章節,希望大家支持指正!第23章:代理模式(ProxyPattern)

描述:

讓我們思考一下下面的代碼://Client

  1. classCustomer{
  2. publicvoidsomeMethod(){
  3. //CreatetheServiceProviderInstance
  4. FileUtilfutilObj=newFileUtil();
  5. //AccesstheService
  6. futilObj.writeToFile(「SomeData」);
  7. }
  8. }

作為它實現的一部分,Customer類創建了一個FileUtil類的一個實例並且直接訪問它的服務。換句話說,對於客戶對象,訪問FileUtil對象的方式是很直接的。它的實現可能是客戶對象訪問服務提供者對象最為普通的方式了。相比較,有些時候客戶對象可能不直接訪問服務提供者(也就是指目標對象),這種情況是由於下面的原因導致的:(1)目標對象的位置??目標對象可能存在於同一臺或者不同機器的不同地址空間。(2)目標對象的存在形式??目標對象可能直到他被請求服務的時候還不存在,或者對象被壓縮。(3)特殊的行為??目標對象可以根據客戶對象的訪問許可權接受或拒絕服務。在多線程環境,一些服務提供者對象需要特殊的考慮。在這些情況下,代理模式(ProxyPattern)建議不要使有特殊需求的客戶對象直接訪問目標對象,而是使用一個單獨的(分離的)對象(也就是指代理對象)為不同的客戶提供通常的、直接的訪問目標對象的方式。代理對象提供和目標對象一樣的介面。代理對象負責與目標對象交互的細節,代表客戶對象與目標對象交互。所以客戶對象不再需要處理訪問目標對象的服務時的特殊需求。客戶對象通過它的介面調用代理對象,代理對象直接把這些調用依次地傳遞給目標對象。客戶對象不需要知道代理的原對象(目標對象)。代理對象隱藏了與客戶對象進行交互的對象細節,如:對象是否是遠程的、是否初始化、是否需要特殊的許可權等。換句話說,代理對象作為客戶和不可訪問的遠程對象或推遲初始化對象之間的透明橋樑。代理對象因使用的場景不同,代理的種類也不同。讓我們來快速的瀏覽一下一些代理和它們的目標。注意:表23.1列出了不同種類的代理對象,在一章中,僅討論遠程代理,其他的一些代理會在本書後面的模式中討論。

Table23.1:ListofDifferentProxyTypes代理類型目的遠程代理提供對在不同地址空間的遠程對象的訪問緩存代理/服務代理為了提供能夠保存目標操作經常用到的結果,代理對象以存儲方式保存這些結果。當客戶對象請求同一個操作時,代理不需要直接訪問目標對象,而是從存貯介質返回操作結果。防火牆代理使用防火牆代理主要是為了保護目標對象以防止有害客戶的訪問。同時也可以防止客戶訪問有害的目標對象。保護代理提供了不同客戶訪問不同層次的目標對象的功能。在創建代理時,定義了一個許可權的集合。雖後,這些許可權用來限制訪問代理的特定部分,如果沒有執行方法的許可權,客戶對象不允許訪問特定的方法。同步代理提供了允許不同的客戶對象安全的同步訪問目標對象的功能。計數代理在執行目標對象的方法前,提供了一些審計機制。代理模式和其他模式從討論不同的代理對象中可以看出:代理對象有兩個主要的特徵:(1)它介於客戶對象和目標對象之間。(2)它接受客戶對象的調用,然後轉發調用給目標對象。在這種情形下,看上去和本書中前面討論的其他模式有些相似。讓我們討論一下代理模式和一些與它相似的模式之間的相同點和不同點。

代理模式和裝飾模式

:代理模式:(1)客戶對象不能直接訪問目標對象(2)代理對象提供了對目標對象的訪問控制(在保護代理中)(3)代理對象不能再增加其他的功能。裝飾模式:(1)如果需要,客戶對象不能直接訪問目標對象。(2)裝飾對象不能控制對目標對象的訪問。(3)裝飾對象可以增加額外的功能。

代理模式和外觀模式:

代理模式:(1)代理對象代表一個單一對象。(2)客戶對象不能直接訪問目標對象。(3)代理對象提供了對於單一目標對象的訪問控制。外觀模式:(1)外觀對象代表了對象的一個子系統。(2)如果必要,客戶對象可以直接訪問子系統中的對象。(3)一個外觀對象提供了一個對子系統組件的簡單的、高層次的介面.

代理模式和責任鏈模式:

代理模式:(1)代理對象代表了一個單一的對象。(2)克輝請求首先被代理對象所接受,但是不直接被代理對象處理。(3)客戶請求總是被傳遞給目標對象。(4)假設客戶與伺服器正常工作,可以保證請求會得到響應,責任鏈模式:(1)責任鏈包括很多對象。(2)接受客戶請求的對象首先處理請求。(3)近當現在的接收者不能處理請求時,客戶請求才被傳遞給下一個對象。(4)不能保證請求會得到響應。也就是請求已經到達責任鏈尾,擔仍然沒有被處理。在Java中,遠程方法調用(RMI)充分的利用了遠程代理模式,讓我們快速的瀏覽一下遠程方法調用(RMI)的概念和遠程方法調用(RMI)通信過程應用的組件。

RMI:快速瀏覽

RMI使客戶對象像訪問本地對象一樣訪問遠程對象並調用其方法成為可能。(如圖23.1)

Figure23.1:Client』sViewofItsCommunicationwithaRemoteObjectUsingRMI下面是為實現RMI功能而一起協作的不同組件。(1)遠程介面(RemoteInterface)??一個遠程對象必須實現一個遠程介面(這個介面擴展java.rmi.Remote)。遠程介面聲明可以被客戶訪問的遠程對象的方法。換句話說,遠程介面可以看成遠程對象對客戶的視圖。需求(要求):1)擴展java.rmi.Remote2)在遠程介面中定義的所有方法必須聲明拋出java.rmi.RemoteException異常。(2)遠程對象(RemoteObject)??遠程對象負責實現在遠程介面中定義的方法。需求(要求):1)必須提供遠程介面的實現。2)必須擴展java.rmi.server.UnicastRemoteObject類。3)必須有一個沒有參數的構造函數。4)必須與一個伺服器相關聯。通過調用零參數的構造函數,伺服器創建遠程對象的一個實例。(3)RMI註冊表(RMIRegistry)??RMI註冊表提供了保持不同遠程對象的地址空間。1)遠程對象需要存儲在一個客戶可以通過命名引用(Namereference)來訪問它的RMI註冊表中。2)一個給定的命名引用僅可以存儲一個對象。(4)客戶(Client)??客戶是一個試圖訪問遠程對象的應用程序。1)必須可以感知被遠程對象實現的介面。2)通過命名引用(Namereference)在RMI註冊表中可以查到遠程對象。一旦查到遠程對象的引用,調用這個引用上的方法。(5)RMIC(JavaRMI樁編譯器)JavaRMIstubcompiler??一旦遠程對象編譯成功,RMIC(JavaRMI樁編譯器)可以生成遠程對象的樁類文件(stub)和框架類文件(skeleton)。樁類文件(stub)和框架類文件(skeleton)從編譯的遠程對象類中產生。這些樁類文件(stub)和框架類文件(skeleton)使客戶對象以無縫的方式訪問遠程對象成為可能。下面這部分描述客戶對象和遠程對象如何通信。

RMI通信機制:

一般地,客戶對象不能按通常方式直接訪問遠程對象。為了使客戶對象像訪問本地對象一樣訪問遠程對象的服務,RMIC(JavaRMI樁編譯器)生成的遠程對象的樁文件(stub)和遠程介面需要拷貝到客戶機器上。樁文件(stub)負責扮演著遠程對象的(遠程)代理的角色,負責把方法的調用傳遞給真實的遠程對象實現所在的遠程伺服器上。任何時候,客戶對象引用遠程對象,這個引用實際上是遠程對象的本地樁文件。也就是,當客戶調用遠程對象上的方法時,調用首先被本地樁實例所接受,樁再將這個調用傳遞到遠程伺服器上。在伺服器端,RMIC產生的遠程對象的框架文件(skeleton)接受這個調用。框架文件(skeleton)在伺服器端,不需要拷貝到客戶機器上。框架文件(skeleton)負責將這些調用轉發到真正的遠程對象的實現上。一旦遠程對象執行了方法,方法返回的結果將按照反方向返回給客戶。圖23.2說明瞭RMI通信的過程

Figure23.2:TheActualRMICommunicationProcess瞭解更多的關於JavaRMI的知識,推薦閱讀RMItutorial

RMI和遠程代理模式:

從RMI通信的討論中,可以看到樁類文件扮演著遠程對象的遠程代理的角色。它使得客戶訪問遠程對象就像訪問本地對象一樣成為可能。因此,一些使用了RMI技術的應用就已經暗含著代理模式的實現。

例子:

在討論外觀模式時,我們建立了一個簡單的客戶數據管理應用來驗證和保存輸入的客戶數據。我們的設計由分別代表不同客戶數據的三個類組成。在應用外觀模式以前,客戶AccountManager可以直接與子系統的三個用來驗證、保存客戶數據的類交互。應用外觀模式,我們定義了一個CustomFacade外觀對象代表客戶與三個子系統類交互(如圖23.3)。

Figure23.3:CustomerDataManagementApplicationfortheLocalModeofOperation?ClassAssociation在這個應用中,子系統組件和外觀對象對於客戶對象AccountManager都是本地的。現在,讓我們建立這個應用的不同版本,這個版本已遠程的方式運行。在遠程方式下,這個應用通過運用JAVARMI技術,訪問遠程對象。在使應用運行在遠程操作模式下的設計中,我們要把子系統組件(Account、Address和CreditCard)和外觀(CustomerFacade)移到遠程伺服器上。這樣會帶來以下好處:1)在伺服器上的對象可以被不同的客戶應用所共享。客戶不再需要維護這些類的本地版本,因此,成為輕型客戶端(light-weighted)。2)可以對變化、性能和監控進行統一的集中控制。

Figure23.4:CustomerDataManagementApplicationfortheRemoteModeofOperation?ClassAssociation讓我們開始運用RMI技術設計遠程操作模式下的客戶數據管理應用。第一步,先定義遠程介面CustomerIntr:這個藉口要滿足:1)聲明外觀實現的方法。2)所有的方法聲明拋出RemoteException異常。3)擴展java.rmi.Remote介面。

  1. publicinterfaceCustomerIntrextendsjava.rmi.Remote{
  2. voidsetAddress(StringinAddress)throwsRemoteException;
  3. voidsetCity(StringinCity)throwsRemoteException;
  4. voidsetState(StringinState)throwsRemoteException;
  5. voidsetFName(StringinFName)throwsRemoteException;
  6. voidsetLName(StringinLName)throwsRemoteException;
  7. voidsetCardType(StringinCardType)throwsRemoteException;
  8. voidsetCardNumber(StringinCardNumber)
  9. throwsRemoteException;
  10. voidsetCardExpDate(StringinCardExpDate)
  11. throwsRemoteException;
  12. booleansaveCustomerData()throwsRemoteException;
  13. }

讓我們重新定義CustomerFacade外觀類,因為它要實現CustomerIntr遠程介面。不同的客戶對象通過CustomerIntr介面在具體類CustomerFacade上的實現與子系統對象進行交互。圖23.5展示了CustomerFacade和它實現的遠程介面CustomerIntr之間的結構和關聯。Listing23.1:CustomerFacadeClass?Revised

  1. publicclassCustomerFacadeextendsUnicastRemoteObject
  2. implementsCustomerIntr{
  3. privateStringaddress;
  4. privateStringcity;
  5. privateStringstate;
  6. privateStringcardType;
  7. privateStringcardNumber;
  8. privateStringcardExpDate;
  9. privateStringfname;
  10. privateStringlname;
  11. publicCustomerFacade()throwsRemoteException{
  12. super();
  13. System.out.println("Serverobjectcreated");
  14. }
  15. publicstaticvoidmain(String[]args)throwsException{
  16. Stringport="1099";
  17. Stringhost="localhost";
  18. //Checkforhostnameargument
  19. if(args.length==1){
  20. host=args[0];
  21. }
  22. if(args.length==2){
  23. port=args[1];
  24. }
  25. if(System.getSecurityManager()==null){
  26. System.setSecurityManager(newRMISecurityManager());
  27. }
  28. //Createaninstanceoftheserver
  29. CustomerFacadefacade=newCustomerFacade();
  30. //BinditwiththeRMIRegistry
  31. Naming.bind("//"+host+":"+port+"/CustomerFacade」,
  32. facade);
  33. System.out.println("ServiceBound…");
  34. }
  35. publicvoidsetAddress(StringinAddress)
  36. throwsRemoteException{
  37. address=inAddress;
  38. }
  39. publicvoidsetCity(StringinCity)
  40. throwsRemoteException{city=inCity;
  41. }
  42. publicvoidsetState(StringinState)
  43. throwsRemoteException{state=inState;
  44. }
  45. publicvoidsetFName(StringinFName)
  46. throwsRemoteException{fname=inFName;
  47. }
  48. publicvoidsetLName(StringinLName)
  49. throwsRemoteException{lname=inLName;
  50. }
  51. publicvoidsetCardType(StringinCardType)
  52. throwsRemoteException{
  53. cardType=inCardType;
  54. }
  55. publicvoidsetCardNumber(StringinCardNumber)
  56. throwsRemoteException{
  57. cardNumber=inCardNumber;
  58. }
  59. publicvoidsetCardExpDate(StringinCardExpDate)
  60. throwsRemoteException{
  61. cardExpDate=inCardExpDate;
  62. }
  63. publicbooleansaveCustomerData()throwsRemoteException{
  64. AddressobjAddress;
  65. AccountobjAccount;
  66. CreditCardobjCreditCard;
  67. /*
  68. clientistransparentfromthefollowing
  69. setofsubsystemrelatedoperations.
  70. */
  71. booleanvalidData=true;
  72. StringerrorMessage="";
  73. objAccount=newAccount(fname,lname);
  74. if(objAccount.isValid()==false){
  75. validData=false;
  76. errorMessage="InvalidFirstName/LastName";
  77. }
  78. objAddress=newAddress(address,city,state);
  79. if(objAddress.isValid()==false){
  80. validData=false;
  81. errorMessage="InvalidAddress/City/State";
  82. }
  83. objCreditCard=newCreditCard(cardType,cardNumber,
  84. cardExpDate);
  85. if(objCreditCard.isValid()==false){
  86. validData=false;
  87. errorMessage="InvalidCreditCardInfo";
  88. }
  89. if(!validData){
  90. System.out.println(errorMessage);
  91. returnfalse;
  92. }
  93. if(objAddress.save()&&objAccount.save()&&
  94. objCreditCard.save()){
  95. returntrue;
  96. }else{
  97. returnfalse;
  98. }
  99. }
  100. }

Figure23.5:Fa?adeDesign?RemoteModeofOperation因為子系統組件對於CustomerFacade類是本地的,子系統組件初始化、方法調用的方式上沒有任何變化,子系統組件對於CustomerFacade類仍然是本地對象。當執行的時候,CustomerFacade自己創建一個實例並把引用名稱(referencename)保存在RMI註冊表中。客戶對象通過引用名稱能取得遠程對象的一個拷貝。因為客戶不需要直接訪問任何的子系統組件。所以在遠程操作模式下的設計中,不需要對子系統的任何組件進行任何的修改。讓我們重新設計客戶類AccountManager:Listing23.2:AccountManagerClass?Revised

  1. publicvoidactionPerformed(ActionEvente){
  2. if(e.getActionCommand().equals(
  3. AccountManager.VALIDATE_SAVE)){
  4. //getinputvalues
  5. StringfirstName=objAccountManager.getFirstName();
  6. StringlastName=objAccountManager.getLastName();
  7. Stringaddress=objAccountManager.getAddress();
  8. try{
  9. //CallregistryforAddOperation
  10. facade=(CustomerIntr)Naming.lookup("rmi://"+
  11. objAccountManager.getRMIHost()+":"+
  12. objAccountManager.getRMIPort()+
  13. "/CustomerFacade");
  14. facade.setFName(firstName);
  15. facade.setLName(lastName);
  16. facade.setAddress(address);
  17. //Clientisnotrequiredtoaccesssubsystemcomponents.
  18. booleanresult=facade.saveCustomerData();
  19. if(result){
  20. validateCheckResult=
  21. "ValidCustomerData:DataSavedSuccessfully";
  22. }else{
  23. validateCheckResult=
  24. "InvalidCustomerData:DataCouldNotBeSaved";
  25. }
  26. }catch(Exceptionex){
  27. System.out.println(
  28. "Error:Pleasechecktoensurethe"+
  29. "remoteserverisrunning"+
  30. ex.getMessage());
  31. }
  32. objAccountManager.setResultDisplay(
  33. validateCheckResult);
  34. }
  35. }

和本地運行模式相似,AccountManager顯示了必要的接受輸入客戶數據的用戶界面,(如圖23.6)當用戶輸入數據點擊Validate&Save按鈕時,它會在RMI註冊表中通過引用名稱取得遠程對象的引用。

Figure23.6:TheUserInterface?RemoteModeofOperation一旦從RMI註冊表中取得了遠程對象的引用,客戶就像調用本地操作一樣調用遠程對象上的操作。圖23.7解釋了這個行為。

Figure23.7:AccountManagerViewofItsCommunicationwiththeRemoteCustomerFacade注意:在運行程序以前,編譯CustomerFacade類產生樁類文件必須拷貝到客戶類AccountManager所在的位置。編譯CustomerFacade類以後,使用RMIC編譯CustomerFacade類文件,產生樁類文件(stub)和框架類文件(skeleton)。編譯和部署應用程序不同部分的具體指導在後面的「附加說明」部分。實際上,當客戶調用遠程對象CustomerFacade上的類似saveCustomerData方法時,在客戶本地的CustomerFacade_stub對象首先接受這個調用,然後CustomerFacade_stub把這個調用傳遞給等待處理的伺服器。在伺服器端的CustomerFacade_skel負責接收通過低層次的網路通信傳遞的方法的調用。然後,它分發這個調用給伺服器上的真實的CustomerFacade對象。例如:saveCustomerData方法,CustomerFacade對象創建必要的子系統的對象,調用這些對象上的驗證和保存客戶數據的方法。處理的結果以相反的方式帶回給客戶端。圖23.8說明瞭真實的通信機制。

Figure23.8:TheActualFlowofCommunication從上面可以看到,CustomerFacade_stub類能使客戶對象像調用本地對象一樣調用用通常的方式不能調用的遠程對象的方法。在這裡樁的作用就是遠程代理。

附加說明

:編譯和部署的說明1)在proxy/Server目錄下,編譯所有的Java文件2)在proxy/Server目錄下,執行下面的命令:RmicCustomerFacade這個命令調用RMI樁編譯器,分別創建樁類文件(stub)CustomerFacade_stub.class和框架類文件(skeleton)CustomerFacade_skel.class。3)從proxy/Server目錄拷貝下面的文件到proxy/Client目錄:CustomerIntr.classCustomerFacade_Stub.class4)在proxy/Client目錄下,編譯所有的Java文件5)開啟rmiregistrystartrmiregistry<objectRegistryPort>(Windows)rmiregistry&(Solaris)<objectRegistryPort>?這是RMI註冊表監聽的埠,默認為1099例如:startrmiregistry6)執行下面的命令:java-Djava.security.policy=<PolicyFile>CustomerFacade<RemoteRegistryHost><RemoteRegistryPort>例如:java-Djava.security.policy=java.policyCustomerFacadelocalhost1099<policyFile>?這是許可權文件的名字。這個文件中,需要指定潛在的運行系統在文件系統的位置。注意:Thejava.policy文件在伺服器目錄下是有效的。<RemoteRegistryHost>?這是註冊的遠程對象正在運行的DNS(DomainNameSystem)域命名系統的名字後者主機的IP地址。如果是在同一臺機器上,使用「localhost」。<RemoteRegistryPort>?註冊的遠程對象監聽指定的RemoteRegistryHost埠,默認為1099。6)下面的信息會在命令行中顯示:ServerobjectcreatedServicebound…7)在proxy/Client目錄下,執行下面的命令運行客戶程序。java-Djava.security.policy=<PolicyFile>AccountManager<RemoteRegistryHost><RemoteRegistryPort>例如:java-Djava.security.policy=java.policyAccountManagerlocalhost1099<policyFile>?這是許可權文件的名字。這個文件中,需要指定潛在的運行系統在文件系統的位置。注意:Thejava.policy文件在客戶目錄下是有效的。<RemoteRegistryHost>?這是註冊的遠程對象正在運行的DNS(DomainNameSystem)域命名系統的名字後者主機的IP地址。如果是在同一臺機器上,使用「localhost」。<RemoteRegistryPort>?註冊的遠程對象監聽指定的RemoteRegistryHost埠,默認為1099。附件為本文的代碼和原文:對於代碼的說明:為了把上面介紹的複雜過程簡單化,自己定義了兩個cmd文件。下載原嗎以後,1)先修改server目錄下的java.policy文件,

  1. grant
  2. {
  3. permissionjava.net.SocketPermission"*:1024-65535",
  4. "connect,accept,resolve";
  5. permissionjava.net.SocketPermission"*:1-1023",
  6. "connect,resolve";
  7. permissionjava.io.FilePermission"[b]C:\eclipse\workspace\AU2142\27\server\*[/b]","read,write";
  8. };

修改C:\eclipse\workspace\AU2142\27\server\*為你的下載目錄,一定要對應。2)再運行server目錄下的RunServer.cmd;3)再運行client目錄下的RunClient.cmd;注意:如果client和server不是在一臺機器上,要修改RunClient.cmd和RunServer.cmd;大家一看著兩個文件,就知道怎麼修改了,地球人都知道了!哈哈!
推薦閱讀:

查看原文 >>
相關文章