本文是我们名为“ Java设计模式 ”的学院课程的一部分。
在本课程中,您将深入研究大量的设计模式,并了解如何在Java中实现和利用它们。 您将了解模式如此重要的原因,并了解何时以及如何应用模式中的每一个。 在这里查看 !
目录
- 1.简介 2.什么是代理模式 3.远程代理 4.虚拟代理 5.保护代理 6.何时使用代理模式 7.其他代理 8. JDK中的代理模式 9.下载源代码
1.简介
在本课程中,我们将讨论结构模式,即代理模式。 代理模式为另一个对象提供代理或占位符,以控制对其的访问。
代理模式提供了许多不同的变体。 一些重要的变体是远程代理,虚拟代理和保护代理。 在本课程中,我们将对这些变体有更多的了解,并将使用Java来实现它们。 但是在我们这样做之前,让我们先全面了解代理模式。
2.什么是代理模式
代理模式用于创建代表对象,该对象控制对另一个对象的访问,该对象可能是远程的,创建起来很昂贵,或者需要保护。
控制对对象的访问的一个原因是将创建和初始化的全部成本推迟到我们实际需要使用它之前。 另一个原因可能是充当驻留在不同JVM中的对象的本地代表。 代理对于控制对原始对象的访问非常有用,尤其是当对象应具有不同的访问权限时。
在代理模式中,客户端不直接与原始对象对话,而是将其调用委托给代理对象,该代理对象调用原始对象的方法。 重要的一点是客户端不了解代理,代理充当客户端的原始对象。 但是这种方法有很多变化,我们将很快看到。
让我们看一下代理模式及其重要参与者的结构。
- 代理人 :
1a。 维护一个使代理可以访问真实主题的参考。 如果RealSubject和Subject接口相同,则代理可以引用Subject。
1b。 提供与Subject相同的接口,以便可以用代理代替真实的Subject。 1c。 控制对真实主题的访问,并可能负责创建和删除它。 - 主题 :
2a。 为RealSubject和Proxy定义公共接口,以便可以在需要RealSubject的任何地方使用Proxy。 - RealSubject :
3a。 定义代理代表的真实对象。
代理模式有三个主要变体:
- 远程代理为不同地址空间中的对象提供了本地代表。
- 虚拟代理根据需要创建昂贵的对象。
- 保护代理控制对原始对象的访问。 当对象应具有不同的访问权限时,保护代理很有用。
我们将在下一部分中逐一讨论这些代理。
3.远程代理
有一家披萨公司,其在各个位置都有分店。 公司的所有者每天都会从各个网点获得公司员工的每日报告。 Pizza Company支持的当前应用程序是桌面应用程序,而不是Web应用程序。 因此,所有者必须要求其雇员生成报告并将其发送给他。 但是现在所有者希望自己生成并检查报告,以便他可以在任何人需要的情况下随时生成报告,而无需任何人的帮助。 所有者希望您为他开发一个应用程序。
这里的问题是所有应用程序都在各自的JVM上运行,而Report Checker应用程序(我们将很快设计)应在所有者的本地系统中运行。 所有者的系统JVM中不存在生成报告所需的对象,并且您不能直接调用远程对象。
远程代理用于解决此问题。 我们知道该报告是由用户生成的,因此需要一个对象来生成报告。 我们所需要做的就是联系位于远程位置的对象,以获得所需的结果。 远程代理充当远程对象的本地代表。 远程对象是驻留在不同JVM堆中的对象。 您将方法调用到本地对象,该方法将调用转移到远程对象。
您的客户端对象的行为类似于其进行远程方法调用。 但这是在处理本地所有网络通信底层细节的堆本地代理对象上调用方法。
Java支持使用RMI在位于两个不同位置(或两个不同JVM)的两个对象之间进行通信。 RMI是远程方法调用,用于构建客户端和服务帮助程序对象,直至使用与远程服务相同的方法创建客户端帮助程序对象。 使用RMI,您不必自己编写任何网络或I / O代码。 使用客户端,您可以像在客户端的本地JVM中运行的对象上的普通方法调用一样调用远程方法。
RMI还提供了运行中的基础结构以使其全部正常工作,包括客户端可以用来查找和访问远程对象的查找服务。 RMI调用和本地方法调用之间有一个区别。 客户助手通过网络发送方法调用,因此RMI调用涉及网络和I / O。
现在让我们看一下代码。 我们有一个接口ReportGenerator
及其具体实现ReportGeneratorImpl
已经在不同位置的JVM上运行。 首先要创建一个远程服务,我们需要更改代码。
ReportGenerator
接口现在将如下所示:
package com.javacodegeeks.patterns.proxypattern.remoteproxy;import java.rmi.Remote;
import java.rmi.RemoteException;public interface ReportGenerator extends Remote{public String generateDailyReport() throws RemoteException;}
这是一个远程接口,它定义客户端可以远程调用的方法。 客户端将使用它作为您的服务的类类型。 Stub和实际服务都将实现此目的。 接口中的方法返回String
对象。 您可以从该方法返回任何对象; 该对象将通过电线从服务器传送回客户端,因此它必须是Serializable
。 请注意,此接口中的所有方法都应抛出RemoteException
。
package com.javacodegeeks.patterns.proxypattern.remoteproxy;import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Date;public class ReportGeneratorImpl extends UnicastRemoteObject implements ReportGenerator{private static final long serialVersionUID = 3107413009881629428L;protected ReportGeneratorImpl() throws RemoteException {}@Overridepublic String generateDailyReport() throws RemoteException {StringBuilder sb = new StringBuilder();sb.append("********************Location X Daily Report********************");sb.append("\\n Location ID: 012");sb.append("\\n Today's Date: "+new Date());sb.append("\\n Total Pizza's Sell: 112");sb.append("\\n Total Price: $2534");sb.append("\\n Net Profit: $1985");sb.append("\\n ***************************************************************");return sb.toString();}public static void main(String[] args) {try {ReportGenerator reportGenerator = new ReportGeneratorImpl();Naming.rebind("PizzaCoRemoteGenerator", reportGenerator);} catch (Exception e) {e.printStackTrace();}}}
上面的类是完成实际工作的远程实现。 客户端希望在其上调用方法的对象。 该类扩展了UnicastRemoteObject
,为了用作远程服务对象,您的对象需要一些与远程相关的功能。 最简单的方法是从java.rmi.server
包中扩展UnicastRemoteObject
,然后让该类为您完成工作。
UnicastRemoteObject
类构造函数将引发RemoteException
,因此您需要编写一个声明了RemoteException
的无参数构造函数。
为了使远程服务可用于客户端,您需要在RMI注册表中注册该服务。 您可以通过实例化并将其放入RMI注册表来完成此操作。 注册实现对象时,RMI系统实际上将存根放在注册表中,因为这是客户端需要的。 Naming.rebind
方法用于注册对象。 它有两个参数,第一个用于命名服务的字符串,另一个参数带有将由客户端获取以使用服务的对象。
现在,要创建存根,您需要在远程实现类上运行rmic 。 Rmic工具随Java软件开发人员一起提供,实现服务实现并创建一个新的存根。 您应该打开命令提示符(cmd),并从远程实现所在的目录运行rmic。
下一步是运行rmiregistry,打开一个终端并启动注册表,只需键入命令rmiregistry 。 但是请确保从具有访问类权限的目录中启动它。
最后一步是启动服务,即运行远程类的具体实现,在这种情况下,该类为ReportGeneratorImpl
。
到目前为止,我们已经创建并运行了服务。 现在,让我们看看客户端将如何使用它。 比萨公司所有者的报表应用程序将使用此服务来生成和检查报表。 我们需要向将使用该服务的客户端提供ReportGenerator
接口和存根。 您可以简单地交付存根以及服务中所需的任何其他类或接口。
package com.javacodegeeks.patterns.proxypattern.remoteproxy;import java.rmi.Naming;public class ReportGeneratorClient {public static void main(String[] args) {new ReportGeneratorClient().generateReport();}public void generateReport(){try {ReportGenerator reportGenerator = (ReportGenerator)Naming.lookup("rmi://127.0.0.1/PizzaCoRemoteGenerator");System.out.println(reportGenerator.generateDailyReport());} catch (Exception e) {e.printStackTrace();}}}
上面的程序将具有以下输出:
********************Location X Daily Report********************Location ID: 012Today's Date: Sun Sep 14 00:11:23 IST 2014Total Pizza Sell: 112Total Sale: $2534Net Profit: $1985***************************************************************
上面的类进行命名查找,并检索用于生成每日报告的对象。 您需要提供主机名的IP和用于绑定服务的名称。 其余的看起来就像是一个常规的旧方法调用。
总之,远程代理充当驻留在不同JVM中的对象的本地代表。 代理上的方法调用导致该调用通过有线传输,远程调用,并将结果返回给代理,然后返回给客户端。
4.虚拟代理
虚拟代理模式是一种节省内存的技术,建议将对象创建推迟到需要时再进行。 它在创建对象时使用,在内存使用或所涉及的处理方面非常昂贵。 在典型的应用程序中,不同的对象组成功能的不同部分。 启动应用程序时,它可能不需要其所有对象立即可用。 在这种情况下,虚拟代理模式建议将对象创建推迟到应用程序需要时再进行。 第一次创建的对象在应用程序中被引用,并且从该点开始重复使用同一实例。 这种方法的优点是应用程序启动时间更快,因为不需要创建和加载所有应用程序对象。
假设您的应用程序中有一个Company
对象,并且此对象在ContactList
对象中包含该公司的雇员列表。 公司中可能有数千名员工。 从数据库中加载Company
对象以及ContactList
对象中所有雇员的列表可能非常耗时。 在某些情况下,您甚至不需要雇员列表,但是您不得不等到公司及其雇员列表加载到内存中。
节省时间和内存的一种方法是避免在需要之前加载员工对象,而这是使用虚拟代理完成的。 该技术也称为延迟加载,您仅在需要时才获取数据。
package com.javacodegeeks.patterns.proxypattern.virtualproxy;public class Company {private String companyName;private String companyAddress;private String companyContactNo;private ContactList contactList ;public Company(String companyName,String companyAddress, String companyContactNo,ContactList contactList){this.companyName = companyName;this.companyAddress = companyAddress;this.companyContactNo = companyContactNo;this.contactList = contactList;System.out.println("Company object created...");}public String getCompanyName() {return companyName;}public String getCompanyAddress() {return companyAddress;}public String getCompanyContactNo() {return companyContactNo;}public ContactList getContactList(){return contactList;}}
上面的Company
类具有对ContactList
接口的引用,该接口的真实对象仅在请求调用getContactList()
方法时才加载。
package com.javacodegeeks.patterns.proxypattern.virtualproxy;import java.util.List;public interface ContactList {public List<Employee> getEmployeeList();
}
ContactList
接口仅包含一种方法getEmployeeList()
,该方法用于获取公司的员工列表。
package com.javacodegeeks.patterns.proxypattern.virtualproxy;import java.util.ArrayList;
import java.util.List;public class ContactListImpl implements ContactList{@Overridepublic List<Employee> getEmployeeList() {return getEmpList();}private static List<Employee>getEmpList(){List<Employee> empList = new ArrayList<Employee>(5);empList.add(new Employee("Employee A", 2565.55, "SE"));empList.add(new Employee("Employee B", 22574, "Manager"));empList.add(new Employee("Employee C", 3256.77, "SSE"));empList.add(new Employee("Employee D", 4875.54, "SSE"));empList.add(new Employee("Employee E", 2847.01, "SE"));return empList;}}
上面的类将创建一个实际的ContactList
对象,该对象将返回公司的员工列表。 仅在需要时才按需加载对象。
package com.javacodegeeks.patterns.proxypattern.virtualproxy;import java.util.List;public class ContactListProxyImpl implements ContactList{private ContactList contactList;@Overridepublic List<Employee> getEmployeeList() {if(contactList == null){System.out.println("Creating contact list and fetching list of employees...");contactList = new ContactListImpl();}return contactList.getEmployeeList();}}
ContactListProxyImpl
是代理类,它也实现ContactList
并保存对实际ContactList
对象的引用。 在方法getEmployeeList()
的实现上,它将检查contactList
引用是否为null,然后将创建一个实际的ContactList
对象,然后将对其调用getEmployeeList()
方法以获取员工列表。
Employee类看起来像这样。
package com.javacodegeeks.patterns.proxypattern.virtualproxy;public class Employee {private String employeeName;private double employeeSalary;private String employeeDesignation;public Employee(String employeeName,double employeeSalary,String employeeDesignation){this.employeeName = employeeName;this.employeeSalary = employeeSalary;this.employeeDesignation = employeeDesignation;}public String getEmployeeName() {return employeeName;}public double getEmployeeSalary() {return employeeSalary;}public String getEmployeeDesignation() {return employeeDesignation;}public String toString(){return "Employee Name: "+employeeName+", EmployeeDesignation: "+employeeDesignation+", Employee Salary: "+employeeSalary;}}package com.javacodegeeks.patterns.proxypattern.virtualproxy;import java.util.List;public class TestVirtualProxy {public static void main(String[] args) {ContactList contactList = new ContactListProxyImpl();Company company = new Company("ABC Company", "India", "+91-011-28458965", contactList);System.out.println("Company Name: "+company.getCompanyName());System.out.println("Company Address: "+company.getCompanyAddress());System.out.println("Company Contact No.: "+company.getCompanyContactNo());System.out.println("Requesting for contact list");contactList = company.getContactList();List<Employee>empList = contactList.getEmployeeList();for(Employee emp : empList){System.out.println(emp);}}}
上面的程序将具有以下输出:
Company object created...
Company Name: ABC Company
Company Address: India
Company Contact No.: +91-011-28458965
Requesting for contact list
Creating contact list and fetching list of employees...
Employee Name: Employee A, EmployeeDesignation: SE, Employee Salary: 2565.55
Employee Name: Employee B, EmployeeDesignation: Manager, Employee Salary: 22574.0
Employee Name: Employee C, EmployeeDesignation: SSE, Employee Salary: 3256.77
Employee Name: Employee D, EmployeeDesignation: SSE, Employee Salary: 4875.54
Employee Name: Employee E, EmployeeDesignation: SE, Employee Salary: 2847.01
如您在TestVirtualProxy
生成的输出中所TestVirtualProxy
,首先我们创建了一个带有代理ContactList
对象的Company
对象。 此时, Company
对象保存一个代理引用,而不是真实的ContactList
对象的引用,因此内存中没有加载任何员工列表。 我们对公司对象进行了一些调用,然后使用getEmployeeList()
方法从联系人列表代理对象中请求雇员列表。 调用此方法时,代理对象将创建一个实际的ContactList
对象并提供雇员列表。
5.保护代理
通常,应用程序中的对象相互交互以实现整体应用程序功能。 通常,大多数应用程序对象可由应用程序中的所有其他对象访问。 有时,可能有必要根据对象的访问权限将对象的可访问性仅限制为一组有限的客户端对象。 当客户端对象尝试访问此类对象时,仅当客户端可以提供适当的身份验证凭据时,才可以向客户端授予对该对象提供的服务的访问权限。 在这种情况下,可以指定一个单独的对象,负责验证不同客户端对象在访问实际对象时的访问权限。 换句话说,每个客户端都必须成功与此指定对象进行身份验证才能访问实际的对象功能。 客户端需要通过身份验证才能访问实际对象的此类对象可以称为使用保护代理实现的对象身份验证器。
返回到我们为披萨公司开发的ReportGenerator
应用程序,所有者现在要求只有他才能生成每日报告。 没有其他员工应该能够这样做。
为了实现此安全功能,我们使用了保护代理,该代理可以检查试图生成报告的对象是否为所有者; 在这种情况下,将生成报告,否则不会生成。
package com.javacodegeeks.patterns.proxypattern.protectionproxy;public interface Staff {public boolean isOwner();public void setReportGenerator(ReportGeneratorProxy reportGenerator);
}
Staff
接口由Owner
和Employee
类Owner
,并且该接口具有两种方法: isOwner()
返回一个boolean
以检查调用对象是否为所有者。 另一个方法用于设置ReportGeneratorProxy
,它是用于生成报告的保护代理, isOwner()
方法返回true。
package com.javacodegeeks.patterns.proxypattern.protectionproxy;public class Employee implements Staff{private ReportGeneratorProxy reportGenerator;@Overridepublic void setReportGenerator(ReportGeneratorProxy reportGenerator){this.reportGenerator = reportGenerator;}@Overridepublic boolean isOwner() {return false;}public String generateDailyReport(){try {return reportGenerator.generateDailyReport();} catch (Exception e) {e.printStackTrace();}return "";}}
Employee
类实现了Staff
接口,因为它是一个雇员isOwner()
方法,返回false
。 generateDailyReport()
方法要求ReportGenerator
生成每日报告。
package com.javacodegeeks.patterns.proxypattern.protectionproxy;public class Owner implements Staff {private boolean isOwner=true;private ReportGeneratorProxy reportGenerator;@Overridepublic void setReportGenerator(ReportGeneratorProxy reportGenerator){this.reportGenerator = reportGenerator;}@Overridepublic boolean isOwner(){return isOwner;}public String generateDailyReport(){try {return reportGenerator.generateDailyReport();} catch (Exception e) {e.printStackTrace();}return "";}}
Owner
类看起来与Employee
类几乎相同,唯一的区别是isOwner()
方法返回true
。
package com.javacodegeeks.patterns.proxypattern.protectionproxy;public interface ReportGeneratorProxy {public String generateDailyReport();
}
ReportGeneratorProxy
充当保护代理,它只有一种用于生成报告的方法generateDailyReport()
。
package com.javacodegeeks.patterns.proxypattern.protectionproxy;import java.rmi.Naming;import com.javacodegeeks.patterns.proxypattern.remoteproxy.ReportGenerator;public class ReportGeneratorProtectionProxy implements ReportGeneratorProxy{ReportGenerator reportGenerator;Staff staff;public ReportGeneratorProtectionProxy(Staff staff){this.staff = staff;}@Overridepublic String generateDailyReport() {if(staff.isOwner()){ReportGenerator reportGenerator = null;try {reportGenerator = (ReportGenerator)Naming.lookup("rmi://127.0.0.1/PizzaCoRemoteGenerator");return reportGenerator.generateDailyReport();} catch (Exception e) {e.printStackTrace();}return "";}else{return "Not Authorized to view report.";}}
}
上面的类是ReportGeneratorProxy
的具体实现,其中包含对作为远程代理的ReportGenerator
接口的引用。 在generateDailyReport()
方法中,它检查工作人员是否正在引用所有者,然后要求远程代理生成报告,否则它返回带有“未授权查看报告”的字符串作为消息。
package com.javacodegeeks.patterns.proxypattern.protectionproxy;public class TestProtectionProxy {public static void main(String[] args) {Owner owner = new Owner();ReportGeneratorProxy reportGenerator = new ReportGeneratorProtectionProxy(owner);owner.setReportGenerator(reportGenerator);Employee employee = new Employee();reportGenerator = new ReportGeneratorProtectionProxy(employee);employee.setReportGenerator(reportGenerator);System.out.println("For owner:");System.out.println(owner.generateDailyReport());System.out.println("For employee:");System.out.println(employee.generateDailyReport());}}
上面的程序将具有以下输出:
For owner:
********************Location X Daily Report********************Location ID: 012Today's Date: Sun Sep 14 13:28:12 IST 2014Total Pizza Sell: 112Total Sale: $2534Net Profit: $1985***************************************************************
For employee:
Not Authorized to view report.
以上输出清楚地表明,所有者可以生成报告,而员工则不能。 保护代理保护访问生成报告的权限,并且仅允许授权对象生成报告。
6.何时使用代理模式
只要需要对对象的引用比简单的指针更广泛或更复杂,就可以使用代理。 这是代理模式适用的几种常见情况:
- 远程代理为不同地址空间中的对象提供了本地代表。
- 虚拟代理根据需要创建昂贵的对象。
- 保护代理控制对原始对象的访问。 当对象应具有不同的访问权限时,保护代理很有用。
7.其他代理
除了上面讨论的三个主要代理外,还有其他一些代理。
- 缓存代理/服务器代理 :提供存储最常用目标操作的结果所需的功能。 代理对象将这些结果存储在某种存储库中。 当客户端对象请求相同的操作时,代理将从存储区域返回操作结果,而无需实际访问目标对象。
- 防火墙代理 : 防火墙代理的主要用途是保护目标对象免受不良客户端的攻击。 防火墙代理还可用于提供防止客户端访问有害目标所需的功能。
- 同步代理 :提供所需的功能,以允许不同的客户端对象安全并发地访问目标对象。
- 智能参考代理 :提供功能,以防止当前有客户端对其进行引用时意外处置/删除目标对象。 为此,代理会保留对目标对象的引用数量的计数。 如果并且在没有引用时,代理会删除目标对象。
- 计数代理 :在对目标对象执行方法之前,提供某种审核机制。
8. JDK中的代理模式
以下情况是在JDK中使用代理模式的示例。
-
java.lang.reflect.Proxy
-
java.rmi.* (whole package)
9.下载源代码
这是关于代理模式的课程。 您可以在此处下载源代码: ProxyPattern-Project.zip
翻译自: https://www.javacodegeeks.com/2015/09/proxy-design-pattern.html