AOP样例-cnblog
Spring AOP 完整可运行示例(日志切面)
环境依赖(Maven)
<!-- Spring AOP 核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
场景说明
业务层 UserService 提供用户新增、查询功能;
使用 AOP 统一记录:方法入参、执行耗时、返回结果、异常信息,不改动任何业务代码。
1. 业务目标类(Target 目标对象)
import org.springframework.stereotype.Service;
@Service
public class UserService {
// 模拟业务方法1
public void addUser(String username, Integer age) {
System.out.println("执行业务:新增用户");
// 模拟异常
if (age < 0) {
throw new RuntimeException("年龄不能为负数");
}
}
// 模拟业务方法2
public String getUserInfo(Long userId) {
System.out.println("执行业务:查询用户");
return "用户ID:" + userId + ",张三";
}
}
2. 切面类(Aspect)统一日志切面
包含 5 种通知,完整覆盖所有场景
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
// 1. @Aspect:标记当前类为切面
@Aspect
@Component
public class LogAspect {
// 2. Pointcut 切入点:execution表达式匹配所有service包下所有类所有方法
// 格式:execution(返回值 包.类.方法(参数))
@Pointcut("execution(* com.demo.service.*.*(..))")
public void servicePointCut() {}
// 【前置通知 @Before】方法执行前触发
@Before("servicePointCut()")
public void beforeLog(JoinPoint joinPoint) {
System.out.println("===== @Before 前置通知 =====");
// JoinPoint连接点:获取方法信息、参数
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("执行方法:" + methodName);
System.out.println("方法入参:" + Arrays.toString(args));
}
// 【正常返回通知 @AfterReturning】方法无异常正常返回时触发
@AfterReturning(pointcut = "servicePointCut()", returning = "result")
public void returnLog(JoinPoint joinPoint, Object result) {
System.out.println("===== @AfterReturning 返回通知 =====");
System.out.println("方法返回值:" + result);
}
// 【异常通知 @AfterThrowing】方法抛出异常时触发
@AfterThrowing(pointcut = "servicePointCut()", throwing = "ex")
public void errorLog(JoinPoint joinPoint, Exception ex) {
System.out.println("===== @AfterThrowing 异常通知 =====");
System.out.println("方法异常信息:" + ex.getMessage());
}
// 【最终通知 @After】无论正常/异常,最后一定执行
@After("servicePointCut()")
public void finalLog() {
System.out.println("===== @After 最终通知 =====");
System.out.println("方法执行结束\n");
}
// 【环绕通知 @Around】功能最强,包裹目标方法,可手动控制执行
@Around("servicePointCut()")
public Object aroundLog(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("【环绕通知】方法执行前");
long start = System.currentTimeMillis();
// 执行目标业务方法
Object res = pjp.proceed();
long cost = System.currentTimeMillis() - start;
System.out.println("【环绕通知】方法执行耗时:" + cost + "ms");
return res;
}
}
3. 测试类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AopTestApp implements CommandLineRunner {
@Autowired
private UserService userService;
public static void main(String[] args) {
SpringApplication.run(AopTestApp.class, args);
}
@Override
public void run(String... args) {
System.out.println("========== 测试正常方法 ==========");
userService.getUserInfo(1001L);
System.out.println("\n========== 测试抛异常方法 ==========");
try {
userService.addUser("李四", -5);
} catch (Exception e) {}
}
}
对应AOP术语一一对照
- 切面 Aspect:
LogAspect加了@Aspect的日志类,存放所有增强逻辑 - 通知 Advice:
@Before/@Around/@AfterReturning五个方法就是五种通知 - 切入点 Pointcut:
@Pointcut定义的execution(* com.demo.service.*.*(..)),匹配要拦截的方法 - 连接点 JoinPoint:
UserService的addUser()、getUserInfo()两个被匹配到的方法 - 目标对象 Target:原始
UserService对象 - 代理对象 Proxy:Spring 自动生成 UserService 的代理对象,容器注入的实际是代理
- 织入 Weaving:Spring 容器启动时,自动把切面日志逻辑嵌入业务方法,运行时生效
执行流程演示(调用userService.getUserInfo())
- IOC容器注入的不是原生UserService,而是动态代理对象Proxy
- 调用
getUserInfo,先进入环绕通知@Around - 执行环绕前置代码 → 执行
@Before前置通知 - 执行原始业务方法
getUserInfo() - 方法正常返回,执行
@AfterReturning返回通知 - 执行
@After最终通知 - 回到环绕通知,打印耗时,返回结果
核心优势体现
业务层 UserService 完全只写业务逻辑,日志打印横向通用代码全部抽离到切面;
后续想修改日志规则,只改切面,无需改动任何业务代码,实现业务与通用功能解耦。
拓展经典场景:事务 AOP
@Transactional 底层原理完全一致:
- 切面拦截加了事务注解的方法
- 环绕通知中开启事务 → 执行业务 → 无异常提交事务 → 异常回滚事务
不需要在每个业务方法手动写commit/rollback。

浙公网安备 33010602011771号