IntroductionAdvisor,真的很冷门!

2022-12-01 21:38:35 浏览数 (1)

Advisor是Spring AOP中的独有术语,它是一种特殊的切面 (Aspect);Advisor有两个分支,分别是PointcutAdvisorIntroductionAdvisor

PointcutAdvisor持有一个通知(Advice)和一个切入点(Pointcut),Spring AOP 将通知建模为org.aopalliance.intercept.MethodInterctptor拦截器,切入点用于声明应该在哪些连接点(Joinpoint)处应用切面逻辑,而连接点在SpringAOP 中专指方法的执行,因此,PointcutAdvisor中的通知是方法级的拦截器;IntroductionAdvisor仅持有一个通知和一个类过滤器(ClassFilter),显然,IntroductionAdvisor中的通知是类级的拦截器。

IntroductionAdvisor 用于为目标对象引入一个或多个接口,在不改动现有目标对象的情况下,目标对象即可以自动持有所引入接口中的行为。IntroductionAdvisor 所持有的通知就是用来拦截目标对象所引入的接口的。

1入门

需求CustomPrintService提供普通黑白打印服务,而ColorfulPrintService则提供彩色打印服务;在不改动现有CustomPrintService的情况下,如何使其具备彩色打印的能力呢?

代码语言:javascript复制
public interface CustomPrintService {
    public void doPrint(String content);
}
public class CustomPrintServiceImpl implements CustomPrintService {
    @Override
    public void doPrint(String content) {
        System.out.println("黑白打印---"   content);
    }
}
代码语言:javascript复制
public interface ColorfulPrintService {
    public void doColorfulPrint(String content);
}
public class ColorfulPrintServiceImpl implements ColorfulPrintService {
    @Override
    public void doColorfulPrint(String content) {
        System.out.println("彩色打印---"   content);
    }
}

在Spring AOP中,能否为目标对象生成代理取决于该目标对象是否有匹配的Advisor,那自然应该从IntroductionAdvisor入手了。DefaultIntroductionAdvisor实现了IntroductionAdvisor接口,一般使用它就行了;在构造DefaultIntroductionAdvisor实例前,需要先构造一个IntroductionInterceptor实例,体贴的官方开发人员为大家预置了DelegatingIntroductionInterceptor,基本可以满足绝大多数场景;当然,大家也可以自行实现IntroductionInterceptor或继承 DelegatingIntroductionInterceptor。

代码语言:javascript复制
public class DelegatingIntroductionInterceptor extends IntroductionInfoSupport implements IntroductionInterceptor {
    // delegate,意为“委派”,即委派引入接口的实现类处理
    private Object delegate;

    public DelegatingIntroductionInterceptor(Object delegate) {
        init(delegate);
    }

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        // 如果当前方法调用指向引入接口,如ColorfulPriintService#doColorfulPrint()
        if (isMethodOnIntroducedInterface(mi)) {
            // 则通过反射调用引入接口的实现类中的对应方法
            return AopUtils.invokeJoinpointUsingReflection(this.delegate, mi.getMethod(), mi.getArguments());
        }
        // 如果当前方法调用没有指向引入接口,那相当于啥都没发生,继续执行拦截器链中下一个拦截器
        return doProceed(mi);
    }

    protected Object doProceed(MethodInvocation mi) throws Throwable {
        return mi.proceed();
    }
}

DelegatingIntroductionInterceptor中invoke()方法会判断当前的方法调用是否指向引入接口,若是,则直接通过反射调用引入接口的实现类中相关方法。

1.1 基于JDK动态代理

代码语言:javascript复制
public static void main(String[] args) {
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.setInterfaces(CustomPrintService.class);
    proxyFactory.setTargetSource(new SingletonTargetSource(new CustomPrintServiceImpl()));
    proxyFactory.setProxyTargetClass(false);

    // DelegatingIntroductionInterceptor构造方法中必须是所引入接口的实现类,
    // 其内部会通过ClassUtils.getAllInterfacesAsSet(delegate)来获取引入接口;
    // DelegatingIntroductionInterceptor实现了IntroductionInfo接口,
    // 所以DefaultIntroductionAdvisor可以通过它的getInterfaces()获取引入的接口。
    DelegatingIntroductionInterceptor delegatingIntroductionInterceptor = new DelegatingIntroductionInterceptor(new ColorfulPrintServiceImpl());
    DefaultIntroductionAdvisor defaultIntroductionAdvisor = new DefaultIntroductionAdvisor(delegatingIntroductionInterceptor);
    // ProxyFactory内部维护了一个interfaces列表,后期所生成的代理类将实现这些接口;
    // 当向ProxyFactory注册IntroductionAdvisor时,会通过IntroductionAdvisor#getInterfaces()来填充interfaces列表;
    proxyFactory.addAdvisor(defaultIntroductionAdvisor);

    CustomPrintService customPrintService = (CustomPrintService) proxyFactory.getProxy();
    customPrintService.doPrint("introduction advisor");

    ColorfulPrintService colorfulPrintService = (ColorfulPrintService) proxyFactory.getProxy();
    colorfulPrintService.doColorfulPrint("introduction advisor");
}

1.2 基于CGLIB代理

代码语言:javascript复制
public static void main(String[] args) {
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.setTargetSource(new SingletonTargetSource(new CustomPrintServiceImpl()));
    proxyFactory.setProxyTargetClass(true);

    // DelegatingIntroductionInterceptor构造方法中必须是所引入接口的实现类,
    // 其内部会通过ClassUtils.getAllInterfacesAsSet(delegate)来获取引入接口;
    // DelegatingIntroductionInterceptor实现了IntroductionInfo接口,
    // 所以DefaultIntroductionAdvisor可以通过它的getInterfaces()获取引入的接口。
    DelegatingIntroductionInterceptor delegatingIntroductionInterceptor = new DelegatingIntroductionInterceptor(new ColorfulPrintServiceImpl());
    DefaultIntroductionAdvisor defaultIntroductionAdvisor = new DefaultIntroductionAdvisor(delegatingIntroductionInterceptor);
    // ProxyFactory内部维护了一个interfaces列表,后期所生成的代理类将实现这些接口;
    // 当向ProxyFactory注册IntroductionAdvisor时,会通过IntroductionAdvisor#getInterfaces()来填充interfaces列表;
    proxyFactory.addAdvisor(defaultIntroductionAdvisor);

    CustomPrintService customPrintService = (CustomPrintService) proxyFactory.getProxy();
    customPrintService.doPrint("introduction advisor");

    ColorfulPrintService colorfulPrintService = (ColorfulPrintService) proxyFactory.getProxy();
    colorfulPrintService.doColorfulPrint("introduction advisor");
}

执行结果

代码语言:javascript复制
黑白打印---introduction advisor
彩色打印---introduction advisor

2IntroductionAdvisor应用案例解读

天哪!IntroductionAdvisor实在是太冷门了,找了一圈,目前只看到Spring Retry模块用到了它。关于Spring Retry的相关知识,请参考《初探Spring Retry》。

RetryConfiguration既继承了AbstractPointcutAdvisor,又实现了IntroductionAdvisor;讲道理,仅仅继承AbstractPointcutAdvisor就足够了,可为啥还要实现IntroductionAdvisor呢?从其源码来看,IntroductionAdvisor相关的两个方法,RetryConfiguration只真正实现了getInterfaces()方法,该方法表明RetryConfiguration为目标对象引入了Retryable接口。

代码语言:javascript复制
public class RetryConfiguration extends AbstractPointcutAdvisor implements IntroductionAdvisor {
    @Override
    public Class<?>[] getInterfaces() {
        return new Class[] { org.springframework.retry.interceptor.Retryable.class };
    }
    
    @Override
    public void validateInterfaces() throws IllegalArgumentException {}
}

可跟进到Retryable的源码后,发现这只是一个标记接口,类似java.io.Serializable;难道RetryConfiguration实现IntroductionAdvisor接口,只是为了标识所生成的代理类具备重试能力?

代码语言:javascript复制
public interface Retryable {}

验证上述猜想最有效的方法就是找到Spring Retry模块中针对IntroductionInterceptor接口的实现类!这对Intellij IDEA来说简直小菜一碟嘛,这个类就是AnnotationAwareRetryOperationsInterceptor。实锤,上述猜想成立!!!

代码语言:javascript复制
public class AnnotationAwareRetryOperationsInterceptor implements IntroductionInterceptor {
    @Override
    public boolean implementsInterface(Class<?> intf) {
        return org.springframework.retry.interceptor.Retryable.class.isAssignableFrom(intf);
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        MethodInterceptor delegate = getDelegate(invocation.getThis(), invocation.getMethod());
        if (delegate != null) {
            return delegate.invoke(invocation);
        }
        else {
            return invocation.proceed();
        }
    }

    private MethodInterceptor getDelegate(Object target, Method method) {
        MethodInterceptor delegate = null;
        Retryable retryable = findAnnotationOnTarget(target, method, Retryable.class);
        if (retryable.stateful()) {
            // StatefulRetryOperationsInterceptor
            delegate = getStatefulInterceptor(target, method, retryable);
        } else {
            // RetryOperationsInterceptor
            delegate = getStatelessInterceptor(target, method, retryable);
        }
        return delegate;
    }
}

3总结

由于IntroductionAdvisor所持有的通知是类级的拦截器,所以IntroductionAdvisor的应用场景很少,一般多使用PointcutAdvisor。另外,无论是基于JDK动态代理还是基于CGLIB代理,最终所生成的代理类都会实现引入的接口,否则无法强制类型转换啊。

0 人点赞