Spring AOP
# Spring AOP 面向切面编程
# 什么是 AOP
AOP(Aspect-Oriented Programming,即 面向切面编程)与 OOP( Object-Oriented Programming,面向对象编程) 相辅相成,提供了与 OOP 不同的抽象软件结构的视角。
在 OOP 中,我们以类(class)作为我们的基本单元,而 AOP 中的基本单元是 Aspect(切面)
# Aspect(切面)
Aspect 由 pointcut 和 Advice 组成, 它既包含了横切逻辑的定义, 也包括了连接点的定义. Spring AOP 就是负责实施切面的框架, 它将切面所定义的横切逻辑织入到切面所指定的连接点中. AOP 的工作重心在于如何将增强织入目标对象的连接点上, 这里包含两个工作:
如何通过 pointcut 和 Advice 定位到特定的 joinpoint 上 如何在 Advice 中编写切面代码. 可以简单地认为, 使用 @Aspect 注解的类就是切面.
# 连接点(join point)
a point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.
程序运行中的一些时间点, 例如一个方法的执行, 或者是一个异常的处理. 在 Spring AOP 中, join point 总是方法的执行点, 即只有方法连接点.
# 切点(point cut)
匹配 join point 的谓词(a predicate that matches join points). Advice 是和特定的 point cut 关联的, 并且在 point cut 相匹配的 join point 中执行. 在 Spring 中, 所有的方法都可以认为是 joinpoint, 但是我们并不希望在所有的方法上都添加 Advice, 而 pointcut 的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述) 来匹配joinpoint, 给满足规则的 joinpoint 添加 Advice.
# Advice(增强)
由 Aspect 添加到特定的 join point(即满足 point cut 规则的 join point) 的一段代码. 许多 AOP 框架, 包括 Spring AOP, 会将 Advice 模拟为一个拦截器(interceptor), 并且在 join point 上维护多个 Advice, 进行层层拦截.
Advice 的类型
- Before Advice, 在 join point 前被执行的 Advice. 虽然 before Advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before Advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)
- After return Advice, 在一个 join point 正常返回后执行的 Advice
- After throwing Advice, 当一个 join point 抛出异常后执行的 Advice
- After(final) Advice, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 Advice.
- Around Advice, 在 join point 前和 joint point 退出后都执行的 Advice. 这个是最常用的 Advice.
# 彻底理解 Aspect, join point, point cut, Advice
看完了上面的理论部分知识, 我相信还是会有不少朋友感觉到 AOP 的概念还是很模糊, 对 AOP 中的各种概念理解的还不是很透彻. 其实这很正常, 因为 AOP 中的概念是在是太多了, 下面我以一个简单的例子来比喻一下 AOP 中 Aspect, jointpoint, pointcut 与 Advice 之间的关系.
让我们来假设一下, 从前有一个叫爪哇的小县城, 在一个月黑风高的晚上, 这个县城中发生了命案. 作案的凶手十分狡猾, 现场没有留下什么有价值的线索. 不过万幸的是, 刚从隔壁回来的老王恰好在这时候无意中发现了凶手行凶的过程, 但是由于天色已晚, 加上凶手蒙着面, 老王并没有看清凶手的面目, 只知道凶手是个男性, 身高约七尺五寸. 爪哇县的县令根据老王的描述, 对守门的士兵下命令说: 凡是发现有身高七尺五寸的男性, 都要抓过来审问. 士兵当然不敢违背县令的命令, 只好把进出城的所有符合条件的人都抓了起来.
来让我们看一下上面的一个小故事和 AOP 到底有什么对应关系. 首先我们知道, 在 Spring AOP 中 join point 指代的是所有方法的执行点, 而 point cut 是一个描述信息, 它修饰的是 join point, 通过 point cut, 我们就可以确定哪些 join point 可以被织入 Advice. 对应到我们在上面举的例子, 我们可以做一个简单的类比, join point 就相当于 爪哇的小县城里的百姓, point cut 就相当于 老王所做的指控, 即凶手是个男性, 身高约七尺五寸, 而 Advice 则是施加在符合老王所描述的嫌疑人的动作: 抓过来审问. 为什么可以这样类比呢?
- join point --> 爪哇的小县城里的百姓: 因为根据定义, join point 是所有可能被织入 Advice 的候选的点, 在 Spring AOP 中, 则可以认为所有方法执行点都是 join point. 而在我们上面的例子中, 命案发生在小县城中, 按理说在此县城中的所有人都有可能是嫌疑人.
- point cut --> 男性, 身高约七尺五寸: 我们知道, 所有的方法(joint point) 都可以织入 Advice, 但是我们并不希望在所有方法上都织入 Advice, 而 pointcut 的作用就是提供一组规则来匹配 joinpoint, 给满足规则的 joinpoint 添加 Advice. 同理, 对于县令来说, 他再昏庸, 也知道不能把县城中的所有百姓都抓起来审问, 而是根据凶手是个男性, 身高约七尺五寸, 把符合条件的人抓起来. 在这里凶手是个男性, 身高约七尺五寸 就是一个修饰谓语, 它限定了凶手的范围, 满足此修饰规则的百姓都是嫌疑人, 都需要抓起来审问.
- Advice --> 抓过来审问, Advice 是一个动作, 即一段 Java 代码, 这段 Java 代码是作用于 point cut 所限定的那些 join point 上的. 同理, 对比到我们的例子中, 抓过来审问 这个动作就是对作用于那些满足 男性, 身高约七尺五寸 的爪哇的小县城里的百姓.
- Aspect: Aspect 是 point cut 与 Advice 的组合, 因此在这里我们就可以类比: "根据老王的线索, 凡是发现有身高七尺五寸的男性, 都要抓过来审问" 这一整个动作可以被认为是一个 Aspect.
或则我们也可以从语法的角度来简单类比一下. 我们在学英语时, 经常会接触什么 定语, 被动句 之类的概念, 那么可以做一个不严谨的类比, 即 joinpoint 可以认为是一个 宾语, 而 pointcut 则可以类比为修饰 joinpoint 的定语, 那么整个 Aspect 就可以描述为: 满足 pointcut 规则的 joinpoint 会被添加相应的 Advice 操作.
# 案例
创建切面
LogAdvice
@Component
@Aspect
public class LogAdvice {
/**
*
* jonit point :所有可以执行的方法
*
* point cut : 是符合定义了一些规则的方法
*
*/
@Pointcut("execution(* com.joe.service..*.*(..))")
public void pointcut(){
}
@Before("pointcut()")
public void beforeAdvice(){
System.out.println("前置增强。。。。");
}
@AfterReturning("pointcut()")
public void afterReturn(){
System.out.println("后置增强--正常返回");
}
@AfterThrowing("pointcut()")
public void afterThrowing(){
System.out.println("后置增强--异常返回");
}
@After("pointcut()")
public void after(){
System.out.println("后置增强--最终返回");
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("连接点之前----方法执行之前");
Object obj=joinPoint.proceed();
//OrderService.print()
System.out.println("连接点之后----方法执行之后");
return obj;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
配置切面 AppConfiguration
@Configuration
@EnableAspectJAutoProxy
public class AppConfiguration {
}
2
3
4
测试切面
OrderService
@Service
public class OrderService {
public void print(){
System.out.println("打印订单。。。。。");
}
public void print1(){
System.out.println("打印订单1。。。。。");
}
public void saveLog(){
System.out.println("保存订单日志。。。");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
App
public class App
{
public static void main( String[] args )
{
ApplicationContext context=new AnnotationConfigApplicationContext("com.joe");
OrderService orderService=context.getBean(OrderService.class);
orderService.print();
}
}
2
3
4
5
6
7
8
9