SpringBoot2.x下Advice和MethodInterceptor的区别

在这两篇文章中,我们学会了借助Spring 自动代理,定义自己的Advisor来增强Bean。

SpringBoot2.x下“AOP自动代理”实战—注解版日志打印
SpringBoot2.x下“后置处理器”完成AOP代理实战—注解版日志打印

但是小伙伴在SpringBoot2.x下“后置处理器”完成AOP代理实战—注解版日志打印中却发现,Spring使用MethodInterceptor来充当Advice。

public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
        implements InitializingBean {

    private Class<? extends Annotation> validatedAnnotationType = Validated.class;
    
    ...
    //定义一个Advisor使用的是pointcut切点和Advice
    @Override
    public void afterPropertiesSet() {
        Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
        this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
    }
    //这个方法返回的却是MethodValidationInterceptor对象。
    protected Advice createMethodValidationAdvice(@Nullable Validator validator) {
        return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
    }
}

显而易见,MethodInterceptorAdvice的子类。

但是我们在实战中却是使用的MethodBeforeAdvice,看上去并未和MethodInterceptor有交集。在执行AOP代理增强方法时,是执行Advice还是执行MethodInterceptor???

前置通知的继承关系图.png

那么Advice和MethodInterceptor到底是什么关系?我们在实际扩展AOP时,是使用Advice还是使用MethodInterceptor做通知???

1. MethodInterceptor结构

/**
 * 用户可以实现MethodInvocation接口,来修原始的行为,使用方式如下
 *
 * <pre class=code>
 * class TracingInterceptor implements MethodInterceptor {
 *   Object invoke(MethodInvocation i) throws Throwable {
 *     System.out.println("method "+i.getMethod()+" is called on "+
 *                        i.getThis()+" with args "+i.getArguments());
 *     Object ret=i.proceed();
 *     System.out.println("method "+i.getMethod()+" returns "+ret);
 *     return ret;
 *   }
 * }
 * </pre>
 *
 * @author Rod Johnson
 */
@FunctionalInterface
public interface MethodInterceptor extends Interceptor {

    //执行invocation之前和之后可以做一些额外的处理
    Object invoke(MethodInvocation invocation) throws Throwable;

}

感觉MethodInterceptor才真正完成对方法的增强。

2. 将Advice转换为MethodInterceptor

以Cglib代理为例:

SpringBoot2.x下“后置处理器”完成AOP代理实战—注解版日志打印实战代码为例:

代理对象.png

debug代码入口:org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept

执行流程.png

将代码定位到
org.springframework.aop.framework.DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice下。

该方法获取到代理对象所有的Advisor,使用Pointcutmethod进行匹配。若匹配成功(即可以增强该方法)。

抽取关键代码:Advice转换为MethodInterceptor关键代码

@Test 
public void test() {
    AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
    Pointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(MyLogAnno.class);
    Advisor advisor = new DefaultPointcutAdvisor(pointcut, (MethodBeforeAdvice)(method, args, target) - >{
        //打印日志
        log.info("调用方法:[{}]", method);
        log.info("方法参数:[{}]", JSON.toJSONString(args));
        log.info("target类:[{}]", target);
    });
    //将Advisor转换为MethodInterceptor[]数组对象
    MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
}

获取Advice将其转换为MethodBeforeAdviceInterceptor

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {

    @Override
    public boolean supportsAdvice(Advice advice) {
        return (advice instanceof MethodBeforeAdvice);
    }
    //获取到advice将其转换为MethodBeforeAdviceInterceptor
    @Override
    public MethodInterceptor getInterceptor(Advisor advisor) {
        MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
        return new MethodBeforeAdviceInterceptor(advice);
    }

}
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {

    private final MethodBeforeAdvice advice;

    public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
        Assert.notNull(advice, "Advice must not be null");
        this.advice = advice;
    }
    //前置通知是在mi.proceed()前面调用advice的逻辑。
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
        return mi.proceed();
    }
}

注意:mi.proceed();方法实际上是调用的org.springframework.aop.framework.ReflectiveMethodInvocation#proceed方法。该方法在List列表(过滤器链)中循环取出MethodInterceptor对象执行invoke方法(invoke方法又会调用proceed方法,直至链表为空)。

在滤器链空的情况时,执行并返回invokeJoinpoint();。(执行连接点的代码,即target方法代码)。

总结:在调用代理方法时,Advice会被转换为MethodInterceptor对象。invoke方法和proceed方法相互调用,完成功能增强!

3. 实战—改造注解版日志打印

@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyLogAnno {

    Class<?>[] value() default {};

}

直接使用MethodInterceptor作为Advice进行通知。

@Slf4j
@Component
public class MyLogAnnoBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
        implements InitializingBean {

    private Class<? extends Annotation> myLogAnnoType = MyLogAnno.class;


    @Override
    public void afterPropertiesSet() throws Exception {
        //对带有myLogAnno注解的方法进行拦截
        Pointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(myLogAnnoType);
        this.advisor = new DefaultPointcutAdvisor(pointcut,
                (MethodInterceptor) (invocation) -> {
                    //打印日志
                    log.info("调用方法:[{}]", invocation.getMethod());
                    log.info("方法参数:[{}]", JSON.toJSONString(invocation.getArguments()));
                    log.info("target类:[{}]", invocation.getThis());
                    //调用拦截器链的其他方法
                    return invocation.proceed();
                });
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容