温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

如何用最通俗的方法讲spring中的AOP

发布时间:2021-10-20 17:35:50 来源:亿速云 阅读:125 作者:柒染 栏目:大数据

这篇文章给大家介绍如何用最通俗的方法讲spring中的AOP,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

写这个系列的目的(可以跳过不看)

自己写这个系列的目的,是因为自己是个比较笨的人,我曾一度怀疑自己的智商不适合干编程这个行业.因为在我自学的过程中,看过无数别人的文章,博客,心得.可那一大堆的术语,技术基础,根本就难以阅读理解.再加上网上文章良莠不齐,很多文章都是写给已经懂的人看的. 一个例子: Q:什么是控制反转. A:将对在自身对象中的一个内置对象的控制反转,反转后不再由自己本身的对象进行控制这个内置对象的创建,而是由第三方系统去控制这个内置对象的创建. ??? 这种专业的解释,在我看来就不是给学习的人看的,而是给已经有相当经验的人读的. 难道不能用更加感性的解释这些东西吗? 编程语言作为一门语言,我认为一定程度上是相当感性的东西,既然我们互相说话时可以听懂,那么,这些技术就可以在一定程度上,完全可以用感性的方式解释.

AOP

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      |
     └────────────────┴────────────────┴────────────────┴────────────────┴────────────────┴────────────────┘
	 
	 我只需要声明一个类,就可以该类中的方法插入到任意类的位置	

有些人说,用代理模式就可以解决上面所说的编程时的一系列问题. 没错! 面向切面 ─── 是思想. 代理模式 ─── 是思想的实现.

二. 画个图片

上面画过了

三. 写个代码

只讲概念,后面讲例子

四. 说个人话

切面的几大概念

1.切面 (Aspect) :

拓展的类 概念 : Aspect 声明类似于Java中的类声明,在Aspect中会包含着一些切点以及相应的增强. 人话 : 创建一个类,这个类中的<增强>将插入到<目标对象>代码中.

2.连接点 (Joint point) :

调用点 概念 : 表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point. 人话 : 被<切点>所包含的内容, 如切点为 userService.add()方法 : 连接点就是userService.add() 如切点为 com.xxx包下的所有service : com.xxx包下的所有service的任意一个方法 如切点为 com.xxx包下的所有add开头的方法 : com.xxx包下的所有add开头的方法

3.切点 (Pointcut):

在哪里增强 概念 : 表示一组<连接点>,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的增强将要发生的地方.或是我们要织入的地方 人话 : 要往哪里插入 如 userService.add()方法 或 com.xxx包下的所有service方法 或 com.xxx包下的所有add开头的方法

4.增强 (Advice) :

拓展方法 概念 : Advice 定义了在<切点>里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个<连接点>之前、之后还是代替执行的代码. 人话 : 切面类中需要定义的,连接点方法调用前的拓展,之后的拓展,或者之前和之后的拓展.

5.目标对象 (Target) :

被增强的目标 概念 : 增强(Advice) 的目标对象.. 人话 : 被增强的目标

6.织入 (Weaving):

插入的动作 概念 : 将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程. 人话 : 将切面类插入进去

例如:
我们要在service的add方法里里增加日志
┌───────────────┐
|   Controller  |
└───────────────┘
─────────────────────────────────────    <-- com.xxx.service.UService  ─── <切点>
┌───────────────┐      ┌────────────┐    <-- log.info所在的类           ─── <切面>
| UService.add  |      |  log.info  |    <-- log.info方法就是           ─── <增强>
├───────────────┤      └────────────┘
|      Dao      |
└───────────────┘

UService.add的调用就是   ─── <连接点>
UService                ─── <目标对象>

到这里你应该理解什么是面向切面了,如果你还是不理解,不怕,可能是我的表达方式不适合你,全网有很多对于面向对象的优秀理解. 虽然筛选好文章需要时间,我可以帮你找出来,但其实在筛选的过程中,更是一个理解的过程.

关于如何用最通俗的方法讲spring中的AOP就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI