在实际研发中,Spring是咱们常常会使用的框架,毕竟它们太火了,也所以Spring相关的知识点也是面试必问点,今天咱们就大话Aop。 特意在周末推文,由于该篇文章阅读起来仍是比较轻松诙谐的,固然了,更主要的是周末的我也在充电学习,但愿有追求的朋友们也尽可能不要放过周末时间,适当充电,为了走上人生巅峰,迎娶白富美。【话说有没有白富美介绍(o≖◡≖)】面试
接下来,直接进入正文。spring
咱们都知道Java是一种面向对象编程【也就是OOP】的语言,不得不说面向对象编程是一种及其优秀的设计,可是任何语言都没法十全十美,对于OOP语言来讲,当须要为部分对象引入公共部分的时候,OOP就会引入大量的重复代码【这些代码咱们能够称之为横切代码】。而这也是Aop出现的缘由,没错,Aop就是被设计出来弥补OOP短板的。Aop即是将这些横切代码封装到一个可重用模块中,继而下降模块间的耦合度,这样也有利于后面维护。编程
学过Spring的都知道,Spring内比较核心的功能即是Ioc和Aop,Ioc的主要做用是应用对象之间的解耦,而Aop则能够实现横切代码【如权限、日志等】与他们绑定的对象之间的解耦,举个浅显易懂的小栗子,在用户调用不少接口的地方,咱们都须要作权限认证,判断用户是否有调用该接口的权限,若是每一个接口都要本身去作相似的处理,未免有点sb了,也不够装x,所以Aop就能够派上用场了,将这些处理的代码放到切片中,定义一下切片、链接点和通知,刷刷刷跑起来就ojbk了。app
想要了解Aop,就要先理解如下几个术语,如PointCut、Advice、JoinPoint。接下来尽可能用白话文描述下。框架
PointCut【切点】 其实切点的概念很好理解,你想要去切某个东西以前总得先知道要在哪里切入是吧,切点格式以下:execution(* com.nuofankj.springdemo.aop.Service.(..)) 能够看出来,格式使用了正常表达式来定义那个范围内的类、那些接口会被当成切点,简单明了。ide
Advice Advice行内不少人都定义成了通知,可是我总以为有点勉强。所谓的Advice其实就是定义了Aop什么时候被调用,确实有种通知的感受,什么时候调用其实也不过如下几种:函数
JoinPoint【链接点】 JoinPoint链接点,其实很好理解,上面又有通知、又有切点,那和具体业务的链接点又是什么呢?没错,其实就是对应业务的方法对象,由于咱们在横切代码中是有可能须要用到具体方法中的具体数据的,而链接点即可以作到这一点。源码分析
先给出两个业务内的接口,一个是聊天,一个是购买东西 post
execution(* com.nuofankj.springdemo.aop.Service.(..))学习
Advice是
Before
JoinPoint【链接点】是
MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod();
代码浅显易懂,其实就是将ChatService和BuyService里边给userId作权限校验的逻辑抽出来作成切片。
那么如何拿到具体业务方法内的具体参数呢? 这里是定义了一个新的注解
送佛送到西,不对,撸码撸整套,接下来给出运行的主类
运行结果以下
首先给出Main类
能够看到我这里用的是AnnotationConfigApplicationContext,解释下
AnnotationConfigApplicationContext是一个用来管理注解bean的容器,因此我能够用该容器取得我定义了@Service注解的类的实例。
打断点后,启动程序,咱们能够看到TestDemo的实例在idea的表现是这样的
而BuyService的实例却不一样
咱们能够从看到BuyService是SpringCGLIB强化过的一个实例,那么问题来了
带着这些疑问,让咱们一步步从Spring源码中找到答案。
为何BuyService被强化过而TestDemo没有?
这个问题比较简单,咱们能够看回上面我对切片的定义
能够从代码中看出,我定义的切点是*Service命名的类,而TestDemo很明显不符合这个设定,所以TestDemo逃过被强化的命运。
SpringCGLIB又是什么?
CGLIB其实就是一种实现动态代理的技术,利用了ASM开源包,先将代理对象类的class文件加载进来,以后经过修改其字节码而且生成子类。结合demo来解读即是SpringCGLIB会先将BuyService加载到内存中,以后经过修改字节码生成BuyService的子类,该子类即是强化后的BuyService,上文看到的强化后的实例即是该子类的实例。
Spring是在何时生成一个强化后的实例的?
这个便厉害了,首先,咱们要先从Spring如何加载切片入手。
【思考Time】 为何我会选择从切片入手呢?缘由很简单,Spring就是由于发现了切片,而且对切片进行解析后才知道了要强化哪些类。
切片的处理第一步即是要加上@Aspect注解,学过注解的都知道,注解的做用更多的是标志识别,也就是告诉Spring这个类要作相关特殊处理,所以咱们能够基于该认识,反调该注解使用的地方
能够从截图看出,我反调了@Aspect后定位到了AbstractAspectJAdvisorFactory类中的hasAspectAnnotation函数,而且携带参数clazz,所以我猜想该接口就是用来识别clazz是否使用了注解@Aspect的地方,因而我打上了断点,而且加了条件 clazz == AuthAspect.class ,从新启动后
咱们看到确实被断点到了,能够得出个人猜想是对的。 咱们先看下断点后作了什么事情,以后再看下具体是哪里进行了扫描。在断点处按F8继续往下走,最后发现
没错,能够看到最终是构建成了一个Advisor对象 ,而且放入了BeanFactoryAspectJAdvisorsBuilder中的advisorsCache中,这样意味着Spring最终会将使用了@Aspect注解的类构建成Advisor对象后保存进BeanFactoryAspectJAdvisorsBuilder.advisorsCache中。
接下来咱们看看具体是哪里进行了使用@Aspect注解的相关类的扫描,此次我断点的地方在BeanFactoryAspectJAdvisorsBuilder中的advisorsCache调用了put的地方。
【思考Time】 为何我会选择在advisorsCache调用了put的地方打断点呢?缘由很简单,由于咱们上面已经分析出@Aspect注解的类构建成Advisor对象后保存进BeanFactoryAspectJAdvisorsBuilder.advisorsCache中,而我经过反调知道put的地方只有一个,所以我能够判定在此处打断点能够知道到底哪里进行了扫描的操做。
经过打断点后我从idea的Frames面板中看到
没错,作了扫描@Aspect注解的扫描器是AbstractAutoProxyCreator类
咱们能够从中看到AbstractAutoProxyCreator最终实现了InstantiationAwareBeanPostProcessor接口。
【思考Time】 这个接口有什么做用呢?具体能够看我前阵子写的一篇文章:mp.weixin.qq.com/s/r2OEqsap6…
如今已经找到了扫描注解的地方,而且咱们也看到了最终是生成了Advisor对象 ,而且放入了BeanFactoryAspectJAdvisorsBuilder中的advisorsCache中,那么Spring是在何时生成强化后的实例的呢? 接下来个人切入点是AbstractAutoProxyCreator中的postProcessAfterInitialization接口。
【思考Time】 之因此会选择AbstractAutoProxyCreator为切入点,是由于经过命名能够看出这是SpringAop用来构建代理[强化]对象的地方,而且因为SpringCGLIB是先将目标类加载到内存中,以后经过修改字节码生成目标类的子类,所以我猜想强化是在目标类实例化后触发postProcessAfterInitialization的时候进行的。
所以我在postProcessAfterInitialization接口中作了断点,而且加了调试条件。
能够看到我这里断点到了ChatService这个类。
【思考Time】 为何专门断点ChatService这个类?之因此会专门定位这个类,由于个人切面的目标类就包含了ChatService,经过定位到该类,咱们能够一步步捕捉Spring的强化操做。
咱们能够看到,生成强化后的对象就藏在wrapIfNecessary中。
【思考Time】 为何我会知道是生成强化后的对象就藏在wrapIfNecessary中呢?由于我经过调试发现,在调用了wrapIfNecessary接口后,返回的对象是强化后的对象。
那么问题来了,为何Spring会知道ChatService类须要进行进行强化呢?咱们能够从wrapIfNecessary中走入更深一层,经过调试,能够看到
在此处会从advisorsCache中根据aspectName取出对应的Advisor。拿到Advisor后,即是进行过滤的地方了,经过F8日后走,能够看到过滤的地方在AopUtils.canApply接口中。
能够看到此处传进来的targetClass符合切面的要求,所以能够进行构建强化对象。 接下来让咱们看下真正产生强化对象的地方了
咱们能够看到在AbstractAutoProxyCreator的createProxy函数中看到,最后会构造出一个强化后的chatService。 那么createProxy又作了什么呢?经过断点一层层深刻后,发现最后会到达
经过源码分析,咱们发如今AbstractAutoProxyCreator构建强化对象的时候是调用了createAopProxy函数,重点来了,咱们能够看到针对targetClass,也就是ChatService作了判断,若是targetClass有实现接口或者targetClass是Proxy的子类,那么使用的是JDK的动态代理实现AOP,若是不是才会使用CGLIB实现动态代理。
那么JDK实现的动态代理和CGLIB实现的动态代理有什么区别吗? 首先动态代理能够分为两种:JDK动态代理和CGLIB动态代理。从文中咱们也能够看出,当目标类有接口的时候才会使用JDK动态代理,实际上是由于JDK动态代理没法代理一个没有接口的类。JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,而CGLIB是针对类实现代理,主要是对指定的类生成一个子类,而且覆盖其中的方法。
原本想一篇文章说完源码跟踪分析Aop和Aop的实现机制代理模式,发现源码跟踪分析已经很占篇幅了,所以没办法只能再开一篇文章专门阐述Aop的实现机制代理模式,期待下篇文章。
你们都知道,我有个习惯,在动手写一篇文章以前会先将该文章相关的资料仔细琢磨一遍,而后再结合源码再调试一遍,结果,说好的
看源码也确实是
源码确实有进行了是不是接口的判断,可是问题来了,我调试的时候发现不管代理类是否有接口,最终都会被强制使用CGLIB代理,没办法,只能翻看SpringBoot的相关文档,最终发现原来SpringBoot从2.0开始就默认使用Cglib代理了,好家伙,怪不得我调试半天找不到缘由。
那么如何解决呢?确定是经过配置啦,按照以下配置便可
在application.properties文件中配置 spring.aop.proxy-target-class=false
便可。
【划重点】 曾经碰见过面试官问,SpringBoot默认代理类型是什么?看完该篇文章,咱们就能够果断的回答是Cglib代理了。经过调试代码发现的规则,我想我这辈子都不会忘记这个默认规则。
简单来讲,就是在运行的时候为目标类动态生成代理类,而在操做的时候都是操做代理类,代理模式有个显而易见的好处,那即是能够在不改变对象方法的状况下对方法进行加强。试想下,咱们在你必需要懂的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
核心类是实现了MethodInterceptor的CGlibProxy类
能够看到其中实现了方法intercept,先是在目标函数被调用前实现本身的业务,好比before()和after(),以后再经过 proxy.invokeSuper(obj, args) 触发目标函数。
最后给出入口类
最后给出运行类,运行类以下
能够看到运行结果
原理很简单 在CglibProxyMain中hello调用say的时候,因为Hello已经被“代理”了,因此在调用say函数的时候实际上是调用CGlibProxy类中的intercept函数。