#yyds干货盘点# 老王读Spring AOP-1Pointcut如何匹配到 join point

Posted 老王学源码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了#yyds干货盘点# 老王读Spring AOP-1Pointcut如何匹配到 join point相关的知识,希望对你有一定的参考价值。

@[TOC](Pointcut如何匹配到 join point)

前言

通过前面的介绍,我们知道,实现 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

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

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

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

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

Pointcut 的类图


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

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));
    

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 源码方面的问题欢迎一起交流,备注:51cto (vx: Kevin-Wang001)


博主好课推荐:

课程 地址
Dubbo源码解读——通向高手之路 https://edu.51cto.com/course/23382.html
正则表达式基础与提升 https://edu.51cto.com/course/16391.html

以上是关于#yyds干货盘点# 老王读Spring AOP-1Pointcut如何匹配到 join point的主要内容,如果未能解决你的问题,请参考以下文章

#yyds干货盘点# 老王读Spring AOP-4Spring AOP 与Spring IoC 结合的过程 && ProxyFactory 解析

#yyds干货盘点# 老王读Spring AOP-1Pointcut如何匹配到 join point

Spring AOP内功修炼#yyds干货盘点#

#yyds干货盘点# Spring AOP详解

#yyds干货盘点# Spring核心之面向切面编程(AOP)

Spring | Spring5学习笔记 #yyds干货盘点#