Spring AOP源码流程解析

    Spring之所以成为java工程师界的“南天一柱,万世楷模”,除了有帮我们解决对象管理的IOC(可见我上一篇文章),还有对面向对象编程的有力补充的AOP(Aspect Oriented Programming)面向切面编程。还是那个古老的话题,专业术语可以帮我简化交流复杂度,却对概念进行了封装,让其变得晦涩难懂。所以我们需要搞清楚的第一点就是何为“面向切面”?为什么一定需要“面向切面”?不需要行不行?

    这里补充一句,昨天有朋友跟我反馈了下,希望我可以多聊些Spring里的设计模式。首先我非常感谢有这些能够认真看完我这个菜鸡博客并且经过思考还提出宝贵意见的朋友,也正是你们我才能乐此不疲的继续写下去^_^!但我个人的观点是我们拿到一个问题,正常逻辑是先去分析问题,然后解决问题,至于解决方案,那是仁者见仁智者见智的事儿。就像面向切面也不是非要动态代理不可,用内部类一样可以做到。当然了,Spring AOP中确实相比于IOC要复杂的多,所以自然有相当的不错的设计模式模式,后面咱们再细聊这个问题。首先,我们还是先把目光放到我们最开始的问题上,不忘初心嘛~~

    软件工程,不仅仅是实现一个功能就完事了,随着时间的推移,版本的迭代,会产生不计其数的需求变更。有一句笑话叫作“写时一时爽,重构火葬场”讲的就是写代码的人没有考虑过未来的变更场景,导致修改时候的工作量如同再造,那你加班加到猝死不能也怪资本主义丑陋的面目吧,哈哈哈。所以我们希望的就是能否有这样的一个功能,当我们要往1000个类上面加一个功能时候,不需要修改这1000个类,而是有一个名为切面的类,里面写上我们需要添加的功能,然后分别切开那1000个类,悄悄的帮我们把功能填进去。也正是这么个奇思妙想,造就了现如今的伟大的AOP模块!

    在我刚开始学习Spring AOP的时候也去网上找了很多博客,其实他们大致都是千篇一律的讲JDK动态代理和Cglib,然后大刀阔斧的在讲代理模式,适配器模式这类设计模式。我不是觉得他们讲的不好讲的不对。只是他们都在很细化的讲“庐山”的某一花某一草某一木,我看完之后还是不是很清楚这个“庐山”真面目。我不知道你们有没想过这个问题。那些博客说了那么多代理对象原理,请问这个代理对象什么时候将我这个正常的对象给狸猫换太子成代理对象?所以,为了给我的好奇心买单,我开始着手于AOP的源码。

    Spring的IOC是基石,所以即便是AOP也会将对象托管给IOC容器。回到最开始的需求,我既然把对象给你Spring管理,自然存在向你索取的需求,所以我还是以getBean为线索展开。开始之前,我先解释一下几个概念,因为Spring的类的含义往往从它的名字中暴露出来,可以减少阅读源码负担。

1.  Advice:通知。虽然我至今还不清楚老外为什么取名字,所以不管它语言含义。Advice是AOP联盟定义的接口,代表切面行为。Spring自身提供了三种切面切入的方式,落实到Spring源码中的类是MethodBeforeAdviceInterceptor,AfterReturningAdviceInterceptor,ThrowsAdviceInterceptor。也就是我们熟悉在目标方法之前之前执行切面逻辑,目标方法返回之前执行切面逻辑,抛出异常的时候执行切面逻辑

2.  PointCut:切点。这个比较容易理解,就是要被代理的方法,也就是我们所说的目标方法

3. Advisor:通知器。封装Advice,PointCut。因为我们通常在配置文件中声明一个切面配置的时候,Advice和Pointcut都是成对出现的,不是吗?

定位需要代理的bean


返回代理bean

    上述的匹配方法是采用的AspectJ的库函数,具体做法目前不是我们关心的重点,其实也就是玩转字符串,匹配 <property name ="expression" value ="execution(* main.Knight.say(..))"> 中的Knight是不是和我这个bean(类型为main.Knight)一致,就像JSON工具包,虽然我也不知道他的具体做法,但我知道底层大致的做法足矣。

    如此一来,我们就清楚了AOP是如何做到“狸猫换太子”了。接下来就是众多博客都有讲解的JDK Proxy 和Cglib做法了。Spring源码在决定生成代理对象的时候会去判断下目标类有没有实现接口,如果实现了接口就生成Proxy代理对象,如果没有实现接口就通过Cglib生成一个类去继承目标类,复写目标类中需要加入切面逻辑的方法,所以目标类方法不能声明称final类型的。这就是二者的区别!这不是我最关心的地方,其实若想知道他们二者的本质区别,只要写一个小demo,然后用反编译的工具就可以一探二者究竟,这里就不赘述了,下面我也只介绍Proxy。

    Spring中JDK 的Proxy类名曰JdkDynamicAopProxy,也就是我们耳熟能详的动态代理。这个类主要实现了AopProxy,InvocationHandler接口

JdkDynamicAopProxy方法

    所谓的代理模式就是,当你对代理对象调用目标类的方法时候,JVM都会触发上图这个invoke方法,以改变方法调用的轨迹,实现切面逻辑的悄悄织入,这是被API封装掉的,如果想落实真相,可以去反编译。我们先看看invoke里面都做了什么?


Proxy中的方法加强真相


methodInterceptor.invoke()真相

    没错,methodInvocation.procceed()就是一个回调方法。


methodInvocation.procceed()

    看到没有,这个procceed就是通过反射的机制调用目标对象的方法。只不过他的调用需要交给拦截器来调用,也就是所谓的回调。

    如此一来Spring实现AOP的脉络就很清楚了,真实的Spring AOP是非常非常复杂的。除非亲自去看,不然很难在有限的篇幅讲清楚,而且本人还是那句话,解决方案是应运着需求的,技术总是后知后觉得,有印象的朋友可能会记得我在前面提到过Spring AOP有用到适配器这种设计模式,为什么我到现在都没提及过,那是因为我给出都是Spring AOP代理过程中最理想最简单的情况,自然不需要想尽办法利用多态的性质去简化代码。

    还记得我前面留下的问题,为什么methodInterceptor就当它是Advice了。因为我给出的切面逻辑是单一的,真实的使用切面逻辑是三个,before,afterReturning,throws。所以Spring源码在执行目标方法之前是有一个拦截链的,里面有多个拦截器,这多个拦截器执行完成之后才会执行目标方法。

    适配器是吧,直接上图!


DefaultAdvisorAdapterRegistry.getInterceptors(Advisor advisor)

    Spring源码是想干啥呢?我解释一下,我现在手上有一堆从配置文件获得的Advice,但我想获得我想要的拦截器,因为拦截器才是真正改变目标方法的地方。但是有由于advice这个对象究竟是before,afterReturning还是throws不得而知,所以我们用适配器去适配它,目的是为了生成对应的不同的拦截器。adapters是一个List,里面分别装有MethodBeforeAdviceAdapter,AfterReturningAdviceAdapter,ThrowsAdviceAdapter。我们的做法就是遍历它们,然后去supportsAdvice()。下面我举一个AfterReturningAdvice例子,其他两个可类推。


AfterReturningAdviceAdapter.supportsAdvice(Advice advice)

    这就是所谓的适配器设计模式。本质还是面向对象的多态,不同的玩法而已,而且instanceof不到万不得以的地步不要使用,因为这样会是代码的扩展性变得不好。至于简单工厂,单例模式这种老掉牙的设计模式就不必也来Spring中凑数了

    好了,Spring AOP也落下帷幕了。我虽然还没工作,但是实习也大半年了,我自己的感觉真实生产中Spring IOC比AOP使用的要多一些,或者说AOP是衣架子,先人已经做的挺好的,我们只需要改一改换一换衣架上的衣服罢了。而且Spring AOP的使用意识其实比Spring AOP的原理在生产中来的更实际一些。

    最后,分享一波阿里云的面试心得:我今年2月份的时候面试了下阿里云的实习生。其中有一个面试题就是针对我在上一家公司的业务,业务是这样的:镜像删除,既要将数据库中的镜像信息删除,还要将调用镜像仓库服务进程的API,实现镜像描述信息和实际的镜像文件都删除。我的回答也比较耿直,因为确实是这么做的,我说我先将数据库中的镜像信息删除,然后删除镜像文件,并且为了提升前端的响应速度,我采用异步的方式,创建子线程的方式去调镜像仓库的API。面试官问:那如果数据库出现异常会怎么样?我:数据会回滚,这是Spring的事务管理机制。面试官:好,那数据回滚了,镜像仓库的镜像没了,你怎么办?我:。。。

    结果可想而知,我挂了。其实这是一个非常简单的问题,只怪当初我对AOP的理解和数据库事务的理解太过僵硬。首先我没有抓住事务的本质,为什么只有操作数据库才算是一个事务?事务的本质就是将一堆行为作为一个整体来看,要么一起成功要么一起失败。就像数据库删除镜像信息和删除镜像文件,这两样事情就是要么一起成功要么一起失败,所以应该将二者视为一个事务。

    因此解决方案很简单,只要在这个业务函数的整体上加一个@Transaction即可,@Transaction就是一个AOP,在业务函数开始之前transaction.start()。然后try catch住整个方法,无论数据库还是调用镜像仓库API只要抛出异常执行transaction,rollback()。方法返回之前调用transaction.commit()。是不是就对应了before,afterReturning,throws啊。

    Spring的实现是复杂的,如何讲清楚他,从构思到攥写都是相当耗时耗力的。我也总算告一段落了,希望这两篇博客可以给予JAVA开发的同学一点帮助。

    后续的话我还会写一些Mysql,Hadoop,Kubernetes博客,希望技术不再是逼格的代表,而是艺术的化身!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • 说明:本文主要内容来自慕课网。配合视频食用口味更佳。主要是顺着已经学习的视频顺序总结一遍,以深化理解和方便日后复习...
    stoneyang94阅读 849评论 3 5
  • 概述 Spring是什么? Spring是一个开源框架,为了解决企业应用开发的复杂性而创建的,但是现在已经不止于企...
    琅筑阅读 1,158评论 2 8
  • 亲爱的时光: 您好。 我感谢你――让我变的如此坚强。 儿时的我畏惧困难,畏惧挫折,是你,默...
    醉梦似水流年转阅读 138评论 0 0
  • 泪光 嘿 你好 眼角的泪光 我是飞舞的蝶 是梦中的影 是你的我 嘿眼角的泪光 你在躲避我吗? 你也觉得我讨厌? 你...
    她笑了阅读 287评论 0 1
  • 合作 “警官,我知道你在苦恼什么,我大概,可以帮助你。我可真是个合作的好市民啊。”陆启文的背后,是从病房紧随他出来...
    花京体验阅读 284评论 1 1