Spring AOP 原理

@Aspect

注解在 Spring 中用于实现面向切面编程(AOP)。它标记一个类为一个切面(Aspect),

这是一个非常好的问题,答案取决于你使用的是纯 Spring Framework 还是 Spring Boot

场景 是否需要 @EnableAspectJAutoProxy 是否能正常运行(AOP生效) 为什么?
纯 Spring Framework 需要 不能 这是启用 @AspectJ 支持的必要配置。
Spring Boot 通常不需要 Spring Boot 的自动配置会帮你完成这个工作。

详细解释

1. 纯 Spring Framework (非 Spring Boot)

@EnableAspectJAutoProxy

在传统的 Spring 应用中,仅仅在你的类上添加 @Aspect 是不够的

  • @Aspect 只是一个标记,告诉 AspectJ 编译器或 Spring AOP 框架这个类是一个切面。
  • 你需要一个机制来启用对这些 @Aspect 类的处理,并根据它们的切点(Pointcut)为目标 Bean 创建代理(Proxy)。
  • 在 Java 配置中,这个机制就是 @EnableAspectJAutoProxy 注解,你需要将它放在一个 @Configuration 类上。
  • 在 XML 配置中,对应的元素是 <aop:aspectj-autoproxy/>

如果没有 @EnableAspectJAutoProxy(或其 XML 等效配置),Spring 容器只会将你的 @Aspect 类视为一个普通的 Bean(前提是你同时使用了 @Component@Bean 来注册它),而不会启用 AOP 代理机制,因此切面不会生效。

2. Spring Boot 应用

在 Spring Boot 应用中,情况就完全不同了,这也是你通常可以省略 @EnableAspectJAutoProxy 的原因。

  • 自动配置(Auto-Configuration): Spring Boot 的核心是自动配置。当你添加了 spring-boot-starter-aop 依赖(它会自动包含在许多 Starter 中,例如 spring-boot-starter-web),并且 classpath 中存在 AspectJ 相关的类(例如 Aspect),Spring Boot 的 AopAutoConfiguration 就会自动生效。
  • AopAutoConfiguration 的作用: 这个自动配置类内部会相当于为你添加了 @EnableAspectJAutoProxy 的功能,从而自动启用对 @AspectJ 切面的支持和 AOP 代理的创建。

小提示: 你的切面类仍然需要被 Spring 容器发现和管理,所以你还需要确保它被标记为 @Component(或通过 @Bean 方法注册),并且你的应用程序配置了组件扫描(Component Scanning,通常由 @SpringBootApplication 间接完成)。

通知类型 @Around @Before @After @AfterThrowing 区别

虽然它们都是 AOP 的通知类型,但在代码编写控制目标方法执行流方面有本质的区别。


核心区别:对目标方法的控制能力

这几种通知(Advice)在实现上最大的区别在于:你是否能控制目标方法的执行,以及你是否能访问修改执行结果。

1. ⚔️ @Around (环绕通知)

方面 描述
代码实现 必须接受一个 ProceedingJoinPoint 参数。
执行控制 最高控制权。你必须显式调用 pjp.proceed() 方法才能执行目标方法。
返回值 你的环绕方法必须返回 pjp.proceed() 的结果,或者返回一个自定义的值来代替目标方法的实际返回值。
异常处理 必须使用 try...catch 块来包裹 pjp.proceed(),从而处理或重新抛出目标方法抛出的异常。
核心区别 它是唯一能阻止目标方法执行、替换返回值、并完全控制异常流的通知。

代码示例:

@Around("execution(* com.example.service.*.*(..))")
public Object profile(ProceedingJoinPoint pjp) throws Throwable {
    // 1. 相当于 @Before 逻辑
    long start = System.currentTimeMillis();

    Object result;
    try {
        // 2. 核心:手动执行目标方法
        result = pjp.proceed(); 
    } catch (Throwable e) {
        // 3. 相当于 @AfterThrowing 逻辑
        log.error("Method failed: " + e.getMessage());
        throw e; // 必须重新抛出异常
    }
    
    // 4. 相当于 @AfterReturning 逻辑
    long end = System.currentTimeMillis();
    log.info(pjp.getSignature() + " executed in " + (end - start) + "ms");
    
    // 5. 返回结果
    return result; 
}

2. ➡️ @Before (前置通知)

方面 描述
代码实现 通常接受一个 JoinPoint 参数(可选)。
执行控制 无控制权。它只是在目标方法执行前被调用,无法阻止目标方法自动执行。
返回值 返回值会被忽略。
异常处理 如果 @Before 方法抛出异常,它会阻止目标方法的执行,并将异常传播给调用者。
核心区别 最纯粹的预处理。只能在执行前做检查或记录,不能影响目标方法的正常执行流程(除非抛出异常)。

代码示例:

@Before("execution(* com.example.service.*.*(..))")
public void doAccessCheck(JoinPoint jp) {
    // 检查用户是否有权限,如果没有,可以抛出 SecurityException
    if (!checkPermission()) {
        throw new SecurityException("No access."); // 阻止目标方法执行
    }
}

3. 🔚 @After (后置/最终通知)

方面 描述
代码实现 通常接受一个 JoinPoint 参数(可选)。
执行控制 无控制权。它在目标方法执行完毕后自动执行,不依赖于执行结果(成功或失败)。
返回值 返回值会被忽略。
异常处理 目标方法中抛出的异常会被 @After 忽略,但会继续向上传播。 @After 自身抛出的异常会覆盖目标方法抛出的异常。
核心区别 保证执行。主要用于资源清理,类似 Java 的 finally 块。

代码示例:

@After("execution(* com.example.service.*.*(..))")
public void releaseResource(JoinPoint jp) {
    // 无论目标方法是否成功,都确保执行资源清理
    System.out.println("Closing database connection or releasing lock...");
}

4. ❌ @AfterThrowing (异常通知)

方面 描述
代码实现 必须使用 throwing 属性来指定一个参数,用于接收目标方法抛出的异常对象。
执行控制 无控制权。只有当目标方法抛出异常时才执行。
返回值 返回值会被忽略。
异常处理 你的通知方法可以捕获并处理这个异常。如果 @AfterThrowing 方法本身不抛出新异常,原异常会继续向上传播。
核心区别 专注于错误日志和报告。它使你能够在不污染业务代码的情况下集中处理异常。

代码示例:

@AfterThrowing(
    pointcut = "execution(* com.example.service.*.*(..))",
    throwing = "ex" // 必须指定一个名称,用于在方法签名中接收异常
)
public void logError(JoinPoint jp, Exception ex) {
    // 记录异常信息,并发送告警邮件
    log.error("Method execution failed: " + jp.getSignature().getName() + ", Reason: " + ex.getMessage());
}

总结对比图

通知类型 目标方法执行前 目标方法执行时机 目标方法执行后 (无论结果) 目标方法抛出异常时 目标方法成功返回时
@Around ✅ (在 pjp.proceed() 前) 由你控制 (通过 pjp.proceed()) ✅ (在 pjp.proceed() 后) ✅ (通过 try/catch 捕获) ✅ (在 pjp.proceed() 后)
@Before ❌ (自动执行)
@After ❌ (自动执行)
@AfterThrowing ❌ (自动执行)

参考资料

https://www.cnblogs.com/shanml/p/16125631.html
https://www.cnblogs.com/shanml/p/16126116.html
王者荣耀十周年送限时点券,燃灯寄梦最高得2000!【高级河灯曜J58ATH】我是高级河灯,点亮可获得80限时点券,复制口令领!

posted @ 2025-10-20 19:37  向着朝阳  阅读(9)  评论(0)    收藏  举报