在前面的文章(Spring AOP切點(diǎn)表達(dá)式詳解)中,我們總結(jié)了Spring Aop切點(diǎn)表達(dá)式的用法,而在上文(Spring Aop原理之Advisor過濾)中我們講到,切點(diǎn)表達(dá)式的解析主要是在PatternParser.parsePointcut()方法中進(jìn)行的。本文的主要目的是講解Spring Aop是如何遞歸的對切點(diǎn)表達(dá)式進(jìn)行解析,并且終封裝為一個Pointcut對象的。
這里我們首先舉一個典型的切點(diǎn)表達(dá)式的例子:
@Pointcut(value = "!execution(public protected void com.business.Dog.*(..)) && args(name)", argNames = "name")
該切點(diǎn)將會匹配如下條件的反面的的所有方法:使用public修飾,返回值為void類型,類為com.business.Dog的方法,并且這些方法必須滿足有一個名稱為name的參數(shù)。
1. 解析入口
如下是PatternParser.parsePointcut()方法的實(shí)現(xiàn):
在parsePointcut()方法中,其對切點(diǎn)表達(dá)式的解析是一個一個進(jìn)行的,解析完成一個之后就判斷其后是&&還是||,然后使用AndPointcut或者OrPointcut將操作符兩邊的結(jié)果進(jìn)行組裝。這里需要說明的是,AndPointcut和OrPointcut內(nèi)部實(shí)際上也只是保存了兩個Pointcut對象,在后面進(jìn)行切點(diǎn)匹配時,其實(shí)際上還是將匹配過程委托給這兩個Pointcut對象進(jìn)行,將兩個Pointcut匹配的結(jié)果進(jìn)行取交或者取并。這里我們繼續(xù)閱讀parseAtomicPointcut()的實(shí)現(xiàn)代碼:
private Pointcut parseAtomicPointcut() { // 判斷當(dāng)前表達(dá)式單元是否以!開頭,如果是,則使用一個NotPointcut對!后面的匹配結(jié)果進(jìn)行封裝 if (maybeEat("!")) { int startPos = tokenSource.peek(-1).getStart(); Pointcut p = new NotPointcut(parseAtomicPointcut(), startPos); return p; } // 如果表達(dá)式是以括號開頭,則直接對括號后面的表達(dá)式進(jìn)行解析,因?yàn)樵谶M(jìn)行順序解析時, // 括號的作用與解析的順序是一致的 if (maybeEat("(")) { Pointcut p = parsePointcut(); eat(")"); return p; } // 判斷表達(dá)式是否是以@符號開頭,如果是,說明表達(dá)式是修飾注解類型的表達(dá)式單元,因而使用 // parseAnnotationPointcut()方法對注解類型的表達(dá)式進(jìn)行解析 if (maybeEat("@")) { int startPos = tokenSource.peek().getStart(); Pointcut p = parseAnnotationPointcut(); int endPos = tokenSource.peek(-1).getEnd(); p.setLocation(sourceContext, startPos, endPos); return p; } // 當(dāng)上述條件都不滿足時,說明當(dāng)前表達(dá)式是一個簡單類型的表達(dá)式,如前面示例中的execution表達(dá)式, // 此時使用parseSinglePointcut()方法對該類型表達(dá)式進(jìn)行解析 int startPos = tokenSource.peek().getStart(); Pointcut p = parseSinglePointcut(); int endPos = tokenSource.peek(-1).getEnd(); p.setLocation(sourceContext, startPos, endPos); return p; }
從上面的代碼中可以看出,Spring在進(jìn)行切點(diǎn)單元的解析的時候主要分為兩種情況進(jìn)行解析:
解析修飾注解類型的切點(diǎn)表達(dá)式,如@annotation,使用parseAnnotationPointcut()方法進(jìn)行解析;
解析一般的切點(diǎn)表達(dá)式,如execution等,使用parseSinglePointcut()方法進(jìn)行解析。
2. 注解表達(dá)式解析
對于注解類型的表達(dá)式的解析,使用的是parseAnnotationPointcut()方法進(jìn)行,我們首先舉例說明注解類型的表達(dá)式的用法:
@Around("@annotation(com.business.annotation.FruitAspect)")
這里@Around將會環(huán)繞使用FruitAspect注解標(biāo)注的方法。下面是parseAnnotationPointcut()的源碼:
public Pointcut parseAnnotationPointcut() { int start = tokenSource.getIndex(); IToken t = tokenSource.peek(); // 獲取當(dāng)前修飾注解類型的表達(dá)式的標(biāo)識符,如annotation,args,within等, // 上述示例中獲取到的就是annotation String kind = parseIdentifier(); IToken possibleTypeVariableToken = tokenSource.peek(); // 這里是做的校驗(yàn),在標(biāo)識符之后不能是使用<>聲明的注解類型列表,如果是, // 獲取的typeVariables就不為空,下面if判斷中就會拋出異常 String[] typeVariables = maybeParseSimpleTypeVariableList(); if (typeVariables != null) { String message = "("; assertNoTypeVariables(typeVariables, message, possibleTypeVariableToken); } // 充值當(dāng)前要解析的表達(dá)式所在位置的索引 tokenSource.setIndex(start); if (kind.equals("annotation")) { // 解析使用@annotation修飾的注解類型,只要目標(biāo)方法上使用其后聲明的注解就會被環(huán)繞 return parseAtAnnotationPointcut(); } else if (kind.equals("args")) { // 解析使用@args修飾的注解類型,其后可以使用多個注解參數(shù)類型,表示如果目標(biāo)方法的參數(shù) // 使用其中任意一個注解參數(shù)類型進(jìn)行標(biāo)注,該方法就會被環(huán)繞 return parseArgsAnnotationPointcut(); } else if (kind.equals("this") || kind.equals("target")) { // this和target其后都是指定一個類或接口類型,分別表示匹配代理對象為指定類型和 // 匹配目標(biāo)對象為指定類型 return parseThisOrTargetAnnotationPointcut(); } else if (kind.equals("within")) { // 解析@within修飾的類型,其后接一個注解類型,表示目標(biāo)類只要使用該注解進(jìn)行標(biāo)注,那么 // 其所有方法將會被環(huán)繞 return parseWithinAnnotationPointcut(); } else if (kind.equals("withincode")) { // 解析@withincode修飾的類型,其后接一個注解類型,表示目標(biāo)方法只要使用該注解進(jìn)行標(biāo)注就會被環(huán)繞 return parseWithinCodeAnnotationPointcut(); } throw new ParserException("pointcut name", t); }
可以看到,對于注解類型的參數(shù)解析都在parseAnnotationPointcut()方法進(jìn)行聲明了,并且使用多個if語句進(jìn)行了分發(fā)。我們這里以@annotation修飾的注解類型進(jìn)行講解,其余的幾個修飾的類型與其解析方式非常類似,讀者可自行閱讀。如下是parseAtAnnotationPointcut()的源碼:
private Pointcut parseAtAnnotationPointcut() { // 將annotation進(jìn)行轉(zhuǎn)換,并將解析的index置于其后 parseIdentifier(); // 判斷annotation后是否為左括號 eat("("); // 如果左括號之后是右括號,而沒有具體的注解類型,則拋出異常 if (maybeEat(")")) { throw new ParserException("@AnnotationName or parameter", tokenSource.peek()); } // 解析具體的注解或變量類型 ExactAnnotationTypePattern type = parseAnnotationNameOrVarTypePattern(); // 解析完注解類型之后其后應(yīng)該是一個右括號,對其進(jìn)行解析,并且將解析的index置于其后 eat(")"); // 將解析的結(jié)果封裝為一個AnnotationPointcut return new AnnotationPointcut(type); }
這里parseAtAnnotationPointcut()主要是解析了annotation之后的左括號,注解類型和右括號,并將解析結(jié)果封裝到了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)); } // 轉(zhuǎn)換簡單的注解類型名稱,也即上述的com.business.annotation.FruitAspect p = parseSimpleAnnotationName(); int endPos = tokenSource.peek(-1).getEnd(); // 設(shè)置該注解類型的基本屬性 p.setLocation(sourceContext, startPos, endPos); // 如果注解類型之后是一個左括號,則對括號中的注解屬性類型進(jìn)行解析,并且將其和前面解析到的 // 注解類型封裝到ExactAnnotationFieldTypePattern中 if (maybeEat("(")) { String formalName = parseIdentifier(); p = new ExactAnnotationFieldTypePattern(p, formalName); eat(")"); } return p; }
這里parseAnnotationNameOrVarTypePattern()方法主要解析了兩部分?jǐn)?shù)據(jù):①注解類型的全路徑名;②注解屬性類型,如此一個注解類型才真正解析完成。
3. 一般表達(dá)式類型解析
對于一般表達(dá)式類型的解析,主要是通過前面講解的parseSinglePointcut()方法進(jìn)行的,如下是該方法的實(shí)現(xiàn)源碼:
public Pointcut parseSinglePointcut() { int start = tokenSource.getIndex(); IToken t = tokenSource.peek(); Pointcut p = t.maybeGetParsedPointcut(); if (p != null) { tokenSource.next(); return p; } // 獲取當(dāng)前表達(dá)式的類型 String kind = parseIdentifier(); if (kind.equals("execution") || kind.equals("call") || kind.equals("get") || kind.equals("set")) { // 對execution,call,get或者set類型的表達(dá)式進(jìn)行解析。這里execution主要修飾的是 // 一整個方法,包括返回值和異常類型;call修飾的也是一個方法,只不過其目標(biāo)是調(diào)用當(dāng)前 // 方法的對象;get和set都是修飾的屬性設(shè)值的方法。 p = parseKindedPointcut(kind); } else if (kind.equals("args")) { // 解析args類型的表達(dá)式,其后可以帶多個全路徑參數(shù)類型,目標(biāo)方法如果參數(shù)類型與其匹配將會被環(huán)繞 p = parseArgsPointcut(); } else if (kind.equals("this")) { // 解析this類型的表達(dá)式,其后接一個類型,表示生成的代理對象必須是其后指定的類型 p = parseThisOrTargetPointcut(kind); } else if (kind.equals("target")) { // 解析target類型的表達(dá)式,其后接一個類型,表示被代理的目標(biāo)對象必須是其后指定的類型 p = parseThisOrTargetPointcut(kind); } else if (kind.equals("within")) { // 解析within類型的表達(dá)式,其后也是接一個類型,表示目標(biāo)類型必須是其后表達(dá)式的類型 p = parseWithinPointcut(); } else if (kind.equals("withincode")) { // 解析withincode類型的表達(dá)式,其后接一個方法,表示目標(biāo)方法必須與其后指定的方法匹配 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 { // 如果表達(dá)式的類型與上述所有類型都不符,那么可能當(dāng)前類型是用戶自定義的類型。對于用戶 // 自定義類型,其只需要實(shí)現(xiàn)PointcutDesignatorHandler接口,其getDesignatorName()用于 // 返回當(dāng)前表達(dá)式類型名,其parse()方法則用于將自定義的表達(dá)式轉(zhuǎn)換為一個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; }
這里只列出了常用的幾種表達(dá)式類型,并且對其含義進(jìn)行了講解,詳細(xì)的用法請查看本人前面寫的文章。這里parseSinglePointcut()方法與parseAnnotationPointcut()的結(jié)構(gòu)非常類似,即這里只是起到一個分發(fā)的作用,具體的實(shí)現(xiàn)在具體的方法中進(jìn)行。另外這里parseSinglePointcut()方法也提供了一個實(shí)現(xiàn)自定義的切點(diǎn)表達(dá)式的方法,具體的使用讀者可以閱讀PointcutDesignatorHandler接口的聲明,對于Pointcut理解比較透徹的話實(shí)現(xiàn)自定義切點(diǎn)表達(dá)式還是比較簡單的。對于切點(diǎn)表達(dá)式的解析,我們這里還是講解常用的一種,即execution類型表達(dá)式的解析,如下是parseKindedPointcut()的源碼:
private KindedPointcut parseKindedPointcut(String kind) { // 解析的表達(dá)式必須是使用括號包圍的 eat("("); SignaturePattern sig; Shadow.Kind shadowKind = null; if (kind.equals("execution")) { // 對execution類型的表達(dá)式進(jìn)行解析,解析時分為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類型的表達(dá)式進(jìn)行解析,分為Method和Contrustor分別進(jìn)行處理 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表達(dá)式進(jìn)行解析 sig = parseFieldSignaturePattern(); shadowKind = Shadow.FieldGet; } else if (kind.equals("set")) { // 對set表達(dá)式進(jìn)行解析 sig = parseFieldSignaturePattern(); shadowKind = Shadow.FieldSet; } else { throw new ParserException("bad kind: " + kind, tokenSource.peek()); } eat(")"); // 將解析后的結(jié)果封裝為一個KindedPointcut return new KindedPointcut(shadowKind, sig); }
這里parseKindedPointcut()將Kinded類型的表達(dá)式在這里分為execution,call,get和set分別進(jìn)行解析,只不過都是使用KindedPointcut進(jìn)行封裝解析結(jié)果。我們這里還是繼續(xù)閱讀execution類型的解析代碼:
public SignaturePattern parseMethodOrConstructorSignaturePattern() { int startPos = tokenSource.peek().getStart(); // 判斷當(dāng)前方法是否使用指定注解進(jìn)行了修飾,這里解析的方式與之前的@annotation不一樣, // 這里是直接使用形如@chapter7.eg6.FruitAspect進(jìn)行修飾即可 AnnotationTypePattern annotationPattern = maybeParseAnnotationPattern(); // 對訪問權(quán)限修飾符進(jìn)行解析,也即public,protected等,也可以使用多個進(jìn)行組合, // 多個進(jìn)行組合時只需要順序列出即可;如果要過濾掉某些修飾符,比如過濾掉public修飾的 // 方法,則在前面加一個!即可,如:!public ModifiersPattern modifiers = parseModifiersPattern(); // 對方法返回值進(jìn)行解析,這里也可以解析基本數(shù)據(jù)類型,如void,int等 TypePattern returnType = parseTypePattern(false, false); TypePattern declaringType; NamePattern name = null; MemberKind kind; // 對返回值類型進(jìn)行判斷,如果返回值類型用點(diǎn)“.”分隔的一部分是new關(guān)鍵字,那么就會認(rèn)為其 // 是進(jìn)行構(gòu)造方法增強(qiáng)的切點(diǎn)表達(dá)式 if (maybeEatNew(returnType)) { kind = Member.CONSTRUCTOR; // 對于構(gòu)造方法,這里returnType始終是TypePattern.ANY,而構(gòu)造的對象類型是 // 通過declaringType來保存的 if (returnType.toString().length() == 0) { declaringType = TypePattern.ANY; } else { declaringType = returnType; } returnType = TypePattern.ANY; name = NamePattern.ANY; } else { // 如果解析的不是構(gòu)造方法,則按照一般的方法解析方式進(jìn)行解析 kind = Member.METHOD; IToken nameToken = tokenSource.peek(); // 首先解析方法所在的class類型 declaringType = parseTypePattern(false, false); if (maybeEat(".")) { // 如果類后面是一個“.”,則將其后的名稱作為方法名進(jìn)行解析 nameToken = tokenSource.peek(); name = parseNamePattern(); } else { // 如果類后面的名稱不是“.”,則將類按照“.”分隔,并將一個“.”之后的部分當(dāng)做方法名, // 之前的部分則作為類名;如果類中沒有“.”,則將整個類名都作為方法名進(jìn)行解析,并且 // 類名使用TypePattern.ANY表示,也即任意的類型名都行 name = tryToExtractName(declaringType); if (declaringType.toString().equals("")) { declaringType = TypePattern.ANY; } } // 如果通過上面的解析后得到的方法名還是null,則拋出異常 if (name == null) { throw new ParserException("name pattern", tokenSource.peek()); } // 獲取方法名,判斷方法名是否為new關(guān)鍵字,如果是,則拋出異常 String simpleName = name.maybeGetSimpleName(); if (simpleName != null && simpleName.equals("new")) { throw new ParserException("method name (not constructor)", nameToken); } } // 對方法后面括號中聲明的參數(shù)進(jìn)行解析 TypePatternList parameterTypes = parseArgumentsPattern(true); // 對參數(shù)后面括號外可能存在的異常拋出列表進(jìn)行解析 ThrowsPattern throwsPattern = parseOptionalThrowsPattern(); // 將上述解析的結(jié)果使用SignaturePattern進(jìn)行封裝,并將其返回 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()方法就是對基本切點(diǎn)表達(dá)式單元進(jìn)行解析的整體流程,其主要分為如下幾個步驟:
首先會解析當(dāng)前方法是否需要使用某個注解進(jìn)行修飾,比如如下的示例就要求目標(biāo)方法必須使用FruitAspect注解進(jìn)行修飾:
@Around("execution(@chapter7.eg6.FruitAspect public void chapter7.eg9.Dog.*(..))")
然后解析當(dāng)前方法所使用的修飾符,即public,protected等,可以取非,需要注意的是,肯定的不能同時有多個,比如不能同時存在public protected,此時雖然表達(dá)式不會報錯,但是基本上匹配不到任何方法,因?yàn)闆]有方法同時是public又是protected的。如下就是要求目標(biāo)方法必須是public的,并且不能是protected修飾的:
@Around("execution(public !protected void chapter7.eg9.Dog.*(..))")
接著就是對返回值類型的解析,對返回值類型的解析又分為兩種情況,一種是目標(biāo)方法是構(gòu)造方法,另一種則是目標(biāo)方法是一般的方法。對于構(gòu)造方法,只需要返回值的一部分是new即可,然后其后緊接著參數(shù)列表;而對于一般的方法,返回值之后則是當(dāng)前方法所在的類,以及修飾的目標(biāo)方法。如下是修飾構(gòu)造方法的一個示例:
@Around("execution(public chapter7.eg9.Dog.new(..))")
如果需要修飾的方法不是構(gòu)造方法,那么其修飾的就是一般方法,對于一般方法,返回值之后就是要修飾的方法所在的類和要修飾的方法,這里的類必須包含全路徑名。比如如下修飾的一般方法:
@Around("execution(public void chapter7.eg9.Dog.*(..))")
在類和方法都解析完成之后,就是解析參數(shù)列表了,對于參數(shù)列表的解析,其會使用“,”對列表進(jìn)行分割,并且會判斷參數(shù)列表中是否包含有“..”和"*"之類的通配符;
就是解析表達(dá)式中是否包含可選的異常拋出表達(dá)式,異常拋出表達(dá)式都是使用throws進(jìn)行修飾的,而throws之后則是接的一系列的類型列表,這里很明顯也是可以使用遞歸進(jìn)行類型列表的解析的。如下使用throws表達(dá)式的一個示例:
@Around("execution(public void chapter7.eg9.Dog.*(..) throws java.lang.Exception)")
4. 小結(jié)
本文主要講解了Spring Aop是如何使用遞歸對切點(diǎn)表達(dá)式進(jìn)行解析的,在解析過程中Spring Aop將其分為了兩種過程,一種是對注解類型的切點(diǎn)表達(dá)式的解析,一種是對一般類型的切點(diǎn)表達(dá)式的解析。而在解析過程中也支持邏輯表達(dá)式&&,||和!的使用,對于這些表達(dá)式,則是通過AndPointcut,OrPointcut和NotPointcut等進(jìn)行封裝的。解析的終結(jié)果就是整個切點(diǎn)表達(dá)式都會封裝到一個Pointcut對象中,而標(biāo)簽中的每一部分都是該P(yáng)ointcut對象的一個子節(jié)點(diǎn),整體類似于一種樹狀結(jié)構(gòu)。