本文由老王出租屋-代理设计模式介绍,将从最简单的静态代理实现开始,然后扩展使用jdk实现动态代理,最后扩展到Cglib实现动态代理代理人。为了对代理模式有更深入的理解,我们将介绍实际应用中的典型案例,包括在Spring和Mybatis中的应用。读者可以拉取完整代码在本地学习,测试通过后将代码上传至码云。一、问题的引出上一篇虽然老王和小王把电脑组装的很完美,但是老王和小王的争吵并没有结束。老汪决定把小汪踢出去,把小汪住的房子租出去,租金用来补游戏本的费用。老王费了九牛二虎之力摸清了各个租房平台的规则,发布了房源信息,随后邻居提醒他:房子出租不等于代收租金,哪天租客把提出一些额外的要求,在合同允许的范围内,尽量满足(为了理解,当然现实中是不存在的),如有问题需与物业协调房子出租后与房产一起。老王开始想,如果我直接租给房客,我会面临两个问题:①我需要了解租房的整个过程,因为我自己的事情和租房是严重耦合的。②我不得不干预房客提出的一些要求,我不得不改变自己的行程。我应该想个办法。第一点是业务和功能解耦。业务层专注于业务,例如网络接口请求。业务层只需要知道调用哪个接口请求方法即可,不需要知道接口请求是如何发起的。网络要求的。第二点是创建一个切面,在这个切面中添加一些常用的附加操作,比如注解解析,日志上报等,避免在每个接口方法中都写这些常用操作。老王灵光一闪:我可以为我的房子找一个代理人来控制房子的管理。也就是通过中介来管理房子。这样做的好处是:在目标实现的基础上,可以增强额外的功能操作,即扩展目标的功能。这实际上是一个静态代理。2.Staticproxy代理模式:为一个对象提供一个替身来控制对这个对象的访问。即通过代理对象访问目标对象。这样做的好处是在目标对象实现的基础上,可以增强额外的功能操作,即可以扩展目标对象的功能。也就是说,静态代理中应该有三个角色:①一个代理对象,消费者可以通过它访问实际的对象(中介)②实际的被代理对象(老王家)③可以被代理的行为的集合,通常它是一个接口(法老和中介之间约定的事件)。老王与中介约定的接口:/***代理行为集合(接口)*@authortcy*@Date02-08-2022*/publicinterfaceHostAgreement{//房屋出租voidrent();}Actual对象类(老王):/***目标对象*@authortcy*@Date02-08-2022*/publicclassHostimplementsHostAgreement{/***目标对象的原始方法*/@Overridepublicvoidrent(){System.out.println("这房子是出租的...");}}代理类(中介):/***实际对象的代理*@authortcy*@Date02-08-2022*/publicclassHostProxyimplementsHostAgreement{//目标对象,通过接口聚合privateHostAgreementtarget;//构造函数publicHostProxy(HostAgreementtarget){this.target=target;}@Overridepublicvoidrent(){System.out.println("在出租房子之前,装修一下....");target.rent();System.out.println("出租房子后,与物业协调....");//方法}}客户端类:/***@authortcy*@Date02-08-2022*/public类客户端{公共静态无效主要(String[]args){//创建目标对象(代理对象)HosthostTarget=newHost();//创建代理对象,并将代理对象传递给代理对象HostProxyhostProxy=newHostProxy(hostTarget);//通过代理对象,调用代理对象的方法hostProxy.rent();}}这样就很好的解决了老王想到的问题。我小声嘀咕,我一个人管理这么多房子,每个房东都让我实现一个代理类,那我就会有很多代理类,这是个问题!另外,有一天协议改变了,我们都有很多工作要做。最好不要让我去实现我们之间的协议(接口)。老王开始修改计划。3.动态代理Jdk老王突然想到使用jdk的动态代理可以很好的解决这个问题。Jdk代理对象的生成是利用JDK的API在内存中动态构建代理对象动态代理,也叫:JDK代理,接口代理。我们修改代码。目标对象和目标对象的协议保持不变,我们需要修改中介(代理类)的代码。Proxyclass:/***Proxyclass*@authortcy*@Date02-08-2022*/publicclassHostProxy{//维护一个目标对象,ObjectprivateObjecttarget;//构造函数,初始化目标publicHostProxy(Objecttarget){this.target=target;}//为目标对象生成代理对象publicObjectgetProxyInstance(){//说明/**//1.ClassLoader加载器:指定当前目标对象使用的类加载器,获取加载器的方法是固定的//2.Class>[]interfaces:目标对象实现的接口类型,使用泛型方法确认类型//3.InvocationHandlerh:事件处理,当目标对象的方法执行时,会触发事件处理器方法,将当前执行的目标对象方法作为参数传递*/returnProxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),newInvocationHandler(){/***这个方法会调用目标对象的方法*@paramproxy*@parammethod*@paramargs*@return*@throwsThrowable*/@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{System.out.println("在出租房子之前,让我们装饰它......");//反射机制调用目标对象的方法ObjectreturnVal=method.invoke(target,args);System.out.println("房子出租后,与物业协调....");返回返回值;}});}}Client:/***@authortcy*@Date02-08-2022*/publicclassClient{publicstaticvoidmain(String[]args){//创建目标对象HostAgreementhostAgreement=newHost();//为目标对象创建代理对象,可以转换为ITeacherDaoHostAgreementhostProxy=(HostAgreement)newHostProxy(hostAgreement).getProxyInstance();//proxyInstance=classcom.sun.proxy.$Proxy0在内存中动态生成一个代理对象//通过代理对象,调用目标对象的方法hostProxy.rent();}}这样就解决了中介实现协议(接口)的问题,不管房子怎么变,中介都能完美实现代理中介想让老王告诉他Proxy.newProxyInstance()是如何完美解决这个问题的。老王挽起袖子开始给他讲解实现原理。当我们使用Proxy.newProxyInstance实现动态代理时,有3个参数,第一个是classloader。publicstaticObjectnewProxyInstance(ClassLoaderloader,Class>[]interfaces,InvocationHandlerh)在我们的代码中,classloader就是目标对象的类加载器。第二个参数是目标对象实现的接口类型。泛型方法用于确认类型。我们使用java反射target.getClass().getInterfaces()来获取接口类型。第三个参数是实现InvocationHandler接口,实现它唯一的方法invoke()。Invoke实际上会执行我们的目标方法,我们可以在invoke前后做一些事情。比如在房子出租前进行装修,或者在出租后与物业协调。听了经纪人的话,他心满意足地离开了。老王总觉得哪里不对劲。代理人退出协议,为什么我还受协议约束?为什么我不也从协议(接口)中提取。我们查了书,觉得Cglib或许能帮到他。四、动态代理Cglib1、概念与实现CGLIB是一个功能强大、高性能的代码生成库。使用非常底层的字节码技术,生成指定目标类的子类,并对子类进行增强,广泛应用于AOP框架(Spring、dynaop)中,提供方法拦截操作。CGLIB代理主要是通过对字节码的操作,对对象引入一个层次的间接,来控制对对象的访问。我们知道Java中有一个动态代理也是这样做的,那么为什么我们不直接使用Java动态代理而不是CGLIB呢?答案是CGLIB比JDK动态代理更强大。JDK动态代理虽然简单易用,但是它有一个致命的缺陷,就是只能代理接口。如果被代理的类是一个没有接口的普通类,那么就不能使用Java动态代理。老王觉得这些概念很不人道,老王还不如直接开工改造工程。CGLIB是第三方类库,需要先引入依赖。
