【干货点】 此处是【好好面试】系列文的第12篇文章。 文章目标主要是经过原理剖析的方式解答Aop动态代理的面试热点问题,经过一步步提出问题和了解原理的方式,咱们能够记得更深更牢,进而解决被面试官卡住喉咙的状况。 问题以下面试
该文章是必需要懂的SpringAop系列的最后一篇文章,第一篇文章是你必需要懂的Spring-Aop之应用篇,第二篇文章是你必需要懂的Spring-Aop之源码跟踪分析Aop,最后一篇文章咱们将会揭露Aop的原理,也就是动态代理。但愿看完这最后一篇文章,你们对SpringAop都有一个全面的掌握,无论面试官如何问,均可以屌回去 ━━( ̄ー ̄*|||━━spring
舒适提示:前面没看的最好点击浏览下!!!app
你们都知道,我有个习惯,在动手写一篇文章以前会先将该文章相关的资料仔细琢磨一遍,而后再结合源码再调试一遍,结果,说好的函数
看源码也确实是测试
源码确实有进行了是不是接口的判断,可是问题来了,我调试的时候发现不管代理类是否有接口,最终都会被强制使用CGLIB代理,没办法,只能翻看SpringBoot的相关文档,最终发现原来SpringBoot从2.0开始就默认使用Cglib代理了,好家伙,怪不得我调试半天找不到缘由。this
那么如何解决呢?确定是经过配置啦,按照以下配置便可3d
在application.properties文件中配置 spring.aop.proxy-target-class=false代理
便可。调试
【划重点】 曾经碰见过面试官问,SpringBoot默认代理类型是什么?看完该篇文章,咱们就能够果断的回答是Cglib代理了。经过调试代码发现的规则,我想我这辈子都不会忘记这个默认规则。cdn
简单来讲,就是在运行的时候为目标类动态生成代理类,而在操做的时候都是操做代理类,代理模式有个显而易见的好处,那即是能够在不改变对象方法的状况下对方法进行加强。试想下,咱们在你必需要懂的Spring-Aop之应用篇有提到使用Aop来作权限认证,若是不用Aop,那么咱们就必需要为全部须要权限认证的方法都加上权限认证代码,听起来就以为蛋疼,你以为对不对?
静态代理类不是说不能够用,若是只有一个类须要被代理,那么天然能够用,如 这是在你必需要懂的Spring-Aop之应用篇使用的一个例子类,该类的做用只是打印出我要买东西。
代理类以下
能够看到这个BuyProxy代理类只是塞了一个IBuyServcie接口进行,并且自身也实现了接口IBuyService,而在buyItem方法被调用的时候会先作本身的操做,再调用塞进去的接口的buyItem方法。 测试类很简单,以下
运行后很天然而然的打印出
静态代理就是简单,可是弊端也很明显,若是有多个类都须要一样的代理,都实现了一样的接口,那么若是使用静态代理的话,咱们就要构造多个Proxy类,就会形成类爆炸。 而使用了Aop后,也就是动态代理后,即可以一次性解决该问题了,具体能够看你必需要懂的Spring-Aop之应用篇中的操做方法。
这里给出一个JDK动态代理的demo 首先给出一个简单的业务类,Hello类和接口
真正实现了类的代理功能的其实就是这个实现了接口InvocationHandler的JdkProxy类
咱们能够看到其中必须实现的方法是invoke,能够看到invoke方法的参数带有Method对象,这个就是咱们的目标Method,如今咱们的目的就是要在这个Method在被调用先后实现咱们的业务,能够看到在method.invoke反调先后实现了before和after业务。
这里再给出一个Main测试类,做用是取得Hello的代理类,而后调用其中的say方法。
运行结果以下
原理很简单 在JdkProxyMain中hello调用say的时候,因为Hello已经被“代理”了,因此在调用say函数的时候实际上是调用JdkProxy类中的invoke函数,而在invoke函数中先是实现了before函数才实现Object result = method.invoke(target, args),这一句实际上是调用say函数,然后才实现after函数,因而这样就能够没必要在改动目标类的前提下实现代理了,而且不会像静态代理那样致使类爆炸。
先给出一个Cglib动态代理的demo
【思考题一】为何CGLIB代理能够直接对类进行代理,而JDK代理却必定要实现接口呢?答案见问末!!!
核心类是实现了MethodInterceptor的CGlibProxy类
能够看到其中实现了方法intercept,先是在目标函数被调用前实现本身的业务,好比before()和after(),以后再经过 proxy.invokeSuper(obj, args) 触发目标函数。
【思考题二】为何这里不像JDK代理那样,直接使用反射[method.invoke(target, args)]触发目标函数?答案见问末!!!
最后给出入口类
最后给出运行类,运行类以下
能够看到运行结果
原理很简单 在CglibProxyMain中hello调用say的时候,因为Hello已经被“代理”了,因此在调用say函数的时候实际上是调用CGlibProxy类中的intercept函数。
动态代理的相关原理已经讲解完毕,接下来让咱们回答如下几个思考题。
「思考解惑一」为何CGLIB代理能够直接对类进行代理,而JDK代理却必定要实现接口呢? 能够从咱们上面的例子看出,在JdkProxy类中取得代理类的方式是 (T)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this) 而在CGlibProxy类中取得代理类的方式是 (T) Enhancer.create(cls, this) 两种取得代理类的方式不一样,致使了一个须要实现接口,一个不须要。
「思考解惑二」为何这里不像JDK代理那样,直接使用反射[method.invoke(target, args)]触发目标函数? 首先要进行反射触发函数,要取得对应的method,以及该method所属对象,也就是target,再次是args方法参数,而咱们看下调试界面
能够从界面看到,目标对象obj并非Hello对象,二是被CGLIB代理过的对象,所以没法像JDK代理那样直接经过反射搞定。