3.1 Spring AOP概述

AOP的概念

AOP是Aspect-Oriented Programming(面向切面编程),利用AOP可以对业务逻辑的各个部分进行分离,使得解决特定领域问题的代码(比如日志记录、性能统计、安全控制、事务处理、异常处理等)从业务逻辑中独立出来,从而在改变这些行为的时候不影响业务逻辑的代码。

AOP的优点

  • 减少代码间的耦合性,使功能具有拔插性,保证自己代码的清洁型。
  • 能够让你只关注自己的代码,不需要关注切面是如何实现的。

AOP的相关术语

术语1:通知(Advice)

这里使用电力公司的工人去各家各户查电表为例。

查电表的工人接到公司老板下达的任务,即包括什么时候去查电表,以及去住户人家怎样查电表。和此相似,通知定义了切面做什么和什么时候去做(确切的说是程序员在通知中定义的)。除了定义切面要完成的工作(what job),还会定位什么时候(when)去履行这项工作。是在方法调用前,还是调用之后,还是前后都是,还是抛出异常时。

切面一共有五种通知:

1. Before 某方法调用之前发出通知。

2. After 某方法完成之后发出通知,不考虑方法运行的结果。

3. After-returning 将通知放置在被通知的方法成功执行之后。

4. After-throwing 将通知放置在被通知的方法抛出异常之后。

5. Around 通知包裹在被通知的方法的周围,在方法调用之前和之后发出通知。

术语2:连接点(Join Point)

有很多房子使用同一个电力公司提供的电,那么这些房子中的每一个都可以,都有机会被查电表。同样,在应用程序中会有好多的地方可以,或者有机会被通知申请完成通知自己的工作。这些机会(某个点)就被称为连接点。连接点是切面在应用程序执行过程中插入的地方,可能是方法调用的时候,异常抛出的时候。

术语3:切点(Pointcut)

很明显一位查电表的工人不能把所有的用电客户都拜访个遍,而是每一个工人都会有自己管辖的客户。同样,一个切面没必要通知(advice)应用程序中所有的连接点(join point)。切点(Pointcut)能够精确定位被切面通知的连接点。

比如一个类里,有15个方法,那就有几十个连接点,但是你并不想在所有方法附近都使用通知(使用叫织入),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。

术语4:切面(Aspect)

切面是通知和切点的结合(不包括连接点,其实连接点的定义就是为了让你好理解切点而产生的中间定义,没有很大实际意义)。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。就好比说电力公司的工人现在知道了他的一天要做些什么、什么时候去做、在哪做。

术语5:引入(introduction)

允许我们向现有的类添加新方法属性。这就是把切面(也就是新方法属性:通知定义的)用到目标类中。

术语6:目标(target)

引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被织入切面,而它自己则专注于业务本身的逻辑。

术语7:代理(proxy)

如何实现整套AOP机制的,都是通过代理。具体在下文会说明什么是代理。

术语8:织入(weaving)

把切面应用到目标对象并创建新的代理对象的过程。有3种方式,Spring采用的是运行时,为什么是运行时,后面解释。

AOP的工作原理

Spring用代理类包裹切面,把他们织入到Spring管理的bean中。也就是说代理类伪装成目标类,它会截取对目标类中方法的调用,让调用者对目标类的调用都先转化成对伪装类的调用,然后伪装类中先执行切面,再把调用转发给真正的目标bean。

那么,如何得到这个伪装类,才不会被调用者发现(要通过JVM的检查,JAVA是强类型检查,哪里都要检查类型)。

方法一

实现和目标类相同的接口,这样就伪装成了和目标类一样的类(实现了同一接口,就像兄弟一样),也就逃过了类型检查,到java运行期的时候,利用多态的后期绑定(所以Spring采用运行时),伪装类(代理类)就变成了接口的真正实现,而他里面包裹了真实的那个目标类,最后实现具体功能的还是目标类,只不过伪装类在之前干了点事情(写日志,安全检查,事物等)。

这就好比,客户让你办件事,每次这个时候,你双胞胎弟弟就会先出来,当然客户分不出来了,以为是你,你这个弟弟虽然办不了这事,但是他知道你能办,所以就答应下来了,并且收了点礼物(写日志),收完礼物就必须把事给人家办了对吧,所以你弟弟又找你这个哥哥来了,最后把这事办了的还是你自己。但是你自己并不知道你弟弟已经收礼物了,你只是专心把这件事情做好。

但是,要是这个类本身就没实现任何一个接口,你怎么伪装我,我就压根没有机会让你搞出这个双胞胎的弟弟,所以第2种代理方式就出现了,创建一个目标类的子类,生个儿子,让儿子伪装我。

方法二

生成子类调用,这次用子类来做伪装类,当然这样也能逃过JVM的强类型检查,子类继承自父类,当然查不出来了。子类重写了目标类的所有方法,当然在这些重写的方法中,不仅实现了目标类的功能,还在这些功能之前,实现了一些其他的(写日志,安全检查,事物等)。

就好比,儿子先从爸爸那把本事都学会了,所有人都找儿子办事情,但是儿子每次办和爸爸同样的事之前,都要收点小礼物(写日志),然后才去办真正的事。当然爸爸是不知道儿子这么干的了。这里要注意,某些本事是爸爸独有的(final的),儿子学不了,学不了就办不了这件事,办不了这个事情,自然就不能收人家礼了,这时就不能作为伪装类了。

总结

前一种兄弟模式,Spring会使用JDK的java.lang.reflect.Proxy类,它允许Spring动态生成一个新类来实现必要的接口,织入通知,并且把对这些接口的任何调用都转发到目标类。

后一种父子模式,Spring使用CGLIB库生成目标类的一个子类,在创建这个子类的时候,Spring织入通知,并且把对这个子类的调用委托到目标类。

相比之下,还是兄弟模式好些,他能更好的实现松耦合,尤其在今天都高喊着面向接口编程的情况下,父子模式只是在没有实现接口的时候,也能织入通知,应当做一种例外。

猜你喜欢

转载自my.oschina.net/u/3342874/blog/1821283