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限时点券,复制口令领!