当前位置: 首页 > 后端技术 > Java

基于Java的代理模式

时间:2023-04-01 17:52:15 Java

代理模式是常见的设计模式之一,本意是为指定的对象提供代理,以控制对该对象的访问。Java中的代理分为动态代理和静态代理。动态代理在Java中被广泛使用,例如Spring的AOP实现和远程RPC调用。静态代理和动态代理最大的区别在于代理类是在JVM启动之前还是之后生成的。本文将介绍Java的静态代理和动态代理,以及两者的比较,重点介绍动态代理的原理和实现。代理模式代理模式的定义:为其他对象提供一个代理来控制对这个对象的访问。在某些情况下,一个对象不适合或不能直接引用另一个对象,代理对象可以充当客户端和目标对象之间的中介。例如:要访问的对象在远程机器上。在面向对象的系统中,由于某些原因(比如对象创建成本很高,或者某些操作需要安全控制,或者需要进程外访问),直接访问会给用户或系统结构带来很多麻烦。我们可以在访问这个对象的时候给这个对象加上一个访问层。代理的组成代理由以下三部分组成:抽象角色:通过接口或抽象类声明真实角色实现的业务方法。代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法实现抽象方法,可以添加自己的操作。真实角色:实现抽象角色,定义真实角色要实现的业务逻辑,为代理角色调用。代理的优点是职责明确:真正的作用是实现实际业务的逻辑,不需要涉及到非业务逻辑(比如事务管理)。隔离性:代理对象可以充当客户端和目标对象之间的中介,目标对象不直接暴露给客户端,从而达到隔离目标对象的功能高扩展性:代理对象可以灵活扩展目标目的。代理示例我们用一个加载和显示图片的例子来说明代理的工作原理。图片存储在磁盘上,每次IO会消耗更多的事件。如果我们需要频繁显示图片,那么每次从磁盘读取会花费很长时间。我们使用代理来缓存图片,第一次读取图片时只从磁盘中读取,然后再从缓存中读取。源码示例如下:importjava.util.*;interfaceImage{publicvoiddisplayImage();}//在SystemAclassRealImageimplementsImage{privateStringfilename;publicRealImage(Stringfilename){this.filename=filename;loadImageFromDisk();}privatevoidloadImageFromDisk(){System.out.println("正在加载"+文件名);}publicvoiddisplayImage(){System.out.println("显示"+文件名);}}//在系统B类ProxyImage上实现图像{privateStringfilename;私有图像图像;publicProxyImage(Stringfilename){this.filename=filename;}publicvoiddisplayImage(){if(image==null)image=newRealImage(filename);image.displayImage();}}classProxyExample{publicstaticvoidmain(String[]args){Imageimage1=newProxyImage("HiRes_10MB_Photo1");图片image2=新ProxyImage("HiRes_10MB_Photo2");image1.displayImage();//加载必要的image2.displayImage();//loadingnecessary}}静态代理静态代理需要在程序中定义两个类:目标对象类和代理对象类,为了保证两种行为的一致性,目标对象和代理对象实现相同的接口。代理类的信息在程序运行前就已经确定,代理对象会包含目标对象的引用。举例说明静态代理的使用:假设我们有一个计算员工工资的接口方法,以及一个实现具体逻辑的实现类。如果我们需要在计算员工工资的逻辑中加入日志怎么办?直接在薪资计算的实现逻辑中加入会导致引入非业务逻辑,不符合规范。这时候我们可以引入一个日志代理,输出工资计算前后的相关日志信息。计算员工工资的接口定义如下:publicinterfaceEmployee{doublecalculateSalary(intid);}计算员工工资的实现类如下:publicclassEmployeeImpl{publicdoublecalculateSalary(intid){return100;}}}Proxywithlogs类的实现如下:publicclassEmployeeLogProxyimplementsEmployee{//代理类需要包含一个目标类的对象引用privateEmployeeImplemployee;//并提供一个带参数的构造方法来指定代理哪个对象publicEmployeeProxyImpl(EmployeeImplemployee){this.employee=employee;}publicdoublecalculateSalary(intid){//记录调用目标类的calculateSalary方法前的日志System.out.println("当前正在计算员工的税后工资:"+id+"");双倍工资=employee.calculateSalary(id);System.out.println("计算员工:"+id+"的税后工资结束");//调用目标类方法后记录returnsalary;}}Dynamic代理动态代理的代理对象类是在程序运行时创建的,而静态代理对象类是在程序编译时确定的,这是两者最大的区别。动态代理的好处是开发者不需要手动编写很多代理类。比如上面的例子,如果Manager类中计算工资的逻辑需要日志,那么我们就需要新建一个ManagerLogProxy来代理对象。如果需要的代理对象很多,那么需要写的代理类也会很多。但是使用动态代理就没有这个问题。一类代理只需要写一次,就可以应用于所有的代理对象。比如上面的Employee和Manager,只需要抽象出一个与工资计算相关的接口,就可以使用同一套动态代理逻辑来实现代理。DynamicProxyExample下面我们使用上面计算员工和经理工资的逻辑来演示动态代理的用法。接口抽象我们知道Employee和Manager都有计算工资的逻辑,需要记录计算工资的逻辑,所以我们需要抽象一个计算工资的接口:publicinterfaceSalaryCalculator{doublecalculateSalary(intid);}interface实际publicclassEmployeeSalaryCalculatorimplementsSalaryCalculator{publicdoublecalculateSalary(intid){return100;}}公共类ManagerSalaryCalculator实现SalaryCalculator{publicdoublecalculateSalary(intid){return1000000;}}创建动态代理的InvocationHandlerpublicclassSalaryLogProxyimplementsInvocationHandler{privateSalaryCalculatorcalculator;publicSalaryLogProxy(SalaryCalculatorcalculator){this.calculator=calculator;}@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{System.out.println("--------------begin--------------");对象调用=method.invoke(subject,args);System.out.println("-------------结束------------");返回调用;}}创建代理对象公共类Main{publicstaticvoidmain(String[]args){SalaryCalculatorcalculator=newManagerSalaryCalculator();InvocationHandlercalculatorProxy=newSalaryLogProxy(subject);SalaryCalculatorproxyInstance=(SalaryCalculator)Proxy.newProxyInstance(calculatorProxy.getClass().getClassLoader(subject.getClass().getInterfaces(),calculatorProxy);proxyInstance.calculateSalary(1);}}动态代理源码分析动态代理的流程如下图所示,可以看到动态代理包含以下内容:目标对象:我们需要代理对象对应上面newManagerSalaryCalculator()接口:目标对象和代理对象需要的方法providetogether对应上面的SalaryCalculatorProxy代理:用于生成代理对象类Proxy对象类:通过proxy获取到的Proxy对象和对应的参数Classloader:用于加载代理对象类的类加载器,对应于calculatorProxy.getClass().getClassLoader()上面Proxy.newProxyInstance动态代理的关键代码是Proxy.newProxyInstance(classLoader,interfaces,handler),可以看到Proxy.newProxyInstance做了两件事:1.获取代理对象类的构造函数,2:根据构造函数实例化代理对象。@CallerSensitivepublicstaticObjectnewProxyInstance(ClassLoaderloader,Class[]interfaces,InvocationHandlerh){Objects.requireNonNull(h);finalClasscaller=System.getSecurityManager()==null?null:反射.getCallerClass();/**查找或生成指定的代理类及其构造函数。*///获取代理对象类的构造函数,包括代理对象类的构造和加载Constructorcons=getProxyConstructor(caller,loader,interfaces);//根据构造函数生成代理实例。returnnewProxyInstance(caller,cons,h);}代理对象类通过查看源码可以发现,代理对象类继承了Proxy类,实现了指定的接口方法。由于java不能继承更多,所以这里继承了Proxy类,其他类继承不了。所以JDK的动态代理不支持实现类的代理,只支持接口的代理。我是狐神,欢迎大家关注我的微信公众号本文首发于微信公众号,版权所有,禁止转载!