AOP即面向切面编程。在软件开发中, 散布于应用中多处的功能被称为横切关注点 。 通常来讲, 这些横切关注点从概念上是与应用的业务逻辑相分离的(但是往往会直接嵌入到应用的业务逻辑之中) 。 把这些横切关注点与业务逻辑相分离正是面向切面编程(AOP) 所要解决的问题。

切面能帮助我们模块化横切关注点。 简而言之, 横切关注点可以被描述为影响应用多处的功能。如果重用通用功能的话,传统的面向对象技术就是继承和委托。但继承会导致一个脆弱的系统体系,而委托则可能会对委托对象进行复杂的调用。这时候,切面是一种取代继承和委托的解决方案。

在使用面向切面编程时,仍要在一个地方定义通用功能, 但是可以通过声明的方式定义这个功能要以何种方式在何处应用, 而无需修改受影响的类。横切关注点可以被模块化为特殊的类,这些类被称为切面(aspect) 。 这样做有两个好处: 首先, 现在每个关注点都集中于一个地方, 而不是分散到多处代码中; 其次, 服务模块更简洁, 因为它们只包含主要关注点(或核心功能) 的代码, 而次要关注点的代码被转移到切面中了。

AOP术语
与大多数技术一样, AOP已经形成了自己的术语。 描述切面的常用术语有通知(advice) 、 切点(pointcut) 和连接点(join point) 。

通知定义了切面是什么以及何时使用。 除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。 Spring切面可以应用5种类型的通知:
前置通知(Before) : 在目标方法被调用之前调用通知功能;
后置通知(After) : 在目标方法完成之后调用通知, 此时不会关心方法的输出是什么;
返回通知(After-returning) : 在目标方法成功执行之后调用通知;
异常通知(After-throwing) : 在目标方法抛出异常后调用通知;
环绕通知(Around) : 通知包裹了被通知的方法, 在被通知的方法调用之前和调用之后执行自定义的行为。

连接点是在应用执行过程中能够插入切面的一个点。 这个点可以是调用方法时、 抛出异常时、 甚至修改一个字段时。 切面代码可以利用这些点插入到应用的正常流程之中, 并添加新的行为。连接点可以理解为触发某个事件的时刻。

切点的定义会匹配通知所要织入的一个或多个连接点,是某个通知到达的范围。而切面是通知和切点的结合。通知和切点是切面的最基本元素。

引入允许向现有的类添加新方法或属性。而织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。
在目标对象的生命周期里有多个点可以进行织入:
编译期: 切面在目标类编译时被织入。 这种方式需要特殊的编译器。 AspectJ的织入编译器就是以这种方式织入切面的。
类加载期: 切面在目标类加载到JVM时被织入。 这种方式需要特殊的类加载器(ClassLoader) , 它可以在目标类被引入应用之前增强该目标类的字节码。 AspectJ 5的加载时织入(load-timeweaving, LTW) 就支持以这种方式织入切面。
运行期: 切面在应用运行的某个时刻被织入。 一般情况下, 在织入切面时, AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的。

Spring支持的AOP
Spring提供了4种类型的AOP支持:
1.基于代理的经典Spring AOP;
2.纯POJO切面;
[email protected]注解驱动的切面;
4.注入式AspectJ切面(适用于Spring各版本) 。
前三种都是Spring AOP实现的变体, Spring AOP构建在动态代理基础之上, 因此, Spring对AOP的支持局限于方法拦截。借助Spring的aop命名空间, 我们可以将纯POJO转换为切面。 实际上, 这些POJO只是提供了满足切点条件时所要调用的方法。

Spring通知
Spring所创建的通知都是用标准的Java类编写的。并且,定义通知所应用的切点通常会使用注解或在Spring配置文件里采用XML来编写。
Spring在运行时通知对象的方法:Spring的切面由包裹了目标对象的代理类实现。代理类处理方法的调用, 执行额外的切面逻辑, 并调用目标方法。
通过在代理类中包裹切面, Spring在运行期把切面织入到Spring管理的bean中。代理类封装了目标类, 并拦截被通知方法的调用, 再把调用转发给真正的目标bean。 当代理拦截到方法调用时,在调用目标bean方法之前, 会执行切面逻辑。直到应用需要被代理的bean时, Spring才创建代理对象。如果使用的是ApplicationContext的话, 在ApplicationContext从BeanFactory中加载所有bean的时候, Spring才会创建被代理的对象。 因为Spring运行时才创建代理对象, 所以我们不需要特殊的编译器来织入Spring AOP的切面。如图所示。
Spring框架学习--AOP
因为Spring基于动态代理, 所以Spring只支持方法连接点。

通过切点来选择连接点
关于Spring AOP的AspectJ切点, 最重要的一点就是Spring仅支持AspectJ切点指示器 的一个子集。 Spring是基于代理的, 而某些切点表达式是与基于代理的AOP无关的。下表是 Spring AOP所支持的AspectJ切点指示器。
Spring框架学习--AOP
如果试图在Spring中尝试使用除上表列出的AspectJ其他指示器时, 将会抛出IllegalArgument-Exception异常。
只有execution指示器是实际执行匹配的, 而其他的指示器都是用来限制匹配的。 这说明execution指示器是在编写切点定义时最主要使用的指示器。

编写切点
例如下面这个例子,先编写一个接口。
Spring框架学习--AOP
假设要编写Performance的perform()方法触发的通知。下图展现了一个切点表达式, 这个表达式能够设置当perform()方法执行时触发通知的调用。
Spring框架学习--AOP
从上图可以得出,使用execution()指示器选择Performance的perform()方法。 方法表达式以“*”号开始, 表示不关心方法返回值的类型。 然后, 指定全限定类名和方法名。 对于方法参数列表,使用两个点号(..) 表明切点要选择任意的perform()方法, 无论该方法的入参是什么。
如果需要限定切点范围,则可以使用within()指示器来限制匹配。如下图所示。
Spring框架学习--AOP
使用“&&”操作符把execution()和within()指示器连接在一起形成与(and) 关系(切点必须匹配所有的指示器) 。类似地,可以使用“||”操作符来标识或(or) 关系, 而使用“!”操作符来标识非(not) 操作。

因为“&”在XML中有特殊含义, 所以在Spring的XML配置里面描述切点时, 要使用and来代替“&&”。 同样, or和not可以分别用来代替“||”和“!”。

Spring还引入了一个新的bean()指示器,它允许在切点表达式中使用bean的ID来标识bean。bean()使用bean ID或bean名称作为参数来限制切点只匹配特定的bean。

相关文章: