Skip to content

引入 (Introductions)

引入(Introductions,在 AspectJ 中称为类型间声明 / inter-type declarations)使切面能够声明:被通知的对象实现给定的接口,并由切面代表这些对象提供该接口的实现。

你可以通过使用 @DeclareParents 注解来实现“引入”。该注解用于声明匹配的类型拥有一个新的父接口(因此得名)。例如,给定一个接口 UsageTracked 及其实现类 DefaultUsageTracked,以下切面声明所有 Service 接口的实现者也都实现了 UsageTracked 接口(例如:为了通过 JMX 进行统计):

java
@Aspect
public class UsageTracking {

	@DeclareParents(value="com.xyz.service.*+", defaultImpl=DefaultUsageTracked.class)
	public static UsageTracked mixin;

	@Before("execution(* com.xyz..service.*.*(..)) && this(usageTracked)")
	public void recordUsage(UsageTracked usageTracked) {
		usageTracked.incrementUseCount();
	}

}
kotlin
@Aspect
class UsageTracking {

	companion object {
		@DeclareParents(value = "com.xyz.service.*+",
			defaultImpl = DefaultUsageTracked::class)
		lateinit var mixin: UsageTracked
	}

	@Before("execution(* com.xyz..service.*.*(..)) && this(usageTracked)")
	fun recordUsage(usageTracked: UsageTracked) {
		usageTracked.incrementUseCount()
	}
}

要实现的接口由被注解字段的类型决定。@DeclareParents 注解的 value 属性是一个 AspectJ 类型模式(Type pattern)。任何匹配该类型的 Bean 都会实现 UsageTracked 接口。

注意,在上述示例的前置通知中,Service Bean 可以直接作为 UsageTracked 接口的实现来使用。如果以编程方式访问该 Bean,你可以编写如下代码:

java
UsageTracked usageTracked = context.getBean("myService", UsageTracked.class);
kotlin
val usageTracked = context.getBean<UsageTracked>("myService")

补充教学

1. 什么是“引入”?(Mixin 模式)

在传统的 OOP 中,如果一个类想实现某个接口,必须在源文件中显式写上 implements InterfaceName“引入” (Introduction) 打破了这一规则:它允许你在不修改原始类代码的情况下,动态地给这个类贴上一个“新标签”(接口),并注入一份“全套实现”。这种模式在其他编程语言中常被称为 Mixin(混入)Trait

2. 为何需要 defaultImpl

Spring AOP 需要知道:当它强行给一个 Bean 加上 UsageTracked 接口后,如果有人调用该接口的方法,具体的执行逻辑在哪里?

  • defaultImpl 指定了提供默认实现逻辑的类。
  • Spring 会在底层创建一个代理,该代理不仅包含原始 Bean 的逻辑,还包含一个 defaultImpl 的实例,用于分发新接口的方法调用。

3. 类型模式中的 *+ 代表什么?

value="com.xyz.service.*+" 表达式中:

  • com.xyz.service.* 匹配该包下的任何类或接口。
  • 最后的 + 是关键:它代表“该类及其所有子类或子接口”。
  • 这意味着你不仅影响了当前的类,还影响了它的整个继承体系。

4. 状态是共享的吗?

这是一个常见的困惑。如果我有 10 个 Service Bean 都被“引入”了 UsageTracked,它们是共享同一个计数器吗?

  • 不是的。由于 @DeclareParents 是实例级别的增强,Spring AOP 会为每个匹配到的 Bean 实例分配一个独立的 defaultImpl 实例。
  • 因此,每个 Service Bean 都有自己独立的 usageCount 计数器,不会互相干扰。

5. 实战场景示例

  • 审计/统计:如文档中的例子,为所有业务 Bean 动态增加一个 getCreationTime()getInvokeCount() 接口。
  • 状态标记:为一个 Bean 动态引入 ActiveCondition 接口,用于标记该对象当前是否可用,而无需改动业务逻辑。
  • 多重继承模拟:虽然 Java 不支持多继承,但通过 AOP 引入,你可以让一个 Bean “看起来”同时拥有多种互不相关的能力。

Based on Spring Framework.