天天看点

【老王读Spring AOP-1】Pointcut如何匹配到 join point前言版本约定正文小结

Pointcut如何匹配到 join point

  • 前言
  • 版本约定
  • 正文
    • Spring 对 AOP 的抽象
    • Pointcut 的类图
    • Pointcut 如何匹配 join point
    • AspectJ expression 匹配测试
    • Spring AOP 支持的 AspectJ 原语类型
  • 小结

前言

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

  1. 找到 Pointcut 所匹配的所有 join point 对应的类
  2. 为Pointcut 匹配到的类生成动态代理
  3. 通过动态代理类执行 Pointcut 对应的 Advice
  4. 将 Spring AOP 与 Spring IoC 进行结合

现在我们就针对第一步来进行分析,看 Pointcut 是如何匹配到 join point 对应的类的。

版本约定

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

正文

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:

切点。通过

ClassFilter

MethodMatcher

匹配所有的 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 的类图

【老王读Spring AOP-1】Pointcut如何匹配到 join point前言版本约定正文小结

可以看出,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 原语中的部分语法进行了支持!

如果本文对你有所帮助,欢迎

点赞收藏