如何使用AOP
用 "打针" 场景一次性记住 AOP 核心概念
想象你去医院打针的过程:
目标对象(Target)
就是 "你"(被打针的人),核心业务是 "生病看病"。
连接点(Join Point)
你身体上所有能打针的地方(胳膊、屁股等所有可能的位置)。
用打针场景重新理解「切点(Pointcut)」
还是以打针为例,但更贴近@Pointcut
注解和表达式的作用:
你去医院打针时,护士不会随便找个地方扎,而是要按「规则」精准定位 ------ 这个「规则」就是切点。
比如医院规定: 「只给儿科 的发烧 患者,在胳膊三角肌 打退烧针 」 这里的「儿科患者 + 发烧 + 胳膊三角肌 + 退烧针」就是一套「切点规则」,对应 AOP 中@Pointcut
的表达式逻辑。
对应到代码中的切点定义
@Pointcut
注解就像医院的「打针规则手册」,而execution 表达式就是手册里的具体条款:
java
// 例:拦截所有Controller中public方法的切点
@Pointcut("execution(public * cn.soboys.controller.*.*(..))")
public void controllerPointcut() {}
这个表达式的含义,对应到打针场景就是:
-
execution
:「在打针动作执行时生效」(方法执行时触发); -
public :「不管是哪种针(肌肉针 / 静脉针),只要是公开给患者的操作」(任意返回值的 public 方法);
-
cn.soboys.controller.*
:「只针对儿科诊室里的所有病床」(指定包下的所有类); -
*(..)
:「不管患者年龄、体重如何,只要是打针操作都算」(任意方法名,任意参数)。


切点的核心作用:精准筛选,可重用
- 精准筛选 :就像医院用「儿科 + 发烧 + 退烧针」筛出需要特殊处理的患者,
execution
表达式通过「返回值 + 类名 + 方法名 + 参数」精准定位要拦截的方法(比如只拦截UserController
里带@PostMapping
的方法)。 - 可重用 :定义一次
@Pointcut("xxx")
,后续所有通知(前置 / 后置等)都能直接引用这个切点,就像医院把「儿科退烧针规则」写在手册里,所有护士都能按同一套规则执行,不用每次重新定义。
通知(Advice)
打针的具体动作:
- 前置通知:打针前 "用酒精棉消毒";
- 后置通知:打完后 "贴创可贴";
- 环绕通知:"按住皮肤→扎针→推药→拔针"(全程包围核心动作);
- 异常通知:如果 "打针时出血了",医生会额外 "止血"。
切面(Aspect)
医生的 "打针流程手册",里面写清了 "在胳膊三角肌(切点)打针,步骤是消毒→扎针→贴创可贴(通知)"------切点 + 通知 = 切面。
- 织入(Weaving):医生按照手册给你打针的过程(把流程 "套" 到你身上)。
一句话总结
- 连接点:所有可能被 "切" 的地方(能打针的所有位置);
- 切点:实际被 "切" 的地方(选好的打针位置);
- 通知:"切" 的时候做什么(消毒、扎针等动作);
- 切面:规定 "在哪里切 + 切了做什么"(完整的打针方案)。
AOP 最适合的场景
记住:所有 "重复出现在多个业务里,但又不是核心业务" 的功能,都该用 AOP。 比如:
- 日志:每个接口都要记录请求,但日志不是业务本身;
- 权限:每个敏感操作都要验权限,验权限不是业务目的;
- 事务:订单、支付等操作都要事务控制,控制事务不是业务核心;
- 监控:统计每个方法的执行时间,统计不是业务逻辑。
就像打针时,"消毒、贴创可贴" 不是看病的核心(核心是给药),但每个打针流程都需要 ------ 这就是 AOP 要干的事。
案例说明
本案例的主要目标是通过两个不同的业务类(Calculator
和 UserService
),展示 AOP 在实际项目中的应用。具体来说,使用 AOP 实现以下功能:
- 对
Calculator
类的所有方法进行日志记录,包括方法调用前、调用后、正常返回和抛出异常时的日志。 - 对
UserService
类的方法进行日志记录、数据脱敏和权限检查。
导入依赖
java
<!--aop 切面-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
定义业务逻辑类
UserService
类
java
import lombok.Data;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public User getUserById(int id) {
// 模拟返回用户信息
return new User(id, "Alice", "alice@example.com");
}
public void createUser(User user) {
// 模拟创建用户
System.out.println("创建用户: " + user);
}
public void updateUser(User user) {
// 模拟更新用户
System.out.println("更新用户: " + user);
}
public void deleteUser(int id) {
// 模拟删除用户
System.out.println("删除用户,ID: " + id);
}
}
@Data
class User {
private int id;
private String name;
private String email;
public User(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
'}';
}
}
意义 :UserService
类模拟了一个用户服务,包含了获取用户信息、创建用户、更新用户和删除用户的方法。通过对这个类的方法进行 AOP 处理,可以展示 AOP 在更复杂业务场景下的应用,如数据脱敏和权限检查。
定义切面
java
@Aspect
@Component
public class UserAspect {
// 定义切点,拦截UserService类的所有方法
@Pointcut("execution(* org.example.aopdemo.UserService.*(..))")
public void allMethods() {}
// 定义切点,拦截UserService类中createUser和updateUser方法
@Pointcut("execution(* org.example.aopdemo.UserService.createUser(..)) || execution(* org.example.aopdemo.UserService.updateUser(..))")
public void userOperations() {}
// 定义切点,拦截UserService类中getUserById方法
@Pointcut("execution(* org.example.aopdemo.UserService.getUserById(..))")
public void getUserOperations() {}
// 前置通知:在方法执行之前执行
@Before("allMethods()")
public void beforeAdvice(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String methodName = methodSignature.getMethod().getName();
System.out.println("前置通知:方法 " + methodName + " 被调用,参数为 " + Arrays.toString(joinPoint.getArgs()));
}
// 后置通知:在方法执行之后执行,无论是否发生异常
@After("allMethods()")
public void afterAdvice(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String methodName = methodSignature.getMethod().getName();
System.out.println("后置通知:方法 " + methodName + " 已执行完毕。");
}
// 返回通知:在方法正常返回后执行
@AfterReturning(pointcut = "getUserOperations()", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String methodName = methodSignature.getMethod().getName();
System.out.println("返回通知:方法 " + methodName + " 返回结果为 " + result);
// 数据脱敏处理
if (result instanceof User) {
User user = (User) result;
System.out.println("数据脱敏处理:用户信息已脱敏,返回的用户ID为 " + user.getId());
}
}
// 异常通知:在方法抛出异常后执行
@AfterThrowing(pointcut = "allMethods()", throwing = "exception")
public void afterThrowingAdvice(JoinPoint joinPoint, Throwable exception) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String methodName = methodSignature.getMethod().getName();
System.out.println("异常通知:方法 " + methodName + " 抛出异常:" + exception.getMessage());
}
// 环绕通知:对createUser和updateUser方法进行权限检查
@Around("userOperations()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String methodName = methodSignature.getMethod().getName();
System.out.println("环绕通知:方法 " + methodName + " 开始执行,进行权限检查...");
// 模拟权限检查
if (!hasPermission()) {
throw new SecurityException("没有权限执行此操作!");
}
Object result = joinPoint.proceed(); // 继续执行原方法
System.out.println("环绕通知:方法 " + methodName + " 执行完成。");
return result;
}
private boolean hasPermission() {
// 模拟权限检查逻辑
return true; // 假设当前用户有权限
}
}
在 AOP(面向切面编程)中,JoinPoint
是一个非常重要的概念,JoinPoint joinPoint
中的 JoinPoint
是一个接口,它代表程序执行过程中的某个特定位置,例如方法调用、异常抛出等。下面详细解释 JoinPoint
的含义、作用以及在提供的代码中的使用方式。
JoinPoint
的含义
JoinPoint
是 AOP 中的一个抽象概念,它表示程序执行过程中可以插入切面代码的点。在 Spring AOP 里,JoinPoint
通常代表方法的执行,因为 Spring AOP 主要基于方法拦截来实现。
JoinPoint
的作用
JoinPoint
提供了一系列方法,用于获取与当前连接点相关的信息,比如:
- 获取方法签名:可以获取被调用方法的名称、参数类型等信息。
- 获取方法参数:可以获取传递给被调用方法的实际参数。
- 获取目标对象:可以获取被调用方法所在的对象实例。
以这个为例子说明:
java
public void beforeAdvice(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String methodName = methodSignature.getMethod().getName();
System.out.println("前置通知:方法 " + methodName + " 被调用,参数为 " + Arrays.toString(joinPoint.getArgs()));
}
joinPoint.getSignature()
:getSignature()
是JoinPoint
接口的一个方法,用于获取当前连接点的签名信息,返回的是一个Signature
类型的对象。(MethodSignature)
:由于我们处理的是方法调用,所以将Signature
对象强制转换为MethodSignature
类型,以便获取方法的具体信息。
测试执行
java
@SpringBootApplication
public class AopDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(AopDemoApplication.class, args);
UserService userService = context.getBean(UserService.class);
System.out.println("\n测试getUserById方法:");
User user = userService.getUserById(1);
System.out.println("\n测试createUser方法:");
userService.createUser(new User(2, "Bob", "bob@example.com"));
System.out.println("\n测试updateUser方法:");
userService.updateUser(new User(2, "Bob Updated", "bob-updated@example.com"));
System.out.println("\n测试deleteUser方法:");
userService.deleteUser(2);
}
}

这里写的只不过是测试方法,在项目中应该是写到config文件中。直接配置,注入到springboot容器中。