声明切面 (Declaring an Aspect)
在启用了 @AspectJ 支持的情况下,Spring 会自动探测应用上下文中定义的任何 Bean,如果其对应的类是一个 @AspectJ 切面(即标注了 @Aspect 注解),Spring 就会利用它来配置 Spring AOP。
以下两个示例展示了声明一个切面所需的最少步骤。
第一个示例展示了应用上下文中的一个普通 Bean 定义,它指向一个标注了 @Aspect 的 Bean 类:
public class ApplicationConfiguration {
@Bean
public NotVeryUsefulAspect myAspect() {
NotVeryUsefulAspect myAspect = new NotVeryUsefulAspect();
// 在此配置切面的属性
return myAspect;
}
}class ApplicationConfiguration {
@Bean
fun myAspect() = NotVeryUsefulAspect().apply {
// 在此配置切面的属性
}
}<bean id="myAspect" class="org.springframework.docs.core.aop.ataspectj.aopataspectj.NotVeryUsefulAspect">
<!-- 在此处配置切面的属性 -->
</bean>第二个示例展示了 NotVeryUsefulAspect 类的定义,该类使用了 @Aspect 注解:
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}import org.aspectj.lang.annotation.Aspect
@Aspect
class NotVeryUsefulAspect切面(标注了 @Aspect 的类)可以像任何其他类一样拥有方法和字段。它们还可以包含切入点(Pointcut)、通知(Advice)和引入(Introduction/Inter-type)的声明。
通过组件扫描自动探测切面
你可以通过多种方式注册切面类:
- 在 Spring XML 配置中注册为常规
<bean>。 - 在
@Configuration类中通过@Bean方法注册。 - 类路径扫描:让 Spring 像探测其他普通 Bean 一样自动探测切面。
注意:仅靠 @Aspect 注解不足以让 Spring 进行自动探测。你必须同时添加 @Component 注解(或者按照 Spring 组件扫描规则添加自定义的构型注解,如 @Service 等)。
切面能被其他切面通知吗?
在 Spring AOP 中,切面本身不能成为其他切面通知的目标。类上的 @Aspect 注解将其标记为一个切面,因此会将其从自动代理机制中排除。
补充教学
1. 切面类的“隐身性”
虽然切面类被注册为 Spring 容器中的一个 Bean,但你通常不会在代码中通过 @Autowired 去注入一个切面类并调用它的方法。切面更多是按照“声明式”的规则在后台静默工作。 如果你发现自己需要频繁手动调用切面类的方法,那可能说明这部分逻辑应该抽象到一个普通的 Service 类中,而不是放在切面里。
2. 为什么需要 @Component?
这是新手最容易混淆的一点:
@Aspect:告诉 Spring,“这是一个 AOP 规则定义类,请解析它里面的通知和切点”。它属于 AspectJ 的范畴。@Component:告诉 Spring,“请把这个类实例化并放入你的 IoC 容器中”。它属于 Spring IoC 的范畴。 如果一个切面类没有被放入容器,Spring AOP 根本没机会扫描到它,切面自然也就不会生效。
3. 切面的生命周期(单例 vs 原型)
默认情况下,应用上下文中的每一个切面都是一个单例(Singleton)。
- 这符合大多数横切关注点的需求(如:日志记录器、事务管理器)。
- 如果你在切面类中定义了非线程安全的成员变量,必须非常小心,因为所有并发请求都会共享这个切面实例。
- 虽然 Spring 支持通过
perthis或pertarget改变切面生命周期,但在企业开发中极少用到。
4. 切面排他性
官方提到的“切面不能被其他切面通知”,是为了防止 AOP 逻辑陷入死循环。 想象一下:如果切面 A 的逻辑触发了切面 B,切面 B 的逻辑又触发了切面 A,程序就会崩溃。因此,Spring AOP 从设计上就禁止了对切面本身的代理。