一、什么是面向切面编程
横切关注点:在软件开发中,散布于应用中多处的功能被称为横切关注点( cross-cutting concern )【比如说日志,安全和事务管理等】。通常来讲,这些横切关注点从概念上是与应用的业务逻辑相分离的(但是往往会直接嵌入到应用的业务逻辑之中)。把这些横切关注点与业务逻辑相分离正是面向切面编程( AOP )所要解决的问题。
切面:横切关注点可以被模块化为特殊的类,这些类被称为切面( aspect )。
1、定义AOP术语
通知( Advice )
切面的工作被称为通知。通知描述切面的工作,同时决定切面何时工作【定义了切面工作做什么,什么时候做】。
Spring切面可以应用5种类型的通知:
-
前置通知(Before) : 在目标方法被调用之前调用通知功能;
-
后置通知(After) : 在目标方法完成之后调用通知, 此时不会关心方法的输出是什么;
-
返回通知(After-returning) : 在目标方法成功执行之后调用通知;
-
异常通知(After-throwing) : 在目标方法抛出异常后调用通知;
-
环绕通知(Around) : 通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
连接点( Join point)
我们的应用可能有数以千计的时机应用通知,这些时机被称为连接点。比如调用方法,抛出异常等行为。
切点( Poincut)
如果说通知定义了切面的“什么”和“何时”的话, 那么切点就定义了“何处”。切点的定义会匹配通知所要织入的一个或多个连接点。
切面( Aspect)
切面是通知和切点的结合。 通知和切点共同定义了切面的全部内容——它是什么, 在何时和何处完成其功能。
引入( Introduction)
引入允许我们向现有的类添加新方法或属性。
织入( Weaving)
织入是把切面应用到目标对象并创建新的代理对象的过程。 【切面在指定的连接点被织入到目标对象中】
在目标对象的生命周期里有多个点可以进行织入:编译期、类加载期、运行期。Spring AOP是在运行期织入切面的,在织入切面时, AOP容器会为目标对象动态地创建一个代理对象。
总结
参照下图,通知包含了需要用于多个应用对象的横切行为; 连接点是程序执行过程中能够应用通知的所有点; 切点定义了通知被应用的具体位置(在哪些连接点)。其中关键的概念是切点定义了哪些连接点会得到通知。
2、Spring对AOP的支持
Spring提供了4种类型的AOP支持:
-
基于代理的经典Spring AOP; -
纯POJO切面; -
@AspectJ注解驱动的切面; -
注入式AspectJ切面(适用于Spring各版本) 。
前三种都是Spring AOP实现的变体, Spring AOP构建在动态代理基础之上, 因此, Spring对AOP的支持局限于方法拦截。
二、通过切点来选择连接点
在Spring AOP中, 要使用AspectJ的切点表达式语言来定义切点。
1、编写切点
为了阐述Spring中的切面, 我们需要有个主题来定义切面的切点。 为此, 我们定义一个Performance接口:
Performance可以代表任何类型的现场表演, 如舞台剧、 电影或音乐会。 假设我们想编写Performance的perform()方法触发的通知。
下面的表达式能够设置当perform()方法执行时触发通知的调用。
我们使用execution()指示器选择Performance的perform()方法。 方法表达式以“*”号开始, 表明了我们不关心方法返回值的类型。 然
后, 我们指定了全限定类名和方法名。 对于方法参数列表, 我们使用两个点号(..) 表明切点要选择任意的perform()方法, 无论该方法的
入参是什么。
三、使用注解创建切面
1、定义切面
从演出的角度来看, 观众是非常重要的, 但是对演出本身的功能来讲,它并不是核心,这是一个单独的关注点。 因此, 将观众定义为一个切面, 并将其应用到演出上就是较为明智的做法。下面定义Audience类:
Audience类使用@AspectJ注解进行了标注。 该注解表明Audience不仅仅是一个POJO, 还是一个切面。 Audience类中的方法都使用注
解来定义切面的具体行为。
Audience有四个方法, 定义了一个观众在观看演出时可能会做的事情。 在演出之前, 观众要就坐(takeSeats()) 并将手机调至静音状态
(silenceCellPhones()) 。 如果演出很精彩的话, 观众应该会鼓掌喝彩(applause()) 。 不过, 如果演出没有达到观众预期的话, 观
众会要求退款(demandRefund()) 。
AspectJ提供了五个注解定义通知,来表明它们应该在什么时候调用:
- @After:通知方法会在目标方法返回或抛出异常后调用
- @AfterReturning: 通知方法会在目标方法返回后调用
- @AfterThrowing: 通知方法会在目标方法抛出异常后调用
- @Around: 通知方法会将目标方法封装起来
- @Before: 通知方法会在目标方法调用之前执行
我们可以看到,这四个方法的切点表达式都是相同的。 我们完全可以只定义这个切点一次, 然后每次需要的时候引用它。
@Pointcut注解能够在一个@AspectJ切面内定义可重用的切点。
为@Pointcut注解设置的值是一个切点表达式。performance()方法的实际内容并不重要, 在这里它实际上应该是空的。 其实该方法本身只是一个标识, 供@Pointcut注解依附。
需要注意的是, 除了注解和没有实际操作的performance()方法, Audience类依然是一个POJO。 我们能够像使用其他的Java类那样调用
它的方法,这与其他的Java类并没有什么区别。Audience只是一个Java类, 只不过它通过注解表明会作为切面使用而已。
除此之外,还需要在JavaConfig配置类的类级别上通过使用EnableAspectJ-AutoProxy注解启用自动代理功能。
AspectJ自动代理会为使用@Aspect注解的bean创建一个代理, 这个代理会围绕着所有该切面的切点所匹配的bean。
需要记住的是, Spring的AspectJ自动代理仅仅使用@AspectJ作为创建切面的指导,切面依然是基于代理的。如果想利用AspectJ的所有能力, 我们必须在运行时使用AspectJ并且不依赖Spring来创建基于代理的切面。
2、创建环绕通知
环绕通知是最为强大的通知类型。 它能够让你所编写的逻辑将被通知的目标方法完全包装起来。
我们重写Audience切面,使用一个环绕通知来代替之前多个不同的前置通知和后置通知。
在这里, @Around注解表明watchPerformance()方法会作为performance()切点的环绕通知。
它接受ProceedingJoinPoint作为参数,这个对象是必须要有的,当要将控制权交给被通知的方法时, 它需要调用ProceedingJoinPoint的proceed()方法。如果不调用, 那么你的通知实际上会阻塞对被通知方法的调用。