当前位置: 首页 > 科技观察

设计模式详解:代理模式

时间:2023-03-20 13:26:52 科技观察

DesignPatterns已经和大家分享了很多常见的模式。有兴趣的朋友可以再复习一下,巩固一下理解。这次要跟大家分享的是设计模式中三种创作中的代理模式。我们在业务场景中可能不会经常使用代理模式,但是面试官经常会问一个问题?请大家说说Spring中AOP的代理模式?jdk的代理模式和cglib的代理模式有什么区别?不清楚的同学可以继续往下看,一定会有收获的。言归正传,我们来一步步分析代理模型。定义及目的首先,代理模式可以分为很多种远程代理:就是把工作委托给一个远程对象(不同的服务器,或者不同的进程)来完成。常用于网络服务。另外,我们的RPC调用也可以理解为远程代理。ProtectionAgent:该模式主要进行安全/权限检查。(接触不多)缓存代理:这个很好理解,就是通过存储来加速调用,比如Sping中的@Cacheable方法,将特定参数得到的结果缓存起来。下次以相同参数调用该方法时,直接从缓存数据中返回。虚拟代理:这种代理主要是给方法增加功能,比如记录一些性能指标等,或者进行延迟初始化。以上只是我们理解的一个概念。接下来我们看一下代理模式的组成部分。Subject(公共接口):客户端使用的现有接口RealSubject(真实对象):真实对象的类ProxySubject(代理对象):代理类从图中可以看出,整个接口其实很简单,这是一个真实的对象和一个代理对象。目的:提供一个实际的代理对象,以便更好地控制实际对象。以上定义来自《设计模式之美》代码示例实现。为了便于理解,我举个例子。不知道大家有没有经历过初中或者高中传小纸条的过程。如果A同学有话要对C同学说(比如放学后约好一起打游戏)但是因为现在是上课时间,我不能大声说出来,所以有一个B同学坐在A同学中间还有C同学,所以现在A同学只能找B同学把纸条给了,让他告诉C同学,但是玩不玩,只能由真正的C同学自己说了算。所以,代理模型可以理解为B同学是C同学的代理,A同学想找C同学,但只能找到B同学,将B同学传递给C同学,同时反馈执行C同学给A同学的成绩。说完例子,我们来看看代码的实现。publicinterfaceSubject{//commoninterfacevoiddoSomething();}定义一个公共接口(就是大家要做的事情:放学后一起玩游戏)publicclassRealSubjectimplementsSubject{//真实对象@OverridepublicvoiddoSomething(){System.out.println("Playinggamesafterschool");}}构造一个真实的对象,即例子中的同学。IllegalAccessException,InstantiationException{this.realSubject=(RealSubject)this.getClass().getClassLoader().loadClass("com.ao.bing.demo.proxyPattern.RealSubject").newInstance();}@OverridepublicvoiddoSomething(){realSubject.doSomething();}publicstaticvoidmain(String[]args){try{//第一个方法newProxySubject().doSomething();//打印结果:放学去打游戏}catch(Exceptione){//异常,代理失败了,//传纸条的被老师抓到了。或者C同学不在座位等异常情况}//方法二newProxySubject(newRealSubject()).doSomething();//打印结果:放学去打游戏}}构建代理对象,也就是B同学,那么可以看到A同学实际上并没有联系C同学,通过B同学代理给C同学,就可以知道C同学放学后能不能和他打游戏。在Main方法中,调用真实对象的方式有两种。第一种:使用类加载器的形式来加载真实的对象,让我们在需要真正的真实对象的时候无所谓。第二种:按值传递真实对象。(理解为装饰器模式)这里需要区分一下,代理模式提供的是完全一样的接口,而装饰器模式是对接口进行增强。StaticProxy、DynamicProxy和cglibProxy分析StaticProxy上面列表的实现其实就是一个静态代理,可以看到整体还是比较简单的。但其缺点也很明显。静态代理需要为每个对象创建一个代理类,增加了维护成本和开发成本。那么为了解决这个问题,动态代理就出来了,不要为每一个需要代理的类都搞定而创建一个代理类动态代理动态代理合理的避开了静态代理的方式,不需要再构建代理类提前代理班级。相反,它是在运行时通过反射机制创建的。写动态代理的时候需要明白两点:Proxy可以理解为调度器,InvocationHandler的增强服务接口可以理解为代理。所以我个人理解动态代理其实就是一种行为监控。具体代码实现举例:螳螂捕蝉,通过螳螂监测蝉的动向。方便后面再说多级代理模式。publicinterfaceBaseService{voidmainService();}publicclassCicadaimplementsBaseService{@OverridepublicvoidmainService(){System.out.println("主要业务,以蝉为例,蝉发出业务呼叫,螳螂听");}}创建一个普通的接口,而真正的ObjectCicadapublicclassPrayingMantisimplementsInvocationHandler{privateBaseServicebaseService;//这里是用来构建参数的,可以使用反射,第一个例子给出的样式代码是publicPrayingMantis(BaseServicebaseService){this.baseService=baseService;}//Mantis的主要业务是监听Object@OverridepublicObjectinvoke(Objectlistener,Methodmethod,Object[]args)throwsThrowable{method.invoke(baseService,args);secondaryMain();returnnull;}//这里理解为增强服务,即我们可以在InvocationHandler的实现中添加其他服务,比如日志等。privatevoidsecondaryMain(){System.out.println("螳螂捕蝉-副业");}}创建螳螂类,监听蝉类的动作publicclassBeanFactory{publicstaticBaseServicenewInstanc(ClassclassFile){//1.创建蝉,一个真正的类ObjectBaseServicetrueCicada=newCicada();//2。创建代理类MantisInvocationHandlerprayingMantis=newPrayingMantis(trueCicada);//3.向Jvm要代理对象,其实是监听对象,ClassclassArray[]={BaseService.class};BaseServicebaseService=(BaseService)Proxy.newProxyInstance(classFile.getClassLoader(),classArray,prayingMantis);returnbaseService;}//测试Demopublicstaticvoidmain(String[]args){BaseServicebaseService=newInstanc(Cicada.class);baseService.mainService();//测试结果:主业务//螳螂捕蝉-副业务}}通过结果可以看出,当蝉的主要业务调用,mantis可以监控蝉的业务,处理其他业务逻辑,这就是为什么Spring中的AOP可以处理它的log段等代理的本质:我觉得其实是一种对行为的监控,一个代理对象的监控行为($proxyInvocationHandler)。代理模式组成:接口:声明需要监控的行为由代理(InvocationHandler)实现:副业务,副业务和主业务绑定执行代理对象(监控对象)cglib动态代理cglib动态代理实际上是和jdk的动态代理很相似,需要实现代理接口才能完成。具体代码如下:);return(Cicada)enhancer.create();}@OverridepublicObjectintercept(Objecto,Methodmethod,Object[]objects,MethodProxymethodProxy)throwsThrowable{Objectobject=methodProxy.invokeSuper(o,objects);secondaryMain();returnobject;}privatevoidsecondaryMain(){系统。out.println("螳螂捕蝉-副业");}publicstaticvoidmain(String[]args){PrayingMantisprayingMantis=newPrayingMantis();Cicadainstance=prayingMantis.getInstance(newCicada());instance.mainService();//结果:主业//螳螂捕蝉-副业}因为蝉都是一样的,这里就不单独贴了。细心的同学已经发现,Cglib不需要通过接口来实现,它是通过实现子类来调用的。Enhancer对象将代理对象设置为代理类的子类,实现动态代理。因为采用了继承的方式,代理类不能被final修改,否则会报错。final类:该类不能被继承,内部方法和变量成为final。JDK与Cglib的区别:jdk动态代理使用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler处理cglib动态代理使用ASM开源包加载类文件代理对象类,并通过修改其字节码来生成子类来处理ASM:一个Java字节码操作框架。它可用于动态生成类或增强现有类的功能。ASM可以直接生成二进制类文件,也可以在类加载到Java虚拟机之前动态改变类的行为。Java类存储在严格格式定义的.class文件中,这些文件具有足够的元数据来解析类中的所有元素:类名、方法、属性和Java字节码(指令)。ASM从类文件中读取信息后,可以改变类行为,分析类信息,甚至可以根据用户要求生成新的类。--以上ASM解释来自简书里的multi-leveldynamicagent。看完上面的动态代理,不知道大家有没有实现多级动态代理的想法。还是以螳螂捕蝉为例,在后面加一只黄鸟,实现多级动态代理模式。publicclassCardinalimplementsInvocationHandler{//监听proxy代理对象privateObjectproxyOne;publicCardinal(ObjectproxyOne){this.proxyOne=proxyOne;}//螳螂的主要业务,也就是监听对象@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{method.invoke(proxyOne,args);secondaryMain();returnnull;}privatevoidsecondaryMain(){System.out.println("金雀花吃螳螂-副业");}}创建一个金莺代理对象,然后作为它的真实对象成为一只螳螂。调用mantis对象时,黄雀能强,同时做相应的业务逻辑publicclassBeanFactory{publicstaticBaseServicenewInstanc(ClassclassFile){//1。创建蝉,真实类对象BaseServicetrueCicada=newCicada();//2.创建代理类MantisInvocationHandlerprayingMantis=newPrayingMantis(trueCicada);//3.向Jvm询问代理对象其实是一个实体对象ClassclassArray[]={BaseService.class};BaseServicebaseService=(BaseService)Proxy.newProxyInstance(classFile.getClassLoader(),classArray,prayingMantis);//4.创建代理实现cardinal类的二级代理InvocationHandlercardinal=newCardinal(baseService);BaseServicesecondBaseService=(BaseService)Proxy.newProxyInstance(classFile.getClassLoader(),classArray,cardinal);//假设要实现三级和四级代理,可以通过在oriole类中增加一层代理来实现。){BaseServicebaseService=BeanFactory.newInstanc(Cicada.class);baseService.mainService();//result:主业务//螳螂捕蝉-副业务//歌手吃螳螂-副业务}}根据这段代码实现一个多级代理进程。螳螂听蝉动,山雀听螳螂动。同样,如果要实现三级代理,四级代理也不难,只需要在每一层之上添加一个代理对象即可。动态代理的本质还是可以理解为将“副业务”与“主业务”解耦,让开发者更专注于主业务,提高开发效率和维护成本。总结我个人认为代理模式在业务代码中是比较少见的,尤其是动态代理基本上是没见过。但是代理模式也是我们必须了解的一种模式,因为学习代理模式可以帮助我们阅读一些源码,排查一些更深层次的问题,或者面对一些业务场景的问题,也可以对设计模式本身有很大的提升是为了解决这个问题而创建的。了解了动态代理之后,AOP的实现原理现在对我们来说就不言而喻了。详细的设计模式到这里就结束了,后面我会给大家总结一些不常见的设计模式。我是敖丙,知道的越多,不知道的越多,我们下期再见!!!