Skip to content

操作被通知的对象 (Manipulating Advised Objects)

无论你如何创建 AOP 代理,你都可以使用 org.springframework.aop.framework.Advised 接口来操作它们。任何 AOP 代理都可以强转为此接口,无论它实现了哪些其他接口。该接口包含以下方法:

java
Advisor[] getAdvisors();

void addAdvice(Advice advice) throws AopConfigException;

void addAdvice(int pos, Advice advice) throws AopConfigException;

void addAdvisor(Advisor advisor) throws AopConfigException;

void addAdvisor(int pos, Advisor advisor) throws AopConfigException;

int indexOf(Advisor advisor);

boolean removeAdvisor(Advisor advisor) throws AopConfigException;

void removeAdvisor(int index) throws AopConfigException;

boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;

boolean isFrozen();
kotlin
fun getAdvisors(): Array<Advisor>

@Throws(AopConfigException::class)
fun addAdvice(advice: Advice)

@Throws(AopConfigException::class)
fun addAdvice(pos: Int, advice: Advice)

@Throws(AopConfigException::class)
fun addAdvisor(advisor: Advisor)

@Throws(AopConfigException::class)
fun addAdvisor(pos: Int, advisor: Advisor)

fun indexOf(advisor: Advisor): Int

@Throws(AopConfigException::class)
fun removeAdvisor(advisor: Advisor): Boolean

@Throws(AopConfigException::class)
fun removeAdvisor(index: Int)

@Throws(AopConfigException::class)
fun replaceAdvisor(a: Advisor, b: Advisor): Boolean

fun isFrozen(): Boolean

getAdvisors() 方法为添加到工厂的每个 Advisor、拦截器或其他通知类型返回一个 Advisor。如果你添加了一个 Advisor,则返回的对象就是你添加的那个。如果你添加的是拦截器或其他通知类型,Spring 会将其包装在一个始终返回 true 的切入点的 Advisor 中。因此,如果你添加了一个 MethodInterceptor,返回的 Advisor 将是一个 DefaultPointcutAdvisor,它包含了你的 MethodInterceptor 以及一个匹配所有类和方法的切入点。

addAdvisor() 方法可用于添加任何 Advisor。通常,持有切入点和通知的 Advisor 是通用的 DefaultPointcutAdvisor,它可以与任何通知或切入点一起使用(但不能用于“引入”)。

默认情况下,即使代理已经创建,也可以添加或移除 Advisor 或拦截器。唯一的限制是无法添加或移除引入 (Introduction) Advisor,因为工厂中现有的代理无法感知接口的变化。(你可以从工厂获取一个新代理来规避此问题。)

以下示例展示了将 AOP 代理转换为 Advised 接口,并检查和操作其通知:

java
Advised advised = (Advised) myObject;
Advisor[] advisors = advised.getAdvisors();
int oldAdvisorCount = advisors.length;
System.out.println(oldAdvisorCount + " advisors");

// 添加一个没有切入点的通知(如拦截器)
// 将匹配所有被代理的方法
// 可用于拦截器、前置、后置返回或异常通知
advised.addAdvice(new DebugInterceptor());

// 使用切入点添加选定通知
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));

assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);
kotlin
val advised = myObject as Advised
val advisors = advised.advisors
val oldAdvisorCount = advisors.size
println("$oldAdvisorCount advisors")

// 添加一个没有切入点的通知(如拦截器)
// 将匹配所有被代理的方法
// 可用于拦截器、前置、后置返回或异常通知
advised.addAdvice(DebugInterceptor())

// 使用切入点添加选定通知
advised.addAdvisor(DefaultPointcutAdvisor(mySpecialPointcut, myAdvice))

assertEquals("Added two advisors", oldAdvisorCount + 2, advised.advisors.size)

TIP

在生产环境的业务对象上修改通知是否明智是有待商榷的(尽管确实存在合法的用例)。然而,这在开发阶段(例如在测试中)非常有用。有时,以拦截器或其他通知的形式添加测试代码,渗透到我们想要测试的方法调用内部,是非常实用的。(例如,通知可以渗透到为该方法创建的事务内部,运行 SQL 来检查数据库是否已正确更新,然后再将事务标记为回滚。)

根据你创建代理的方式,你通常可以设置一个 frozen(冻结)标志。在这种情况下,AdvisedisFrozen() 方法返回 true,任何通过添加或移除来修改通知的尝试都将导致 AopConfigException。在某些情况下,冻结被通知对象的状态是很有用的(例如,防止调用代码移除安全拦截器)。


补充教学

1. 深度解析:为什么代理对象可以强转为 Advised

当你使用 Spring AOP 创建代理(无论是 JDK 动态代理还是 CGLIB 代理)时,生成的代理类不仅会实现你指定的业务接口,默认还会实现 org.springframework.aop.framework.Advised 接口

  • JDK 代理:Spring 在调用 Proxy.newProxyInstance 时,会将 Advised.class 加入接口列表中。
  • CGLIB 代理:生成的子类会直接实现 Advised 接口。

这就是为什么你总能在代码中看到 (Advised) proxy 这种看似魔幻的强转。

2. 动态修改拦截器的妙用

文档中提到的测试场景非常经典。但在生产环境中,它也可以用于:

  • 热插拔功能:例如在系统负载过高时,动态给某些接口添加一个“熔断拦截器”或“限流拦截器”,待负载降低后再移除。
  • 动态日志开关:无需重启应用,通过管理后台获取到 Bean 的代理,动态注入一个 DebugInterceptor 来排查线上问题。

3. 安全锁:Frozen 配置

正如文档所言,frozen 属性是系统的安全保障。

  • 开发建议:如果你编写的是核心基础设施(如权限控制、多租户隔离),建议在初始化代理后立即调用 setFrozen(true)
  • 防止篡改:这可以防止其他开发人员或恶意脚本通过强转 Advised 接口来绕过你的安全逻辑。

4. 优先级与位置

addAdvice(int pos, Advice advice) 允许你指定位置。

  • 位置 0:表示这个拦截器将成为拦截器链的最外层,第一个被触发。
  • 默认位置:不带索引的 addAdvice 通常会将新通知添加到链的末尾(最接近目标对象)。
  • 注意:修改位置会直接改变通知的执行顺序(见之前的 Advisor 排序讲解)。

Based on Spring Framework.