Introduction Advice

tiger1000 / 152 /

ChatGPT 可用网址,仅供交流学习使用,如对您有所帮助,请收藏并推荐给需要的朋友。
https://ckai.xyz

1、Advice与MethodInterceptor

Advice是被某个切面在特定的连接点采取的操作。Spring 提供了多种Advice,可以方便扩展。Advice的类型包括around(环绕advice)、before(前置advice) 和 after(后置advice)。在Spring中将Advice建模为一个Interceptor。下图是Spring 中常用的Advice的继承图。

图中的Advice接口时整个Advice体系的根接口,Advice接口的全路径的类名是:org.aopalliance.aop.Advice。这个类是Aop联盟定义的类。

Advice的子类带有Aspectj开头,这些类是包装Aspectj注解的advice方法。

Spring支持仅支持方法级别的链接点,因此Spring的Advice最终都会通过MethodInterceptor 来执行invoke方法。

Object invoke(@Nonnull MethodInvocation invocation) throws Throwable;

那么可能会有疑问,图中的AspectJMethodBeforeAdvice 没有实现MethodInterceptor,那么是怎么实现Advice操作的呢?Advise和Pointcut一起组成了Advisor,Spring在创建代理对象的时候会查找所有适用对象方法的Advisor,然后通过Advisor创建出MethodInterceptor,具体代码可以参考:AdvisorAdapter及其实现。

今天我们要介绍的是Introduction Advice。

Introduction Advice(引入通知)

Spring 对待introduction advice 作为一种特殊的拦截通知.
引入(Introduction)需要一个IntroductionAdvisor 和一个实现接口IntroductionInterceptor的类。

public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);
}

继承自MethodInterceptor接口的invoke()方法需要实现引入功能。也就是说,如果调用的方法实在一个引入接口方法,引入拦截器负责处理方法调用,而不需要调用连接点的proceed();
Introduction advice 不能和任何pointcut使用,因为它仅适用于类,而不是方法级别。只能用IntroductionAdvisor 来使用introduction advice 。

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
  ClassFilter getClassFilter();
  void validateInterfaces() throws IllegalArgumentException;
}
public interface IntroductionInfo {
  Class<?>[] getInterfaces();
}

getInterfaces()方法返回被这个Advisor对象引入的接口。
validateInterfaces()方法被内部用来检查引入的接口能否被配置的IntroductionInterceptor来实现。

来看一个Spring test套件的例子,假设我们想将以下接口引入到一个或者很多个对象中:

public interface Lockable {
  void lock();
  void unlock();
  boolean locked();
}

这个例子解释了一个混入的实现。我们想要将一个无论任何类型的类转换为Lockable,并能调用lock()和unlock()方法。如果我们调用lock()方法,我们想要实现所有的setter方法会抛出LockedException.因此,我们可以通过一个切面来提供在对一个对象毫不了解的情况下来实现不可变的功能。

首先,我们需要实现一个IntroductionInterceptor来实现这个艰巨任务。我们可以通过扩展org.springframework.aop.support.DelegatingIntroductionInterceptor 便利类。当然我们也可以直接实现IntroductionInterceptor,但是使用DelegatingIntroductionInterceptor在大多数情况下是最好的选择。

DelegatingIntroductionInterceptor 旨在将介绍委托给所引入接口的真实实现,从而隐藏了拦截的使用。可以通过构造参数设置任意类型的代理。默认的代理(构造参数没有参数的话)是this对象。因此,在这个例子中,代理对象是实现LockMixin的子类DelegatingIntroductionInterceptor。

public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {
  private boolean locked;
  public void lock() {
  this.locked = true;
  }
  public void unlock() {
  this.locked = false;
  }
  public boolean locked() {
  return this.locked;
  }
  public Object invoke(MethodInvocation invocation) throws Throwable {
  if (locked() && invocation.getMethod().getName().indexOf("set") == 0) {
  throw new LockedException();
  }
  return super.invoke(invocation);
  }
}

给定一个代理(默认是this对象),DelegatingIntroductionInterceptor 实例查找这个代理实现的所有接口(除了IntroductionInterceptor),并且通过他们支持引入。LockMixin可以通过调用suppressInterface方法来取消不应该被暴露的接口。

然而,无论一个IntroductionInterceptor准备支持多少个接口,IntroductionAdvisor都会控制实际暴露哪些接口。 引入的接口隐藏了目标对同一接口的任何实现.

LockMixin扩展了DelegatingIntroductionInterceptor 并且实现了Lockable。父类(DelegatingIntroductionInterceptor) 自动检查到Lockable接口可以被支持引入,我们不需要特别指定。通常不需要覆写invoke()方法。DelegatingIntroductionInterceptor的实现(如果方法是引入方法,那么调用代理方法,否则继续前往连接点)基本上就满足了。在LockMixIn例子中,,invoke()方法进行了一个set方法检测。

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
  public LockMixinAdvisor() {
  super(new LockMixin(), Lockable.class);
  }
}

通常引入通知的Advisor是per-instance(每一个代理对象对应一个Advisor实例),以为引入是有状态的。如何控制per-instance,在Advisor的isPerInstance()中有解释,使用sigleton和prototype范围的bean定义或者适当的代理创建编程。


作者
tiger1000
许可协议
CC BY 4.0
发布于
2023-08-27
修改于
2024-12-22
Bonnie image
尚未登录