【老王读Spring AOP-1】Pointcut 与 join point 如何匹配

前言

通过前面的介绍,我们知道,实现 Spring AOP 大体会分如下几步:

  1. 找到与 bean 匹配的所有的 Advisor
  2. 使用所有匹配的 Advisor 来为 bean 生成生成动态代理
  3. 通过动态代理类执行 Advice
  4. 将 Spring AOP 与 Spring IoC 进行结合

现在我们就针对第一步来进行分析,看 Spring 是如何找到与 bean 匹配的 Advisor 的?

版本约定

Spring 5.3.9 (通过 SpringBoot 2.5.3 间接引入的依赖)

正文

bean 中的每一个方法都是一个 join point,那么 join point 是如何与 Advisor 进行关联的呢?
Spring 中通过 Pointcut 来将 bean 中的 join point 与 Advisor 进行匹配。

Pointcut 由 ClassFilter 和 MethodMatcher 组成。多个 Pointcut 可以组合起来构成 ComposablePointcut

public interface Pointcut {
    ClassFilter getClassFilter();
    MethodMatcher getMethodMatcher();
}

可以看出,Pointcut 匹配类有两个条件:类过滤器 + 方法匹配器。通过这两个条件来筛选匹配的类。

Spring 对 AOP 的抽象

Spring 对 AOP 抽象出了几个关键的类,也叫 AOP 的基础设施类:
Pointcut、Advice、Advisor、AopInfrastructureBean

@see AbstractAutoProxyCreator#isInfrastructureClass()
Spring AOP 的基础设施类是不可以被代理的。

Pointcut:
切点。通过 ClassFilterMethodMatcher 匹配所有的 join point。

Advice:
它是一个标记型接口,没有任何方法,用于标记 Advice 类。

Advisor:
持有 Advice 的基础接口。Advisor 是一个只包含有一个 Advice 对象的切面。

https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-aj-configure

AopInfrastructureBean:
标记型接口,用于表示这个 bean 是 Spring AOP 基础设施的一部分。被标记的 bean 即使被 pointcut 匹配到,也不会被代理。

Pointcut 的类图

pointcut
可以看出,Spring 支持 aspectj 表达式匹配,也支持正则表达式匹配 和 按方法名匹配。

AspectJExpressionPointcut: 对 aspectj 表达式语法的支持。它是 Spring 中最常用的切入点匹配表达式。

Pointcut 如何匹配 join point

这里我们主要研究一下 aspectj 表达式的匹配问题,所以,重点就是 AspectJExpressionPointcut 这个类。
它是通过 AspectJExpressionPointcut#matches(Method, Class<?>, boolean hasIntroductions) 方法来完成匹配的:

public boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions) {
    obtainPointcutExpression();
    ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);

    // Special handling for this, target, @this, @target, @annotation
    // in Spring - we can optimize since we know we have exactly this class,
    // and there will never be matching subclass at runtime.
    if (shadowMatch.alwaysMatches()) {
        return true;
    } else if (shadowMatch.neverMatches()) {
        return false;
    } else {
        // the maybe case
        if (hasIntroductions) {
            return true;
        }
        // A match test returned maybe - if there are any subtype sensitive variables
        // involved in the test (this, target, at_this, at_target, at_annotation) then
        // we say this is not a match as in Spring there will never be a different
        // runtime subtype.
        RuntimeTestWalker walker = getRuntimeTestWalker(shadowMatch);
        return (!walker.testsSubtypeSensitiveVars() || walker.testTargetInstanceOfResidue(targetClass));
    }
}

aspectj 表达式匹配 join point 会通过 AspectJExpressionPointcut#matches() 方法进行匹配。
我们还可以通过 AopUtils.canApply(Advisor, Class, boolean hasIntroductions) 工具类来完成匹配

Tips:
aspectj 表达式是可以支持 and、or、not 的,最终在 parse 的时候会将其转化为 &&、||、!:

// AspectJExpressionPointcut#replaceBooleanOperators()
private String replaceBooleanOperators(String pcExpr) {
    String result = StringUtils.replace(pcExpr, " and ", " && ");
    result = StringUtils.replace(result, " or ", " || ");
    result = StringUtils.replace(result, " not ", " ! ");
    return result;
}

AspectJ expression 匹配测试

public class FooService {
    public void doBiz(){
    }

    public String m1(){
        return null;
    }
    public String m2(int flag){
        return null;
    }
}


public class ExpressionMatchTest {

  public static void main(String[] args) {
    AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
//        pc.setExpression("execution(* com.kvn.aop.expression.FooService.*(..))");
//        pc.setExpression("execution(void com.kvn.aop.expression.*.*(..))");
    pc.setExpression("execution(* com.kvn.aop.expression.*.*(int))");

    // 类级别的匹配
    boolean rlt = AopUtils.canApply(pc, FooService.class);
    System.out.println(pc.getExpression() + ", 匹配:" + FooService.class.getName() + ", 结果:" + rlt);

    System.out.println("--------------------");
    // 方法级别的匹配
    Method[] methods = FooService.class.getDeclaredMethods();
    for (Method method : methods) {
      boolean matches = pc.matches(method, method.getDeclaringClass());
      System.out.println(pc.getExpression() + ", 匹配:" + method + ", 结果:" + matches);
    }
  }

}

例子输出:

execution(* com.kvn.aop.expression.*.*(int)), 匹配:com.kvn.aop.expression.FooService, 结果:true
--------------------
execution(* com.kvn.aop.expression.*.*(int)), 匹配:public void com.kvn.aop.expression.FooService.doBiz(), 结果:false
execution(* com.kvn.aop.expression.*.*(int)), 匹配:public java.lang.String com.kvn.aop.expression.FooService.m1(), 结果:false
execution(* com.kvn.aop.expression.*.*(int)), 匹配:public java.lang.String com.kvn.aop.expression.FooService.m2(int), 结果:true

Spring AOP 支持的 AspectJ 原语类型

Spring AOP 并不是支持所有的 AspectJ 语法,只是对部分语法进行了支持。
Spring AOP 支持的 AspectJ 语法有: execution、args、reference pointcut、this、target、within、@annotation、@within、@args、@target

具体可以看 AspectJExpressionPointcut 的源码:

public class AspectJExpressionPointcut extends AbstractExpressionPointcut
		implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {
    private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();
    static {
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
    }
	......
}

aspectj 所有的原语:

public final class PointcutPrimitive extends TypeSafeEnum {

	public static final PointcutPrimitive CALL = new PointcutPrimitive("call",1);
	public static final PointcutPrimitive EXECUTION = new PointcutPrimitive("execution",2);
	public static final PointcutPrimitive GET = new PointcutPrimitive("get",3);
	public static final PointcutPrimitive SET = new PointcutPrimitive("set",4);
	public static final PointcutPrimitive INITIALIZATION = new PointcutPrimitive("initialization",5);
	public static final PointcutPrimitive PRE_INITIALIZATION = new PointcutPrimitive("preinitialization",6);
	public static final PointcutPrimitive STATIC_INITIALIZATION = new PointcutPrimitive("staticinitialization",7);
	public static final PointcutPrimitive HANDLER = new PointcutPrimitive("handler",8);
	public static final PointcutPrimitive ADVICE_EXECUTION = new PointcutPrimitive("adviceexecution",9);
	public static final PointcutPrimitive WITHIN = new PointcutPrimitive("within",10);
	public static final PointcutPrimitive WITHIN_CODE = new PointcutPrimitive("withincode",11);
	public static final PointcutPrimitive CFLOW = new PointcutPrimitive("cflow",12);
	public static final PointcutPrimitive CFLOW_BELOW = new PointcutPrimitive("cflowbelow",13);
	public static final PointcutPrimitive IF = new PointcutPrimitive("if",14);
	public static final PointcutPrimitive THIS = new PointcutPrimitive("this",15);
	public static final PointcutPrimitive TARGET = new PointcutPrimitive("target",16);
	public static final PointcutPrimitive ARGS = new PointcutPrimitive("args",17);
	public static final PointcutPrimitive REFERENCE = new PointcutPrimitive("reference pointcut",18);
	public static final PointcutPrimitive AT_ANNOTATION = new PointcutPrimitive("@annotation",19);
	public static final PointcutPrimitive AT_THIS = new PointcutPrimitive("@this",20);
	public static final PointcutPrimitive AT_TARGET = new PointcutPrimitive("@target",21);
	public static final PointcutPrimitive AT_ARGS = new PointcutPrimitive("@args",22);
	public static final PointcutPrimitive AT_WITHIN = new PointcutPrimitive("@within",23);
	public static final PointcutPrimitive AT_WITHINCODE = new PointcutPrimitive("@withincode",24);

	private PointcutPrimitive(String name, int key) {
		super(name, key);
	}

}

小结

Spring AOP 抽象出了 Pointcut、Advice、Advisor、AopInfrastructureBean 几个基础设施类。

aspectj 表达式匹配是通过 AspectJExpressionPointcut 来进行支持的。
Spring AOP 只对 aspectj 原语中的部分语法进行了支持!


如果本文对你有所帮助,欢迎点赞收藏!

源码测试工程下载:
老王读Spring IoC源码分析&测试代码下载
老王读Spring AOP源码分析&测试代码下载

公众号后台回复:下载IoC 或者 下载AOP 可以免费下载源码测试工程…

阅读更多文章,请关注公众号: 老王学源码
gzh


系列博文:
【老王读Spring AOP-0】SpringAop引入&&AOP概念、术语介绍
【老王读Spring AOP-1】Pointcut如何匹配到 join point
【老王读Spring AOP-2】如何为 Pointcut 匹配的类生成动态代理类
【老王读Spring AOP-3】Spring AOP 执行 Pointcut 对应的 Advice 的过程
【老王读Spring AOP-4】Spring AOP 与Spring IoC 结合的过程 && ProxyFactory 解析
【老王读Spring AOP-5】@Transactional产生AOP代理的原理
【老王读Spring AOP-6】@Async产生AOP代理的原理
【Spring 源码阅读】Spring IoC、AOP 原理小总结

相关阅读:
【Spring源码三千问】Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy?
【Spring源码三千问】Advice、Advisor、Advised都是什么接口?
【Spring源码三千问】没有AspectJ,Spring中如何使用SpringAOP、@Transactional?
【Spring源码三千问】Spring AOP 中 AbstractAdvisorAutoProxyCreator、AbstractAdvisingBeanPostProcessor的区别
【Spring 源码三千问】同样是AOP代理bean,为什么@Async标记的bean循环依赖时会报错?

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老王学源码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值