面向切面编程(AOP)通过提供另外一种思考程序结构的途经来弥补面向对象编程(OOP)的不足。在OOP中模块化的关键单元是类(classes),而在AOP中模块化的单元则是切面。切面能对关注点进行模块化,例如横切多个类型和对象的事务管理。(在AOP术语中通常称作横切(crosscutting)关注点。)
AOP的基本概念
切面(Aspect)
一个关注点的模块化,是通知和切点的结合。通知和切点共同定义了切面的全部内容——它是什么,在何时和何处完成其功能。
连接点(Joinpoint)
连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。因为Spring基于代理实现AOP,所以只支持方法连接点。
通知(Advice)
在切面的某个特定的连接点上执行的动作。Spring切面可以应用6种类型的通知:
1 前置通知(Before):在目标方法被调用之前调用通知功能;
2 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
3 返回通知(After-returning):在目标方法成功执行之后调用通知;
4 异常通知(After-throwing):在目标方法抛出异常后调用通知;
5 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
6 引入通知(Introduction):用来给一个类型声明额外的方法或属性。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。可以在无需修改现有的类的情况下,让它们具有新的行为和状态。
切入点(Pointcut)
切入点是匹配连接点的表达式。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行。一个切面并不需要通知应用的所有连接点。切点有助于缩小切面所通知的连接点的范围。
目标对象(Target Object)
被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。
AOP代理(AOP Proxy)
AOP框架创建的对象,用来实现切面(例如通知方法执行等等)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
织入(Weaving)
织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入:
编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5的加载时织入(load-timeweaving,LTW)就支持以这种方式织入切面。
运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的。
示例代码:
/** * 检查入参合法性 * * @author 喻聪 * @date 2018-01-26 * */ @Aspect
public class ValidParamAspect { // Controller层切点 @Pointcut("execution (* com.yucong.controller..*.*(..))") public void aspect() { } @Before("aspect()") public void doBefore(JoinPoint jp) throws Throwable { Object[] args = jp.getArgs(); if(args != null && args.length > 1 ) { //取出第2个参数 Object obj = jp.getArgs()[1]; if(obj instanceof BindingResult) { BindingResult bindingResult = (BindingResult)obj; // 校验返回错误集合中的第一个错误信息 if (bindingResult.hasErrors()) { List<ObjectError> errors = bindingResult.getAllErrors(); throw new ParameterIllegalException(errors.get(0).getDefaultMessage()); } } } } }
Spring提供了4种类型的AOP支持:
基于代理的经典Spring AOP
基于XML配置,曾经它的确非常棒。但是现在Spring提供了更简洁和干净的面向切面编程方式。引入了简单的声明式AOP和基于注解的AOP之后,Spring经典的AOP看起来就显得非常笨重和过于复杂。
纯POJO切面
借助Spring的aop命名空间,我们可以将纯POJO转换为切面。实际上,这些POJO只是提供了满足切点条件时所要调用的方法。遗憾的是,这种技术需要XML配置,但这的确是声明式地将对象转换为切面的简便方式。
@AspectJ注解驱动的切面
Spring借鉴了AspectJ的切面,以提供注解驱动的AOP。本质上,它依然是Spring基于代理的AOP,但是编程模型几乎与编写成熟的AspectJ注解切面完全一致。这种AOP风格的好处在于能够不使用XML来完成功能。
需要导入aspectjweaver.jar,4.3.8依赖1.8.9
注入式AspectJ切面
如果AOP的**切点要求超出了方法**(如构造器或属性拦截),那么需要考虑使用AspectJ来实现切面。
前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。