这篇文章给大家介绍如何用最通俗的方法讲spring中的AOP,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。
自己写这个系列的目的,是因为自己是个比较笨的人,我曾一度怀疑自己的智商不适合干编程这个行业.因为在我自学的过程中,看过无数别人的文章,博客,心得.可那一大堆的术语,技术基础,根本就难以阅读理解.再加上网上文章良莠不齐,很多文章都是写给已经懂的人看的. 一个例子: Q:什么是控制反转. A:将对在自身对象中的一个内置对象的控制反转,反转后不再由自己本身的对象进行控制这个内置对象的创建,而是由第三方系统去控制这个内置对象的创建. ??? 这种专业的解释,在我看来就不是给学习的人看的,而是给已经有相当经验的人读的. 难道不能用更加感性的解释这些东西吗? 编程语言作为一门语言,我认为一定程度上是相当感性的东西,既然我们互相说话时可以听懂,那么,这些技术就可以在一定程度上,完全可以用感性的方式解释.
AOP是面向切面. 得,说了白说. 什么是面向切面?这是个问题,但我们先不着急回答.
我们的代码结构,大多都是MVC模式.那我们用MVC举个简单的例子. MVC模式的话,最常用的spring + springMVC + Mybatis,大多数是下面的样子.
┌───────────────┐ | view | 用户界面 ├───────────────┤ | Controller | 控制层 ├───────────────┤ | Service | 服务层,也叫业务层 ├───────────────┤ | Dao | 持久层,也叫数据访问层 ├───────────────┤ | Database | 数据库 └───────────────┘
外加Model作为数据载体在各个层之间传来传去 只要是接触过JavaWeb开发的,上面都看的懂,如果这个不懂暂时还是不要深究这些知识,先以框架应用为主.
这个Service是个UserService,作用只有一个:保存用户.
┌────────────────────┐ | view | ├────────────────────┤ | Controller | ├────────────────────┤ | UserService.add | ───> 新增用户 ├────────────────────┤ | Dao | ├────────────────────┤ | Database | └────────────────────┘
现在,有需求要我们在Service中增加日志,开发这个功能的同事不在了,经理要求我们来做这个需求. 那么根据面向对象的理念. 调用service方法前要调用一个日志方法,调用完后要调用一个日志方法,那么代码中就要增加两行代码. 于是,代码成了这样:
┌────────────────────┐ | view | ├────────────────────┤ | Controller | ├────────────────────┤ | log.info... | ───> 调用日志 | UserService.add | ───> 新增用户 | log.info... | ───> 调用日志 ├────────────────────┤ | Dao | ├────────────────────┤ | Database | └────────────────────┘
这么写正确吗? 答案是当然正确,需求解决了,日志正常保存了,领导很高兴.
第二天,又有需求要控制service的事务,OK,然后代码变成了这样.
┌────────────────────┐ | view | ├────────────────────┤ | Controller | ├────────────────────┤ | Transaction | ───> 事务管理 | log.info... | ───> 调用日志 | UserService.add | ───> 新增用户 | log.info... | ───> 调用日志 | Transaction | ───> 事务管理 ├────────────────────┤ | Dao | ├────────────────────┤ | Database | └────────────────────┘
第三天,项目经理要求我们记录方法的运行所用时间,小意思,然后代码变成了这样
┌────────────────────┐ | view | ├────────────────────┤ | Controller | ├────────────────────┤ | beginTime | ───> 开始时间 | Transaction | ───> 事务管理 | log.info... | ───> 调用日志 | UserService.add | ───> 新增用户 | log.info... | ───> 调用日志 | Transaction | ───> 事务管理 | endTime | ───> 结束时间 ├────────────────────┤ | Dao | ├────────────────────┤ | Database | └────────────────────┘
第四天,客户要求我们实现新增用户时的权限校验,好说,代码变成了这样
┌────────────────────┐ | view | ├────────────────────┤ | Controller | ├────────────────────┤ | permissions | ───> 校验权限 | beginTime | ───> 开始时间 | Transaction | ───> 事务管理 | log.info... | ───> 调用日志 | UserService.add | ───> 新增用户 | log.info... | ───> 调用日志 | Transaction | ───> 事务管理 | endTime | ───> 结束时间 ├────────────────────┤ | Dao | ├────────────────────┤ | Database | └────────────────────┘ /* 不知道你们怎么想,但我现在,仍然觉得这种写法挺好的. 通俗易懂,可读性强到不知道哪里去了. 当然有个前提,这个项目只有这一个方法. */
第五天,开发UserService的同事回来一看,一脸懵逼,明明走的时候只有一行,现在却有一大堆不知道什么意思的代码存在. 得了,他在此基础上开发,增加各种方法,功能. 随着需求的变动,慢慢的,一百个service都变成了上面那个样子. 接着记录日志的功能有了变动,你发现会影响到调用的类,搜了一下整个项目,发现有特么几千个地方都调用了这个方法. 你心中一万头草泥马掠过,然后提出了辞职. 接着有个新人接手了这个项目,打开一看,这映入眼帘的一大坨屎一样的是特么什么东西?? 硬着头皮改了几版,每个版本都有BUG,领导一怒之下,项目作废了,重金找了几个人重新做了.
==那么问题来了== 现在的需求搞砸了可以辞职,项目烂了可以重做. 那世界上第一个遇到这个问题的人,是怎么解决的?技术就是这技术,重做不还是一样会遇到这些问题吗?那我还怎么拓展功能呢?难道所有项目最终都会走向这种绝境? 于是前人开始探索:
┌───────────────┐ | Controller | | ┼───────> 拓展方法写在这? ├───────────────┤ | Service | ├───────────────┤ | ┼───────> 拓展方法写在这? | Dao | └───────────────┘
这不都是一回事吗?根本不解决问题.
我等凡夫俗子解决不了,但世界上有的是自带外挂的人. 于是,有那么几个人就找到了解决办法! 既然我要拓展方法,就要在你的方法里写代码,那么我能不能在不改你代码的基础上来拓展呢?
==比如在这个地方!==
┌───────────────┐ | Controller | ├───────────────┼───────> 比如,写在这! | Service | └───────────────┘
what the hell ?????? 你是让我在一个莫须有的地方写代码?
当然不是,大神的意思是代码要这样写.
┌───────────────┐ | Controller | ┌───────────────┐ ├───────────────┼──────┤ log.info... | | Service | └───────────────┘ └───────────────┘
但执行的时候,会是这样
┌───────────────┐ | Controller | ├───────────────┤ | log.info... | | Service | └───────────────┘
what the hell ??????????????????????????????? 这是为什么?
==答案很简单:== 首先你既然打算了解AOP,那你应该是有一些Java基础的. 我们都知道Java文件是没有用的,这就相当于一个txt文件,当程序真正运行的时候,是把Java文件通过编译器编译成class文件来运行的. 那么我们好像看到重点了,编译器! 既然最终运行的是class文件,而class文件又是编译器生成的,那我们是不是可以在编译器上动动手脚呢? 没错,想要达成上面的效果,就需要一个不同的编译器. 把日志这段代码编译到service中 当然了,这个编译器不需要我们开发,而是早有人搞定了.(若是我能开发,我早就去谷歌开发者大会上做演讲了= =) 这就是大名鼎鼎的 :==ajc编译器==(他属于AspectJ框架,可以单独使用此框架写个例子,可以加深理解)
1. 想象一下,有这么两坨东西 ┌────────────────────┐ | Controller.java | ├────────────────────┤ ┌─────────────────┐ | Service.java | | log.info.java | └────────────────────┘ └─────────────────┘ 2. 然后开始编译 : 编译器开始把Service.java中的代码编织成一个class文件. 3. ajc编译器开始编译 : ajc开始把log.info.java编织插入到service中. ┌────────────────────┐ | Controller.java | _________________ ├────────────────────┤ _/ log.info.java | | Service.java | \ ________________| └────────────────────┘ 4. 就像上面,把log.info织入到这个这个方法中. 5. 最终形成的calss文件,就是这样 ┌───────────────┐ | Controller | ├───────────────┤ | log.info... | | Service | └───────────────┘
成了,问题解决了.有了这个编译器,前面的问题有迎刃而解. ==ps : ajc编译器并不是唯一解决办法==
那么问题又又来了,==面向切面是什么?== 也许你已经有答案了.
┌───────────────┐ | Controller | ┌──────────────────────────────────┐ ├───────────────┼──────> | 我们在这个角度写代码,就是面向切面. | | Service | | | ├───────────────┼──────> | 我们在这个角度写代码,就是面向切面. | | Dao | └──────────────────────────────────┘ └───────────────┘
我们站在那条线的角度写代码,就是面向切面. 那条线,是不是将原本的代码分成了两个部分呢? 更形象一点
┌───────────────┐ | Controller | └───────────────┘ ───────────────────────────────────── 这条线,像不像把整个代码切开的一条线呢?那么这条线,就叫切面 ───────────────────────────────────── 如 : 日志调用 ───────────────────────────────────── 如 : 调用时间 ───────────────────────────────────── 如 : 事务管理 ┌───────────────┐ | Service | ├───────────────┤ | Dao | └───────────────┘
上面的每一个如,就是一个切面类,也相当于一个切面,毕竟没有人规定我们必须只有一个切面不是?
面向切面是一种人们面对难题时的解决方案,他并不是一个新的语言或是新的技术. 他消除了面向对象的一系列编程陋习.或者说解决了面向对象的一些缺点.
到了这里,我相信有些人还是不太懂面向切面的意义或者作用是什么,没关系,不要气馁,你一定比我当时可聪明多了.请继续往下看.
这张图可能并不太能直观的表达面向切面意义有多么重大. ┌───────────────┐ | Controller | └───────────────┘ ───────────────────────── ┌───────────────┐ | Service | ├───────────────┤ | Dao | └───────────────┘ 那么,这张图呢? ┌────────────────┬────────────────┬────────────────┬────────────────┬────────────────┬────────────────┐ | Controller1 | Controller2 | Controller3 | Controller4 | Controller5 | Controller6 | └────────────────┴────────────────┴────────────────┴────────────────┴────────────────┴────────────────┘ ─────────────────────── 面向切面的方法,可以织入到所有的service中,而不需要改变service的代码 ───────────────────────── ─────────────────────── 权限校验 ─────────────────────────────────────────────────────────────────────────────── ─────────────────────── 日志调用 ─────────────────────────────────────────────────────────────────────────────── ─────────────────────── 调用时间 ─────────────────────────────────────────────────────────────────────────────── ─────────────────────── 事务管理 ─────────────────────────────────────────────────────────────────────────────── ┌────────────────┬────────────────┬────────────────┬────────────────┬────────────────┬────────────────┐ | Service1 | Service2 | Service3 | Service4 | Service5 | Service6 | └────────────────┴────────────────┴────────────────┴────────────────┴────────────────┴────────────────┘ ─────────────────────── 面向切面的方法,可以织入到所有的Dao中,而不需要改变Dao的代码 ───────────────────────────────── ─────────────────────── 日志调用 ─────────────────────────────────────────────────────────────────────────────── ─────────────────────── 调用时间 ─────────────────────────────────────────────────────────────────────────────── ┌────────────────┬────────────────┬────────────────┬────────────────┬────────────────┬────────────────┐ | Dao1 | Dao2 | Dao3 | Dao4 | Dao5 | Dao6 | └────────────────┴────────────────┴────────────────┴────────────────┴────────────────┴────────────────┘ 我只需要声明一个类,就可以该类中的方法插入到任意类的位置
有些人说,用代理模式就可以解决上面所说的编程时的一系列问题. 没错! 面向切面 ─── 是思想. 代理模式 ─── 是思想的实现.
上面画过了
只讲概念,后面讲例子
拓展的类 概念 : Aspect 声明类似于Java中的类声明,在Aspect中会包含着一些切点以及相应的增强. 人话 : 创建一个类,这个类中的<增强>将插入到<目标对象>代码中.
调用点 概念 : 表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point. 人话 : 被<切点>所包含的内容, 如切点为 userService.add()方法 : 连接点就是userService.add() 如切点为 com.xxx包下的所有service : com.xxx包下的所有service的任意一个方法 如切点为 com.xxx包下的所有add开头的方法 : com.xxx包下的所有add开头的方法
在哪里增强 概念 : 表示一组<连接点>,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的增强将要发生的地方.或是我们要织入的地方 人话 : 要往哪里插入 如 userService.add()方法 或 com.xxx包下的所有service方法 或 com.xxx包下的所有add开头的方法
拓展方法 概念 : Advice 定义了在<切点>里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个<连接点>之前、之后还是代替执行的代码. 人话 : 切面类中需要定义的,连接点方法调用前的拓展,之后的拓展,或者之前和之后的拓展.
被增强的目标 概念 : 增强(Advice) 的目标对象.. 人话 : 被增强的目标
插入的动作 概念 : 将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程. 人话 : 将切面类插入进去
例如: 我们要在service的add方法里里增加日志 ┌───────────────┐ | Controller | └───────────────┘ ───────────────────────────────────── <-- com.xxx.service.UService ─── <切点> ┌───────────────┐ ┌────────────┐ <-- log.info所在的类 ─── <切面> | UService.add | | log.info | <-- log.info方法就是 ─── <增强> ├───────────────┤ └────────────┘ | Dao | └───────────────┘ UService.add的调用就是 ─── <连接点> UService ─── <目标对象>
到这里你应该理解什么是面向切面了,如果你还是不理解,不怕,可能是我的表达方式不适合你,全网有很多对于面向对象的优秀理解. 虽然筛选好文章需要时间,我可以帮你找出来,但其实在筛选的过程中,更是一个理解的过程.
关于如何用最通俗的方法讲spring中的AOP就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。