原 荐 Spring Aop原理之切点表达式解析

栏目: 编程工具 · 发布时间: 6年前

内容简介:在前面的文章(这里我们首先举一个典型的切点表达式的例子:该切点将会匹配如下条件的反面的的所有方法:使用public修饰,返回值为void类型,类为com.business.Dog的方法,并且这些方法必须满足有一个名称为name的参数。

在前面的文章( Spring AOP切点表达式详解 )中,我们总结了Spring Aop切点表达式的用法,而在上文( Spring Aop原理之Advisor过滤 )中我们讲到,切点表达式的解析主要是在 PatternParser.parsePointcut() 方法中进行的。本文的主要目的是讲解Spring Aop是如何递归的对切点表达式进行解析,并且最终封装为一个Pointcut对象的。

这里我们首先举一个典型的切点表达式的例子:

@Pointcut(value = "!execution(public protected void com.business.Dog.*(..)) && args(name)", argNames = "name")

该切点将会匹配如下条件的反面的的所有方法:使用public修饰,返回值为void类型,类为com.business.Dog的方法,并且这些方法必须满足有一个名称为name的参数。

1. 解析入口

如下是 PatternParser.parsePointcut() 方法的实现:

public Pointcut parsePointcut() {
    // 转换一个Pointcut单元,比如上述的execution和args都属于一个切点单元
    Pointcut p = parseAtomicPointcut();
    
    // 判断当前切点单元之后是否是一对&&符号,如果是,则递归的将&&后面的切点表达式解析为一个Pointcut
    // 对象,然后将两个Pointcut对象使用AndPointcut连接起来
    if (maybeEat("&&")) {
        p = new AndPointcut(p, parseNotOrPointcut());
    }

    // 判断当前切点单元之后是否是以对||符号,如果是,则递归的将||后面的切点表达式解析为一个Pointcut
    // 对象,然后将两个Pointcut对象使用OrPointcut连接起来
    if (maybeEat("||")) {
        p = new OrPointcut(p, parsePointcut());
    }

    return p;
}

parsePointcut() 方法中,其对切点表达式的解析是一个一个进行的,解析完成一个之后就判断其后是&&还是||,然后使用AndPointcut或者OrPointcut将操作符两边的结果进行组装。这里需要说明的是,AndPointcut和OrPointcut内部实际上也只是保存了两个Pointcut对象,在后面进行切点匹配时,其实际上还是将匹配过程委托给这两个Pointcut对象进行,最后将两个Pointcut匹配的结果进行取交或者取并。这里我们继续阅读 parseAtomicPointcut() 的实现代码:

private Pointcut parseAtomicPointcut() {
    // 判断当前表达式单元是否以!开头,如果是,则使用一个NotPointcut对!后面的匹配结果进行封装
    if (maybeEat("!")) {
        int startPos = tokenSource.peek(-1).getStart();
        Pointcut p = new NotPointcut(parseAtomicPointcut(), startPos);
        return p;
    }
    
    // 如果表达式是以括号开头,则直接对括号后面的表达式进行解析,因为在进行顺序解析时,
    // 括号的作用与解析的顺序是一致的
    if (maybeEat("(")) {
        Pointcut p = parsePointcut();
        eat(")");
        return p;
    }
    
    // 判断表达式是否是以@符号开头,如果是,说明表达式是修饰注解类型的表达式单元,因而使用
    // parseAnnotationPointcut()方法对注解类型的表达式进行解析
    if (maybeEat("@")) {
        int startPos = tokenSource.peek().getStart();
        Pointcut p = parseAnnotationPointcut();
        int endPos = tokenSource.peek(-1).getEnd();
        p.setLocation(sourceContext, startPos, endPos);
        return p;
    }
    
    // 当上述条件都不满足时,说明当前表达式是一个简单类型的表达式,如前面示例中的execution表达式,
    // 此时使用parseSinglePointcut()方法对该类型表达式进行解析
    int startPos = tokenSource.peek().getStart();
    Pointcut p = parseSinglePointcut();
    int endPos = tokenSource.peek(-1).getEnd();
    p.setLocation(sourceContext, startPos, endPos);
    return p;
}

从上面的代码中可以看出,Spring在进行切点单元的解析的时候主要分为两种情况进行解析:

  • 解析修饰注解类型的切点表达式,如@annotation,使用parseAnnotationPointcut()方法进行解析;
  • 解析一般的切点表达式,如execution等,使用parseSinglePointcut()方法进行解析。

2. 注解表达式解析

对于注解类型的表达式的解析,使用的是parseAnnotationPointcut()方法进行,我们首先举例说明注解类型的表达式的用法:

@Around("@annotation(com.business.annotation.FruitAspect)")

这里@Around将会环绕使用FruitAspect注解标注的方法。下面是parseAnnotationPointcut()的源码:

public Pointcut parseAnnotationPointcut() {
    int start = tokenSource.getIndex();
    IToken t = tokenSource.peek();
    // 获取当前修饰注解类型的表达式的标识符,如annotation,args,within等,
    // 上述示例中获取到的就是annotation
    String kind = parseIdentifier();
    IToken possibleTypeVariableToken = tokenSource.peek();
    // 这里是做的校验,在标识符之后不能是使用<>声明的注解类型列表,如果是,
    // 获取的typeVariables就不为空,下面if判断中就会抛出异常
    String[] typeVariables = maybeParseSimpleTypeVariableList();
    if (typeVariables != null) {
        String message = "(";
        assertNoTypeVariables(typeVariables, message, possibleTypeVariableToken);
    }
    
    // 充值当前要解析的表达式所在位置的索引
    tokenSource.setIndex(start);
    if (kind.equals("annotation")) {
        // 解析使用@annotation修饰的注解类型,只要目标方法上使用其后声明的注解就会被环绕
        return parseAtAnnotationPointcut();
    } else if (kind.equals("args")) {
        // 解析使用@args修饰的注解类型,其后可以使用多个注解参数类型,表示如果目标方法的参数
        // 使用其中任意一个注解参数类型进行标注,该方法就会被环绕
        return parseArgsAnnotationPointcut();
    } else if (kind.equals("this") || kind.equals("target")) {
        // this和target其后都是指定一个类或接口类型,分别表示匹配代理对象为指定类型和
        // 匹配目标对象为指定类型
        return parseThisOrTargetAnnotationPointcut();
    } else if (kind.equals("within")) {
        // 解析@within修饰的类型,其后接一个注解类型,表示目标类只要使用该注解进行标注,那么
        // 其所有方法将会被环绕
        return parseWithinAnnotationPointcut();
    } else if (kind.equals("withincode")) {
        // 解析@withincode修饰的类型,其后接一个注解类型,表示目标方法只要使用该注解进行标注就会被环绕
        return parseWithinCodeAnnotationPointcut();
    }
    throw new ParserException("pointcut name", t);
}

可以看到,对于注解类型的参数解析都在 parseAnnotationPointcut() 方法进行声明了,并且使用多个if语句进行了分发。我们这里以 @annotation 修饰的注解类型进行讲解,其余的几个修饰的类型与其解析方式非常类似,读者可自行阅读。如下是 parseAtAnnotationPointcut() 的源码:

private Pointcut parseAtAnnotationPointcut() {
    // 将annotation进行转换,并将解析的index置于其后
    parseIdentifier();
    
    // 判断annotation后是否为左括号
    eat("(");
    // 如果左括号之后是右括号,而没有具体的注解类型,则抛出异常
    if (maybeEat(")")) {
        throw new ParserException("@AnnotationName or parameter", tokenSource.peek());
    }
    // 解析具体的注解或变量类型
    ExactAnnotationTypePattern type = parseAnnotationNameOrVarTypePattern();
    // 解析完注解类型之后其后应该是一个右括号,对其进行解析,并且将解析的index置于其后
    eat(")");
    
    // 将解析的结果封装为一个AnnotationPointcut
    return new AnnotationPointcut(type);
}

这里 parseAtAnnotationPointcut() 主要是解析了annotation之后的左括号,注解类型和右括号,并将解析结果封装到了 AnnotationPointcut 对象中。对于注解类型的具体解析过程在 parseAnnotationNameOrVarTypePattern() 方法中,如下是该方法的源码:

protected ExactAnnotationTypePattern parseAnnotationNameOrVarTypePattern() {
    ExactAnnotationTypePattern p = null;
    int startPos = tokenSource.peek().getStart();
    // 如果注解类型前使用了@符号,则抛出异常
    if (maybeEat("@")) {
        throw new ParserException("@Foo form was deprecated in AspectJ 5 M2: " 
            + "annotation name or var ", tokenSource.peek(-1));
    }
    // 转换简单的注解类型名称,也即上述的com.business.annotation.FruitAspect
    p = parseSimpleAnnotationName();
    int endPos = tokenSource.peek(-1).getEnd();
    // 设置该注解类型的基本属性
    p.setLocation(sourceContext, startPos, endPos);
    // 如果注解类型之后是一个左括号,则对括号中的注解属性类型进行解析,并且将其和前面解析到的
    // 注解类型封装到ExactAnnotationFieldTypePattern中
    if (maybeEat("(")) {
        String formalName = parseIdentifier();
        p = new ExactAnnotationFieldTypePattern(p, formalName);
        eat(")");
    }
    return p;
}

这里 parseAnnotationNameOrVarTypePattern() 方法主要解析了两部分数据:①注解类型的全路径名;②注解属性类型,如此一个注解类型才真正解析完成。

3. 一般表达式类型解析

对于一般表达式类型的解析,主要是通过前面讲解的 parseSinglePointcut() 方法进行的,如下是该方法的实现源码:

public Pointcut parseSinglePointcut() {
    int start = tokenSource.getIndex();
    IToken t = tokenSource.peek();
    Pointcut p = t.maybeGetParsedPointcut();
    if (p != null) {
        tokenSource.next();
        return p;
    }

    // 获取当前表达式的类型
    String kind = parseIdentifier();
    if (kind.equals("execution") || kind.equals("call") 
        || kind.equals("get") || kind.equals("set")) {
        // 对execution,call,get或者set类型的表达式进行解析。这里execution主要修饰的是
        // 一整个方法,包括返回值和异常类型;call修饰的也是一个方法,只不过其目标是调用当前
        // 方法的对象;get和set都是修饰的属性设值的方法。
        p = parseKindedPointcut(kind);
    } else if (kind.equals("args")) {
        // 解析args类型的表达式,其后可以带多个全路径参数类型,目标方法如果参数类型与其匹配将会被环绕
        p = parseArgsPointcut();
    } else if (kind.equals("this")) {
        // 解析this类型的表达式,其后接一个类型,表示生成的代理对象必须是其后指定的类型
        p = parseThisOrTargetPointcut(kind);
    } else if (kind.equals("target")) {
        // 解析target类型的表达式,其后接一个类型,表示被代理的目标对象必须是其后指定的类型
        p = parseThisOrTargetPointcut(kind);
    } else if (kind.equals("within")) {
        // 解析within类型的表达式,其后也是接一个类型,表示目标类型必须是其后表达式的类型
        p = parseWithinPointcut();
    } else if (kind.equals("withincode")) {
        // 解析withincode类型的表达式,其后接一个方法,表示目标方法必须与其后指定的方法匹配
        p = parseWithinCodePointcut();
    } else if (kind.equals("cflow")) {
        p = parseCflowPointcut(false);
    } else if (kind.equals("cflowbelow")) {
        p = parseCflowPointcut(true);
    } else if (kind.equals("adviceexecution")) {
        eat("(");
        eat(")");
        p = new KindedPointcut(Shadow.AdviceExecution, 
                new SignaturePattern(Member.ADVICE, ModifiersPattern.ANY,
                    TypePattern.ANY, TypePattern.ANY, NamePattern.ANY, 
                    TypePatternList.ANY, ThrowsPattern.ANY,
                    AnnotationTypePattern.ANY));
    } else if (kind.equals("handler")) {
        eat("(");
        TypePattern typePat = parseTypePattern(false, false);
        eat(")");
        p = new HandlerPointcut(typePat);
    } else if (kind.equals("lock") || kind.equals("unlock")) {
        p = parseMonitorPointcut(kind);
    } else if (kind.equals("initialization")) {
        eat("(");
        SignaturePattern sig = parseConstructorSignaturePattern();
        eat(")");
        p = new KindedPointcut(Shadow.Initialization, sig);
    } else if (kind.equals("staticinitialization")) {
        eat("(");
        TypePattern typePat = parseTypePattern(false, false);
        eat(")");
        p = new KindedPointcut(Shadow.StaticInitialization, 
                new SignaturePattern(Member.STATIC_INITIALIZATION,
                    ModifiersPattern.ANY, TypePattern.ANY, typePat, NamePattern.ANY, 
                    TypePatternList.EMPTY, ThrowsPattern.ANY,
                    AnnotationTypePattern.ANY));
    } else if (kind.equals("preinitialization")) {
        eat("(");
        SignaturePattern sig = parseConstructorSignaturePattern();
        eat(")");
        p = new KindedPointcut(Shadow.PreInitialization, sig);
    } else if (kind.equals("if")) {
        eat("(");
        if (maybeEatIdentifier("true")) {
            eat(")");
            p = new IfPointcut.IfTruePointcut();
        } else if (maybeEatIdentifier("false")) {
            eat(")");
            p = new IfPointcut.IfFalsePointcut();
        } else {
            if (!maybeEat(")")) {
                throw new ParserException(
                    "in annotation style, if(...) pointcuts cannot contain code. " 
                    + "Use if() and put the code in the annotated method", t);
            }
            p = new IfPointcut("");
        }
    } else {
        // 如果表达式的类型与上述所有类型都不符,那么可能当前类型是用户自定义的类型。对于用户
        // 自定义类型,其只需要实现PointcutDesignatorHandler接口,其getDesignatorName()用于
        // 返回当前表达式类型名,其parse()方法则用于将自定义的表达式转换为一个Pointcut对象
        boolean matchedByExtensionDesignator = false;
        for (PointcutDesignatorHandler pcd : pointcutDesignatorHandlers) {
            if (pcd.getDesignatorName().equals(kind)) {
                p = parseDesignatorPointcut(pcd);
                matchedByExtensionDesignator = true;
            }

        }
        if (!matchedByExtensionDesignator) {
            tokenSource.setIndex(start);
            p = parseReferencePointcut();
        }
    }
    return p;
}

这里只列出了常用的几种表达式类型,并且对其含义进行了讲解,详细的用法请查看本人前面写的文章。这里 parseSinglePointcut() 方法与 parseAnnotationPointcut() 的结构非常类似,即这里只是起到一个分发的作用,具体的实现在具体的方法中进行。另外这里 parseSinglePointcut() 方法也提供了一个实现自定义的切点表达式的方法,具体的使用读者可以阅读 PointcutDesignatorHandler 接口的声明,对于Pointcut理解比较透彻的话实现自定义切点表达式还是比较简单的。对于切点表达式的解析,我们这里还是讲解最常用的一种,即execution类型表达式的解析,如下是 parseKindedPointcut() 的源码:

private KindedPointcut parseKindedPointcut(String kind) {
    // 解析的表达式必须是使用括号包围的
    eat("(");
    SignaturePattern sig;

    Shadow.Kind shadowKind = null;
    if (kind.equals("execution")) {
        // 对execution类型的表达式进行解析,解析时分为Method和Contrustor类型分别处理
        sig = parseMethodOrConstructorSignaturePattern();
        if (sig.getKind() == Member.METHOD) {
            shadowKind = Shadow.MethodExecution;
        } else if (sig.getKind() == Member.CONSTRUCTOR) {
            shadowKind = Shadow.ConstructorExecution;
        }
    } else if (kind.equals("call")) {
        // 对call类型的表达式进行解析,分为Method和Contrustor分别进行处理
        sig = parseMethodOrConstructorSignaturePattern();
        if (sig.getKind() == Member.METHOD) {
            shadowKind = Shadow.MethodCall;
        } else if (sig.getKind() == Member.CONSTRUCTOR) {
            shadowKind = Shadow.ConstructorCall;
        }
    } else if (kind.equals("get")) {
        // 对get表达式进行解析
        sig = parseFieldSignaturePattern();
        shadowKind = Shadow.FieldGet;
    } else if (kind.equals("set")) {
        // 对set表达式进行解析
        sig = parseFieldSignaturePattern();
        shadowKind = Shadow.FieldSet;
    } else {
        throw new ParserException("bad kind: " + kind, tokenSource.peek());
    }
    eat(")");
    // 将解析后的结果封装为一个KindedPointcut
    return new KindedPointcut(shadowKind, sig);
}

这里 parseKindedPointcut() 将Kinded类型的表达式在这里分为execution,call,get和set分别进行解析,只不过最后都是使用KindedPointcut进行封装解析结果。我们这里还是继续阅读execution类型的解析代码:

public SignaturePattern parseMethodOrConstructorSignaturePattern() {
    int startPos = tokenSource.peek().getStart();
    // 判断当前方法是否使用指定注解进行了修饰,这里解析的方式与之前的@annotation不一样,
    // 这里是直接使用形如@chapter7.eg6.FruitAspect进行修饰即可
    AnnotationTypePattern annotationPattern = maybeParseAnnotationPattern();
    // 对访问权限修饰符进行解析,也即public,protected等,也可以使用多个进行组合,
    // 多个进行组合时只需要顺序列出即可;如果要过滤掉某些修饰符,比如过滤掉public修饰的
    // 方法,则在前面加一个!即可,如:!public
    ModifiersPattern modifiers = parseModifiersPattern();
    // 对方法返回值进行解析,这里也可以解析基本数据类型,如void,int等
    TypePattern returnType = parseTypePattern(false, false);

    TypePattern declaringType;
    NamePattern name = null;
    MemberKind kind;

    // 对返回值类型进行判断,如果返回值类型用点“.”分隔的最后一部分是new关键字,那么就会认为其
    // 是进行构造方法增强的切点表达式
    if (maybeEatNew(returnType)) {
        kind = Member.CONSTRUCTOR;
        // 对于构造方法,这里returnType始终是TypePattern.ANY,而构造的对象类型是
        // 通过declaringType来保存的
        if (returnType.toString().length() == 0) {
            declaringType = TypePattern.ANY;
        } else {
            declaringType = returnType;
        }
        returnType = TypePattern.ANY;
        name = NamePattern.ANY;
    } else {
        // 如果解析的不是构造方法,则按照一般的方法解析方式进行解析
        kind = Member.METHOD;
        IToken nameToken = tokenSource.peek();
        // 首先解析方法所在的class类型
        declaringType = parseTypePattern(false, false);
        if (maybeEat(".")) {
            // 如果类后面是一个“.”,则将其后的名称作为方法名进行解析
            nameToken = tokenSource.peek();
            name = parseNamePattern();
        } else {
            // 如果类后面的名称不是“.”,则将类按照“.”分隔,并将最后一个“.”之后的部分当做方法名,
            // 之前的部分则作为类名;如果类中没有“.”,则将整个类名都作为方法名进行解析,并且
            // 类名使用TypePattern.ANY表示,也即任意的类型名都行
            name = tryToExtractName(declaringType);
            if (declaringType.toString().equals("")) {
                declaringType = TypePattern.ANY;
            }
        }
        
        // 如果通过上面的解析后得到的方法名还是null,则抛出异常
        if (name == null) {
            throw new ParserException("name pattern", tokenSource.peek());
        }
        
        // 获取方法名,判断方法名是否为new关键字,如果是,则抛出异常
        String simpleName = name.maybeGetSimpleName();
        if (simpleName != null && simpleName.equals("new")) {
            throw new ParserException("method name (not constructor)", nameToken);
        }
    }

    // 对方法后面括号中声明的参数进行解析
    TypePatternList parameterTypes = parseArgumentsPattern(true);

    // 对参数后面括号外可能存在的异常抛出列表进行解析
    ThrowsPattern throwsPattern = parseOptionalThrowsPattern();
    // 将上述解析的结果使用SignaturePattern进行封装,并将其返回
    SignaturePattern ret = new SignaturePattern(kind, modifiers, returnType, 
        declaringType, name, parameterTypes, throwsPattern, annotationPattern);
    int endPos = tokenSource.peek(-1).getEnd();
    ret.setLocation(sourceContext, startPos, endPos);
    return ret;
}

这里 parseMethodOrConstructorSignaturePattern() 方法就是对基本切点表达式单元进行解析的整体流程,其主要分为如下几个步骤:

  • 首先会解析当前方法是否需要使用某个注解进行修饰,比如如下的示例就要求目标方法必须使用FruitAspect注解进行修饰:
@Around("execution(@chapter7.eg6.FruitAspect public void chapter7.eg9.Dog.*(..))")
  • 然后解析当前方法所使用的修饰符,即public,protected等,可以取非,需要注意的是,肯定的不能同时有多个,比如不能同时存在public protected,此时虽然表达式不会报错,但是基本上匹配不到任何方法,因为没有方法同时是public又是protected的。如下就是要求目标方法必须是public的,并且不能是protected修饰的:
@Around("execution(public !protected void chapter7.eg9.Dog.*(..))")
  • 接着就是对返回值类型的解析,对返回值类型的解析又分为两种情况,一种是目标方法是构造方法,另一种则是目标方法是一般的方法。对于构造方法,只需要返回值的最后一部分是new即可,然后其后紧接着参数列表;而对于一般的方法,返回值之后则是当前方法所在的类,以及修饰的目标方法。如下是修饰构造方法的一个示例:
@Around("execution(public chapter7.eg9.Dog.new(..))")
  • 如果需要修饰的方法不是构造方法,那么其修饰的就是一般方法,对于一般方法,返回值之后就是要修饰的方法所在的类和要修饰的方法,这里的类必须包含全路径名。比如如下修饰的一般方法:
@Around("execution(public void chapter7.eg9.Dog.*(..))")
  • 在类和方法都解析完成之后,就是解析参数列表了,对于参数列表的解析,其会使用“,”对列表进行分割,并且会判断参数列表中是否包含有“..”和"*"之类的通配符;
  • 最后就是解析表达式中是否包含可选的异常抛出表达式,异常抛出表达式都是使用throws进行修饰的,而throws之后则是接的一系列的类型列表,这里很明显也是可以使用递归进行类型列表的解析的。如下使用throws表达式的一个示例:
@Around("execution(public void chapter7.eg9.Dog.*(..) throws java.lang.Exception)")

4. 小结

本文主要讲解了Spring Aop是如何使用递归对切点表达式进行解析的,在解析过程中Spring Aop将其分为了两种过程,一种是对注解类型的切点表达式的解析,一种是对一般类型的切点表达式的解析。而在解析过程中也支持逻辑表达式&&,||和!的使用,对于这些表达式,则是通过AndPointcut,OrPointcut和NotPointcut等进行封装的。解析的最终结果就是整个切点表达式都会封装到一个Pointcut对象中,而标签中的每一部分都是该Pointcut对象的一个子节点,整体类似于一种树状结构。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Anatomy

Web Anatomy

Robert Hoekman Jr.、Jared Spool / New Riders / 2009-12-11 / USD 39.99

At the start of every web design project, the ongoing struggles reappear. We want to design highly usable and self-evident applications, but we also want to devise innovative, compelling, and exciting......一起来看看 《Web Anatomy》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具