引言:
欢迎收看 - 会的不用看,不会的看不懂系列之动态代理
在java学习中,动态代理是一个很基础的知识点,但是这个知识点往往很多人没有将其搞明白,导致在学习Java的一些框架(例如spring,hibernate)时,对其原理模模糊糊,半知半解,今天,我们就将针对动态代理来做一个大白话式的讲解。希望能对大家在学习Java的时候,有所帮助。
Chapter 1:什么是代理
在计算机学科中,很多地方都用到了代理,例如网络中的正向代理,反向代理等。
代理是一个很常见的概念,从字面意思来解释就是你想做一件事情,又不想亲自去做,或者不能亲自去做(出于某种特殊原因),但是又想达到做了那件事情的结果。那么这个时候,就需要请一个人,或者机器去帮助你做这件事情,从而达到想要的目的。
最后这个别人帮你做事的过程,就叫做代理。
例如,你平时要上班,但是你这时候碰巧你的车吃了罚单,需要去交管所一趟。但是公司最近项目很紧,没法让你亲自去,这个时候,你请了你的好朋友A去车管所帮你处理罚单,这样的一个过程,就是代理。
代理在Java中,主要分为两种,一种是静态代理,另一个种是动态代理。
两者的区别在于:
静态代理是你做每一件事情,都要请一个专门的代理。
动态代理是你做任何事情,只需要一个代理就够了,这个代理能帮你做任何事情的代理(23333)。
而我们今天要来说的就是动态代理
Chapter 2:动态代理的实现
在java的使用中,很多地方都用到了动态代理。例如我们熟悉的一些框架,Spring框架的两大核心思想----IOC和AOP,AOP的实现就是使用的动态代理。还有Hibernate中的懒加载,看似是查询得到的一个数据对象,其实得到的是一个代理对象,使用到的技术原理也是动态代理。接下来,我们就来说说动态代理如何实现
Java的动态代理,实现的方式主要有两种:JDK的动态代理,Cglib的动态代理
这两者的区别在于:
JDK的动态代理需要基于接口去代理,也就是需要代理的对象需要实现任何一个接口,才能对这个对象进行代理
而Cglib则更为强大一点,可以对任何类进行的动态代理
(1) JDK动态代理的实现
JDK的动态代理,主要基于两个东西
1.InvocationHandler -- 接口
2.Proxy -- 对象
话不多说,我们先PO代码,再基于代码来解析
首先是服务员接口WaiterInterface,有两个方法,关注其中的服务方法service即可
然后是服务员的实现Waitress,对服务员接口进行的实现
接下来就是核心代码--代理对象处理类
最后是得到代理对象,运行其方法
运行结果
在这个案例中,服务员要对顾客进行服务,但是在服务之前,和服务之后需要做一系列的事情。
然而我们的专职服务员并不想做之前和之后的事情,她只想专注于服务客户,那么这些之前和之后的事情谁来做呢,当然可以交给我们的机器人(代理对象)来做,这样既完成了服务的工作,也完成了擦桌子和收拾桌子的行为。
这里的专注很重要!!!强调一下!!~
就像你在做Router层的时候,需要加日志去记录用户的访问,如果你用的SpringMVC框架,那么你是想在每一个controller的方法上加日志记录代码,还是统一做一个代理去记录日志呢?Ok,如果你的方法比较少,可以每一个方法上加。那么问题来了,我如果想要对用户进行权限控制,你依然是在controller每个方法上加代码去做权限控制吗?显然是不可能的。这样的代码,会越来越乱。这就是专注的重要性,专注自己要做的事情。
回到我们的代码中来,首先是一个Waiter(服务员)接口,大家很容易理解
然后是一个Waitress去实现了Waiter接口,变成了真正的服务员。她实现了service方法和come方法。
那么接下来问题来了,怎么去对这个Waitress进行代理呢。
答案是使用Proxy和InvocationHandler
我们先来看看InvocationHandler的源代码
InvocationHandler是一个接口,只有一个invoke方法。
上面的那段注释,大致意思就是说。每一个代理对象都会关联InvocationHandler的实现类。当你调用代理对象的任何方法时,实则调用的是InvocationHandler的invoke方法
invoke方法有三个参数,proxy代表的是代理对象,method代表的是被代理的对象所被调用的方法,例如上面调用的service。args代表被代理对象调用方法时的参数。
当执行method.invoke时,执行的就是service方法,但是我们可以再service执行之前和之后做一些操作
我们再来说说Proxy。他是一个类,主要用来获取代理对象。Proxy有一个静态方法,专门用来获取代理对象,叫做newProxyInstance
其中有三个参数,第一个参数是你要代理接口实现对象的类加载器,第二个参数是代理接口实现对象的所有接口,第三个参数就是InvocationHandler的实现类。
大致流程如下图
1.编写Waiter接口,并用一个类实现它
2.通过Proxy的静态方法newProxyInstance,传入Waiter实现类对象的类加载器(Loader)和Interfaces,以及InvocationHandler的实现类,获取Waiter接口的代理对象
3.Waiter的代理对象waiter,看似是一个Waiter接口,实则是对于代理对象的引用
4.当调用waiter对象的任何方法时,其实调用的是InvocationHandler的invoke()方法
这就是动态代理的整个流程。
(2) Cglib动态代理的实现
Cglib的动态代理,不同于JDK的动态代理,JDK的动态代理是基于接口的一种代理,也就是说,某各类必须实现某个接口,才能进行代理。
而Cglib是可以对任何类进行动态代理,相比如JDK的动态代理来说,没有那么局限
那么问题来了,Cglib代理的原理是什么呢?
原来,Cglib是通过字节码技术创建被代理对象类的子类,实现动态代理。
想要使用Cglib的动态代理,首先需要加入一些Jar包(JDK动态代理是Java原生的实现方式,所以不用引入jar包)
是的,你没有看错,需要这四个jar包,什么?你说去哪里下载?maven仓库了解一下。
Cglib的动态代理要做的事情和JDK动态代理是一样的,还是先PO代码
首先是Waitress类
然后是代理对象处理类
最后是运行
可以很明显地看出,cglib获取代理对象的方式是使用Enhancer对象。通过enhancer对象的create()方法,传入被代理对象以及MethodInterceptor的实现类,来获取代理对象。
接着调用代理对象的任何方法,依然调用的是MethodInterceptor的实现类的intercept()方法。
Intercept方法有四个参数,proxy代表被代理对象,method代表被代理对象被执行的方法,args代表被代理对象的方法的参数,methodProxy代表代理对象
当执行method.invoke或者methodProxy.invokeSuper时,实则执行的是实例中的service()方法,我们依然可以在service()方法的执行前后做一些操作,从而达到代理效果。
CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。同时,由于CGLib由于是采用动态创建子类的方法,对于final方法(无法继承),无法进行代理。
总结:
以上便是动态代理的全部内容,当然在Java中除了动态代理还有静态代理,但是动态代理的灵活性会高很多,所以多数情况下都是使用动态代理。
动态代理是一个对于初学者来说相对难懂的内容,但是在很多框架中都用到了这个技术,所以还是非常重要的。
感谢您阅读-----会的不用看,不会的看不懂系列
谢谢。