Spring Aop原理之Advisor過濾

       在上文(Spring Aop之Advisor解析)中咱們講到,Spring Aop對目標bean的代理主要分爲三個步驟:獲取全部的Advisor,過濾當前bean可應用的Advisor和使用Advisor爲當前bean生成代理對象,而且上文咱們也講解了Spring是如何獲取全部的Advisor的。本文主要講解這其中的第二個步驟,即Spring是如何從這些Advisor中過濾獲得能夠應用到當前bean的Advisor。java

1. 切點表達式解析

       在上文中咱們講到了AbstractAdvisorAutoProxyCreator.findEligibleAdvisors()方法,該方法中首先會獲取到全部的Advisor,而後會過濾獲得可應用的Advisor。以下是該方法的實現:express

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    // 獲取全部的Advisor
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    // 對獲取到的Advisor進行過濾,判斷哪些Advisor能夠應用到當前bean
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, 
       beanClass, beanName);
    // 對可應用的Advisor進行擴展
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}

       這裏咱們直接進入第二個方法的調用,該方法中始終會將Introduction類型的Advisor和其他的Advisor分開進行處理,因爲Introduction類型的Advisor使用相對較少,本文主要以普通的Advisor,即便用@Before,@After等進行修飾的Advisor進行講解。以下是findAdvisorsThatCanApply()的實現:緩存

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, 
         Class<?> clazz) {
    if (candidateAdvisors.isEmpty()) {
        return candidateAdvisors;
    }
    
    // 判斷當前Advisor是否爲IntroductionAdvisor,若是是,則按照IntroductionAdvisor的方式進行
    // 過濾,這裏主要的過濾邏輯在canApply()方法中
    List<Advisor> eligibleAdvisors = new LinkedList<>();
    for (Advisor candidate : candidateAdvisors) {
        // 判斷是否爲IntroductionAdvisor,而且判斷是否能夠應用到當前類上
        if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
            eligibleAdvisors.add(candidate);
        }
    }
    
    // 若是當前Advisor不是IntroductionAdvisor類型,則經過canApply()方法判斷當前Advisor是否
    // 能夠應用到當前bean
    boolean hasIntroductions = !eligibleAdvisors.isEmpty();
    for (Advisor candidate : candidateAdvisors) {
        // 對IntroductionAdvisor類型進行過濾
        if (candidate instanceof IntroductionAdvisor) {
            continue;
        }
        
        // 判斷是否能夠應用到當前bean類型
        if (canApply(candidate, clazz, hasIntroductions)) {
            eligibleAdvisors.add(candidate);
        }
    }
    return eligibleAdvisors;
}

       findAdvisorsThatCanApply()方法主要將IntroductionAdvisor和普通的Advisor分開進行處理,而且最終都是經過canApply()方法進行過濾。以下是canApply()方法的源碼,這裏咱們直接看其最終調用的方法:ui

public static boolean canApply(Pointcut pc, Class<?> targetClass, 
        boolean hasIntroductions) {
    Assert.notNull(pc, "Pointcut must not be null");
    // 獲取當前Advisor的CalssFilter,而且調用其matches()方法判斷當前切點表達式是否與目標bean匹配,
    // 這裏ClassFilter指代的切點表達式主要是當前切面類上使用的@Aspect註解中所指代的切點表達式
    if (!pc.getClassFilter().matches(targetClass)) {
        return false;
    }

    // 判斷若是當前Advisor所指代的方法的切點表達式若是是對任意方法都放行,則直接返回
    MethodMatcher methodMatcher = pc.getMethodMatcher();
    if (methodMatcher == MethodMatcher.TRUE) {
        return true;
    }

    // 這裏將MethodMatcher強轉爲IntroductionAwareMethodMatcher類型的緣由在於,
    // 若是目標類不包含Introduction類型的Advisor,那麼使用
    // IntroductionAwareMethodMatcher.matches()方法進行匹配判斷時能夠提高匹配的效率,
    // 其會判斷目標bean中沒有使用Introduction織入新的方法,則可使用該方法進行靜態匹配,從而提高效率
    // 由於Introduction類型的Advisor能夠往目標類中織入新的方法,新的方法也多是被AOP環繞的方法
    IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
    if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
        introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
    }

    // 獲取目標類的全部接口
    Set<Class<?>> classes = 
        new LinkedHashSet<>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
    classes.add(targetClass);
    for (Class<?> clazz : classes) {
        // 獲取目標接口的全部方法
        Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
        for (Method method : methods) {
            // 若是當前MethodMatcher也是IntroductionAwareMethodMatcher類型,則使用該類型
            // 的方法進行匹配,從而達到提高效率的目的;不然使用MethodMatcher.matches()方法進行匹配
            if ((introductionAwareMethodMatcher != null &&
                 introductionAwareMethodMatcher.matches(method, 
                     targetClass, hasIntroductions)) 
                || methodMatcher.matches(method, targetClass)) {
                return true;
            }
        }
    }

    return false;
}

       在canApply()方法中,邏輯主要分爲兩個部分:經過ClassFilter對類進行過濾和經過MethodMatcher對方法進行過濾。這裏的ClassFilter其實主要指的是@Aspect註解中使用的切點表達式,而MethodMatcher主要指的是@Before,@After等註解中使用的切點表達式。Spring Aop對切點表達式進行解析的過程都是經過遞歸來實現的,兩種解析方式是相似的,這裏咱們主要講解Spring Aop是如何對方法上的切點表達式進行解析的,而且是如何匹配目標方法的。以下是MethodMatcher.matches()方法的實現:this

public boolean matches(Method method, @Nullable Class<?> targetClass, 
        boolean beanHasIntroductions) {
    // 獲取切點表達式,並對其進行解析,解析以後將解析的結果進行緩存
    obtainPointcutExpression();
    // 獲取目標方法最接近的方法,好比若是method是接口方法,那麼就找到該接口方法的實現類的方法
    Method targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
    // 將對切點表達式解析後的結果與要匹配的目標方法封裝爲一個ShadowMatch對象,而且對目標方法進行
    // 匹配,匹配的結果將存儲在ShadowMatch.match參數中,該參數是FuzzyBoolean類型的,
    // 其保存了當前方法與切點表達式的匹配結果
    ShadowMatch shadowMatch = getShadowMatch(targetMethod, method);

    if (shadowMatch.alwaysMatches()) {
        return true;  // 若是匹配上了則返回true
    } else if (shadowMatch.neverMatches()) {
        return false;  // 若是沒匹配上則返回false
    } else {
        // 在不確認可否匹配的時候,經過判斷是否有Introduction類型的Advisor,來進行進一步的匹配
        if (beanHasIntroductions) {
            return true;
        }

        // 若是不確認可否匹配,則將匹配結果封裝爲一個RuntimeTestWalker,
        // 以便在方法運行時進行動態匹配
        RuntimeTestWalker walker = getRuntimeTestWalker(shadowMatch);
        return (!walker.testsSubtypeSensitiveVars() ||
                (targetClass != null && walker.testTargetInstanceOfResidue(targetClass)));
    }
}

       在matches()方法中,其主要作了兩件事:對切點表達式進行解析,和經過解析的切點表達式與目標方法進行匹配。咱們首先看看Spring Aop是如何解析切點表達式的,以下是obtainPointcutExpression()的實現源碼:.net

private PointcutExpression obtainPointcutExpression() {
    // 若是切點表達式爲空,則拋出異常
    if (getExpression() == null) {
        throw new IllegalStateException(
            "Must set property 'expression' before attempting to match");
    }
    if (this.pointcutExpression == null) {
        // 獲取切點表達式類加載器,默認和Spring使用的類加載器是同一加載器
        this.pointcutClassLoader = determinePointcutClassLoader();
        // 對切點表達式進行解析
        this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
    }
    return this.pointcutExpression;
}

       這裏咱們繼續深刻看看buildPointcutExpression()的實現原理:debug

private PointcutExpression buildPointcutExpression(@Nullable ClassLoader classLoader) {
    // 使用類加載器實例化一個PointcutParser對象,用於對切點表達式進行解析
    PointcutParser parser = initializePointcutParser(classLoader);
    // 將切點表達式中使用args屬性指定的參數封裝爲PointcutParameter類型的對象
    PointcutParameter[] pointcutParameters = 
        new PointcutParameter[this.pointcutParameterNames.length];
    for (int i = 0; i < pointcutParameters.length; i++) {
        pointcutParameters[i] = parser.createPointcutParameter(
            this.pointcutParameterNames[i], this.pointcutParameterTypes[i]);
    }
    
    // 使用PointcutParser對切點表達式進行轉化,這裏replaceBooleanOperators()只是作了一個簡單的
    // 字符串轉換,將and、or和not轉換爲&&、||和!
    return parser.parsePointcutExpression(replaceBooleanOperators(resolveExpression()),
            this.pointcutDeclarationScope, pointcutParameters);
}

       buildPointcutExpression()方法首先實例化了一個PointcutParser,而後將@Before,@After註解中args屬性指定的參數進行了封裝,最後經過PointcutParser對切點表達式進行解析。以下是PointcutParser.parsePointcutExpression()的源碼:代理

public PointcutExpression parsePointcutExpression(String expression, Class<?> inScope, 
        PointcutParameter[] formalParameters)
			throws UnsupportedPointcutPrimitiveException, IllegalArgumentException {
    PointcutExpressionImpl pcExpr = null;
    try {
        // 對切點表達式進行解析
        Pointcut pc = resolvePointcutExpression(expression, inScope, formalParameters);
        pc = concretizePointcutExpression(pc, inScope, formalParameters);
        // 對切點表達式執行的類型進行校驗
        validateAgainstSupportedPrimitives(pc, expression);
        // 將解析獲得的Pointcut封裝到PointcutExpression中
        pcExpr = new PointcutExpressionImpl(pc, expression, formalParameters, getWorld());
    } catch (ParserException pEx) {
        throw new IllegalArgumentException(
            buildUserMessageFromParserException(expression, pEx));
    } catch (ReflectionWorld.ReflectionWorldException rwEx) {
        throw new IllegalArgumentException(rwEx.getMessage());
    }
    return pcExpr;
}

       這裏實際解析的邏輯在resolvePointcutExpression()方法中,咱們繼續看該方法的實現:code

protected Pointcut resolvePointcutExpression(String expression, Class<?> inScope, 
        PointcutParameter[] formalParameters) {
    try {
        // 將切點表達式封裝到PatternParser中
        PatternParser parser = new PatternParser(expression);
        // 設置自定義的切點表達式處理器
        parser.setPointcutDesignatorHandlers(pointcutDesignators, world);
        // 解析切點表達式
        Pointcut pc = parser.parsePointcut();
        // 校驗切點表達式是否爲支持的類型
        validateAgainstSupportedPrimitives(pc, expression);
        // 將args屬性所指定的參數封裝到IScope中
        IScope resolutionScope = buildResolutionScope((inScope == null 
           ? Object.class : inScope), formalParameters);
        // 經過args屬性指定的參數與當前切面方法的參數進行對比,而且將方法的參數類型封裝到Pointcut中
        pc = pc.resolve(resolutionScope);
        return pc;
    } catch (ParserException pEx) {
        throw new IllegalArgumentException(buildUserMessageFromParserException(expression, pEx));
    }
}

       能夠看到,這裏對切點表達式的解析主要分爲兩個部分,第一部分是對切點表達式的解析,第二部分是對指定的參數進行解析。因爲切點表達式和參數的綁定解析比較複雜,咱們將在下一篇文章中進行講解。orm

2. 切點匹配

       關於切點的匹配,這裏主要是在getShadowMatch()方法中實現的。以下是getShadowMatch()方法的源碼:

private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) {
    // 從緩存中獲取ShadowMatch數據,若是緩存中存在則直接返回
    ShadowMatch shadowMatch = this.shadowMatchCache.get(targetMethod);
    if (shadowMatch == null) {
        synchronized (this.shadowMatchCache) {
            PointcutExpression fallbackExpression = null;
            Method methodToMatch = targetMethod;
            shadowMatch = this.shadowMatchCache.get(targetMethod);
            if (shadowMatch == null) {
                try {
                    try {
                        // 獲取解析後的切點表達式,因爲obtainPointcutExpression()方法在以前
                        // 已經調用過一次,於是這裏調用時能夠直接從緩存中獲取以前解析的結果。
                        // 這裏將解析後的切點表達式與當前方法進行匹配,並將匹配結果封裝
                        // 爲一個ShadowMatch對象
                        shadowMatch = obtainPointcutExpression()
                            .matchesMethodExecution(methodToMatch);
                    } catch (ReflectionWorldException ex) {
                        try {
                            // 若是匹配失敗,則在目標方法上找切點表達式,組裝成爲一個回調切點表達式,
                            // 而且對回調切點表達式進行解析
                            fallbackExpression = getFallbackPointcutExpression(
                                methodToMatch.getDeclaringClass());
                            if (fallbackExpression != null) {
                                // 使用回調切點表達與目標方法進行匹配
                                shadowMatch = fallbackExpression
                                    .matchesMethodExecution(methodToMatch);
                            }
                        } catch (ReflectionWorldException ex2) {
                            fallbackExpression = null;
                        }
                    }
                    if (shadowMatch == null && targetMethod != originalMethod) {
                        methodToMatch = originalMethod;
                        try {
                            // 若是目標方法與當前切點表達式匹配失敗,則判斷其原始方法與切點表達式
                            // 匹配是否成功
                            shadowMatch = obtainPointcutExpression()
                                .matchesMethodExecution(methodToMatch);
                        } catch (ReflectionWorldException ex3) {
                            try {
                                // 獲取原始方法上標註的切點表達式,做爲回調切點表達式,而且對
                                // 該切點表達式進行解析
                                fallbackExpression = getFallbackPointcutExpression(
                                    methodToMatch.getDeclaringClass());
                                if (fallbackExpression != null) {
                                    // 使用解析獲得的回調切點表達式與原始方法進行匹配
                                    shadowMatch = fallbackExpression
                                        .matchesMethodExecution(methodToMatch);
                                }
                            } catch (ReflectionWorldException ex4) {
                                fallbackExpression = null;
                            }
                        }
                    }
                } catch (Throwable ex) {
                    logger.debug("PointcutExpression matching " 
                                 + "rejected target method", ex);
                    fallbackExpression = null;
                }
                
                // 這裏若是目標方法和原始方法都沒法與切點表達式匹配,就直接封裝一個不匹配的結果
                // 到ShadowMatch中,而且返回
                if (shadowMatch == null) {
                    shadowMatch = new ShadowMatchImpl(
                        org.aspectj.util.FuzzyBoolean.NO, null, null, null);
                } else if (shadowMatch.maybeMatches() && fallbackExpression != null) {
                    // 若是經過匹配結果沒法當即判斷當前方法是否與目標方法匹配,就將匹配獲得的
                    // ShadowMatch和回調的ShadowMatch封裝到DefensiveShadowMatch中
                    shadowMatch = new DefensiveShadowMatch(shadowMatch,
                        fallbackExpression.matchesMethodExecution(methodToMatch));
                }
                
                // 將匹配結果緩存起來
                this.shadowMatchCache.put(targetMethod, shadowMatch);
            }
        }
    }
    return shadowMatch;
}

       關於getShadowMatch()方法,這裏須要說明的是,其參數是兩個方法,這裏是兩個方法的緣由在於當前目標方法多是實現了某個接口的方法,於是這裏會對目標方法及其接口方法都進行匹配。從上述匹配邏輯中也能夠看出這一點,即若是沒法經過目標方法獲取匹配結果,則經過其原始方法獲取匹配結果。這裏結果的匹配都是在PointcutExpression.matchesMethodExecution()方法中進行的,以下是該方法的實現:

public ShadowMatch matchesMethodExecution(Method aMethod) {
    // 判斷目標方法是否匹配當前方法
    ShadowMatch match = matchesExecution(aMethod);
    // 這裏的MATCH_INFO始終爲false
    if (MATCH_INFO && match.maybeMatches()) {
        System.out.println("MATCHINFO: method execution match on '" 
            + aMethod + "' for '" + this.expression + "': "
            + (match.alwaysMatches() ? "YES" : "MAYBE"));
    }
    return match;
}

       這裏咱們繼續閱讀matchesExecution()方法:

private ShadowMatch matchesExecution(Member aMember) {
    // 對aMember進行解析,由於其有可能爲Method,也多是Constructor,
    // 將解析後的結果封裝爲一個Shadow對象
    Shadow s = ReflectionShadow.makeExecutionShadow(world, aMember, this.matchContext);
    // 將生成的Shadow與當前的切點表達式進行匹配,並將匹配結果封裝到ShadowMatch中
    ShadowMatchImpl sm = getShadowMatch(s);
    // 設置subject,withinCode,withinType屬性
    sm.setSubject(aMember);
    sm.setWithinCode(null);
    sm.setWithinType(aMember.getDeclaringClass());
    return sm;
}

       在matchesExecution()方法中,其首先對當前要匹配的對象進行解析封裝,由於aMember有多是Method,也有多是Contructor。封裝以後將封裝的結果與當前解析獲得的Pointcut對象進行匹配,具體的匹配過程在getShadowMatch()方法中,以下是該方法的實現:

private ShadowMatchImpl getShadowMatch(Shadow forShadow) {
    // 使用解析獲得的Pointcut對象遞歸的對目標對象進行匹配
    org.aspectj.util.FuzzyBoolean match = pointcut.match(forShadow);
    Test residueTest = Literal.TRUE;
    ExposedState state = getExposedState();
    if (match.maybeTrue()) {
        // 對一些可能存在的須要進行匹配的內容進行匹配
        residueTest = pointcut.findResidue(forShadow, state);
    }
    
    // 將匹配結果封裝到ShadowMatch對象中
    ShadowMatchImpl sm = new ShadowMatchImpl(match, residueTest, state, parameters);
    sm.setMatchingContext(this.matchContext);
    return sm;
}

       這裏getShadowMatch()方法中對目標對象的匹配過程其實很是簡單,由於其直接將匹配過程委託給瞭解析獲得的Pointcut對象進行遞歸調用匹配,匹配完成以後將匹配結果封裝到ShadowMatch中並返回。

3. 小結

       本文首先講解了Spring Aop是如何解析切點表達式,將其遞歸的封裝爲Pointcut對象的,而後講解了經過解析獲得的Pointcut對象,如何遞歸的匹配目標屬性或方法,其匹配結果也就決定了當前Advisor的切面邏輯是否可以應用到目標對象上。

相關文章
相關標籤/搜索