用最通俗的方法讲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                ─── <目标对象>

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值