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术语一一对照

  1. 切面 AspectLogAspect 加了@Aspect的日志类,存放所有增强逻辑
  2. 通知 Advice@Before/@Around/@AfterReturning 五个方法就是五种通知
  3. 切入点 Pointcut@Pointcut 定义的 execution(* com.demo.service.*.*(..)),匹配要拦截的方法
  4. 连接点 JoinPointUserServiceaddUser()getUserInfo() 两个被匹配到的方法
  5. 目标对象 Target:原始 UserService 对象
  6. 代理对象 Proxy:Spring 自动生成 UserService 的代理对象,容器注入的实际是代理
  7. 织入 Weaving:Spring 容器启动时,自动把切面日志逻辑嵌入业务方法,运行时生效

执行流程演示(调用userService.getUserInfo())

  1. IOC容器注入的不是原生UserService,而是动态代理对象Proxy
  2. 调用 getUserInfo,先进入环绕通知 @Around
  3. 执行环绕前置代码 → 执行 @Before 前置通知
  4. 执行原始业务方法 getUserInfo()
  5. 方法正常返回,执行 @AfterReturning 返回通知
  6. 执行 @After 最终通知
  7. 回到环绕通知,打印耗时,返回结果

核心优势体现

业务层 UserService 完全只写业务逻辑,日志打印横向通用代码全部抽离到切面;
后续想修改日志规则,只改切面,无需改动任何业务代码,实现业务与通用功能解耦。

拓展经典场景:事务 AOP

@Transactional 底层原理完全一致:

  1. 切面拦截加了事务注解的方法
  2. 环绕通知中开启事务 → 执行业务 → 无异常提交事务 → 异常回滚事务
    不需要在每个业务方法手动写 commit/rollback
posted @ 2026-07-17 22:37  alij  阅读(8)  评论(0)    收藏  举报