1. gzyueqian
      13352868059

      哪個java培訓(xùn)中心好?你可以來試聽一下粵嵌的java課程

      更新時間: 2018-09-20 16:00:51來源: java學(xué)習(xí)瀏覽量:4965

          在前面的文章(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)。

      免費(fèi)預(yù)約試聽課

      亚洲另类欧美综合久久图片区_亚洲中文字幕日产无码2020_欧美日本一区二区三区桃色视频_亚洲AⅤ天堂一区二区三区

      
      

      1. 亚洲欧美激情国产综合久久久 | 亚洲午夜免费在线 | 亚洲字幕一区二区 | 亚洲AV乱码久久精品蜜桃 | 亚洲欧美丝袜精品久久 | 欧美在线视频精品一区 |