f10@t's blog

Spring AOP再探

字数统计: 965阅读时长: 4 min
2023/10/22

在学习Spring Framework的时候接触过AOP的相关内容—>Spring基础学习 - AOP机制 · f10@t's blog (f10at.cn)

但是当时没有记录使用注解的方式,且仅学习了Advisor没有了解其与Aspect的联系和区别,遂补一个坑。

喜欢我昆明池吗?

基于注解编写切面

其实整个过程还是比较简单的,可以先入为主参考如下代码,其中定义了所有的五类通知类型。其他代码此处省略。

1
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package lzw.spring.springreview.aspect;

import lombok.extern.log4j.Log4j2;
import lzw.spring.springreview.entity.People;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
* 用户服务切面
*
* @author lzwgiter
* @email float311@163.com
* @since 2023/10/22
*/
@Aspect
@Log4j2
@Component
public class PeopleServiceAspect {
/**
* execution开始定义切入点
* 含义:“返回值为任意类型的lzw.spring.springreview.service.impl.PeopleServiceImpl下getPeople方法,参数列表为String,Integer”
* * - 任意返回值
* 类的全路径
* 方法名称及参数
*/
@Pointcut(value = "execution(* lzw.spring.springreview.service.impl.PeopleServiceImpl.getPeople(String, Integer))")
public void pointcutDef() {
}

/**
* 前置通知
*
* @param joinPoint 连接点
*/
@Before(value = "pointcutDef()")
public void beforeGetPeople(JoinPoint joinPoint) {
log.info(joinPoint.getSignature().getName() + " 执行前");
}

/**
* 后置通知
*
* @param joinPoint
*/
@After(value = "pointcutDef()")
public void afterGetPeople(JoinPoint joinPoint) {
log.info(joinPoint.getSignature().getName() + " 执行后");
}

/**
* 返回通知
*
* @param joinPoint
* @param peopleToReturn
*/
@AfterReturning(value = "pointcutDef()", returning = "peopleToReturn")
public void afterReturning(JoinPoint joinPoint, People peopleToReturn) {
log.info(joinPoint.getSignature().getName() + "的返回值是" + peopleToReturn.toString());
}

/**
* 异常通知
*
* @param joinPoint
* @param e
*/
@AfterThrowing(value = "pointcutDef()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Exception e) {
log.info(joinPoint.getSignature() + "方法抛出异常了,异常是" + e);
}

/**
* 环绕通知
*
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around(value = "pointcutDef()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info(proceedingJoinPoint.getSignature().getName() + "环绕通知前");
Object toReturn = proceedingJoinPoint.proceed();
log.info(proceedingJoinPoint.getSignature().getName() + "环绕通知后");
return toReturn;
}
}

直接运行,从输出中就可以看到AOP的效果了:

从运行结果可以看出这五类通知和切入点函数的相对执行顺序

环绕通知->前置通知->返回通知->后置通知->环绕通知。而异常通知则在切入点函数抛出异常时执行。

上述代码关键点:

  • @Aspect:代表这是一个切面定义类;
  • @Pointcut:定义一个切点,可以使用execution关键字来定义精细化的函数切入点位置;
  • @Before:前置通知,value属性指定切点;
  • @After:后置通知,value属性指定切点;
  • @Around:环绕通知,value属性指定切点;
  • @AfterReturning:返回通知,value属性指定切点,returning属性指定返回对象变量名称;
  • @AfterThrowing:异常通知,value属性指定切点,throwing属性指定异常变量名称;

因而在使用Spring AOP时,我们针对需要切面编程的服务单元编写一个切面,其中可以定义多个@Pointcut注解的函数并配合execution表达式实现精细化的切点定义,然后针对这些切点定义不同的通知类型。

exection表达式的含义及上述例子中的表达式如下:

这里有一个区别,在之前的文章(Spring基础学习 - AOP机制 · f10@t's blog (f10at.cn))中我学习的Spring切面实际上只是Advisor,即由一个切点和一个通知组成,可以理解为一个取了特值的Aspect。如上述代码所示,可以看到实际上对于一个真正的Aspect切面,我们是可以定义多个切点和多个通知的。

但其实Advisor也不是完全没用。个人理解在Spring中,我们也可以通过将不同Advisor注入到容器中,并根据不同需求使用ProxyFactoryBean将多个需要的Advisor组装成一个Aspect,从而提高代码的复用性

参考学习

  • 《Spring Boot进阶 原理、实战与面试题分析》——郑天民
CATALOG
  1. 1. 基于注解编写切面
  2. 2. 参考学习