Spring基于注解形式的 AOP的原理流程及源码解析(三)

此篇博客主要讲解Spring如何验证将要实例化的Bean是否应该被代理,生成代理对象的时机问题。

在第二篇博客中,Spring对容器内所有的标识了@Aspect注解的的类的切面方法(标识了@Around, @Before, @After, @AfterReturning, @AfterThrowing的方法)生成切面Advisor,切面由切入点Pointcut和通知组成,切入点一般是ExpressionPointcut,及带表达式的切入点,由注解上的value或pointcut属性来指定;通知一般就是被织入的方法,就是注解标识的方法。

Spring代理Bean默认是singleton形式,即多个织入方法织入同一个代理对象,而不是每个方法生成一个对象。传统的AOP是每个方法生成一个代理对象。详情可查看@Aspect的value属性。如果被代理对象的scope不是singleton,则需要在value属性上指定代理的类型,比如是perTarget等。

基于注解形式的AOP的代理对象生成的时机一般是在每个Bean初始化之后,及执行BeanPostProcessor的postProcessAfterInitialization方法,可看AbstractAutoProxyCreator的方法结构。如果对象的scope非singleton,那么生成代理对象的时机肯定在postProcessAfterInitialization;如果对象的scope是singleton,那么可能在postProcessAfterInitialization,也可能在getEarlyBeanReference方法。如果被代理的Bean触发了循环引用circular references,即Bean A注入了Bean B,而B内也注入了A,那么在解析B的时候会触发A的getEarlyBeanReference方法,如果A符合被代理的条件,则会直接生产代理对象,将代理对象注入B的属性中。在这种情况下有危险,B本来想注入A,但是结果注入的是A的代理对象,此时A还是一个原始的对象,还未注入属性及执行初始化方法,所以有很大程度上会报异常。结论 : 动态代理scope = singleton时请注意避免循环引用。

接下来将生成代理对象的代码:
代码块1

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    //如果scope = singleton的Bean触发circular references,会在此步生成代理实例,生成时机比正常的早得多
    public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (!this.earlyProxyReferences.contains(cacheKey)) {
            this.earlyProxyReferences.add(cacheKey);
        }
        return wrapIfNecessary(bean, beanName, cacheKey);
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            //大部分的动态代理对象均在此步骤生成,此步骤是Bean实例化的最后一步
            if (!this.earlyProxyReferences.contains(cacheKey)) {
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }

    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        }
        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        }
        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }

        // Create proxy if we have advice.
        //校验此类是否应该被代理,也就是将此类和所有的ExpressionPointcut匹配,匹配代码解析看下一块(代码块2)
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            Object proxy = createProxy(
                    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }

        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }


}

验证在实例化的Bean是否应该被代理,如果应该则发挥那些符合条件的待织入切面Advisor:
代码块2

public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator {

    protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) {
        List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
        if (advisors.isEmpty()) {
            return DO_NOT_PROXY;
        }
        return advisors.toArray();
    }

    protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
        //获取容器中的所有切面
        List<Advisor> candidateAdvisors = findCandidateAdvisors();
        //验证beanClass是否该被代理,如果应该,则返回所有符合条件的待织入切面
        List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
        extendAdvisors(eligibleAdvisors);
        if (!eligibleAdvisors.isEmpty()) {
            eligibleAdvisors = sortAdvisors(eligibleAdvisors);
        }
        return eligibleAdvisors;
    }

    protected List<Advisor> findAdvisorsThatCanApply(
            List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {

        ProxyCreationContext.setCurrentProxiedBeanName(beanName);
        try { //匹配过程,看下一块代码(代码块3)
            return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
        }
        finally {
            ProxyCreationContext.setCurrentProxiedBeanName(null);
        }
    }

}

AopUtils工具类循环的将每个Advisor与BeanClass匹配,如果匹配上就方法返回的集合中,
验证步骤:

  1. 在类上做精确匹配,如果匹配规则定义在方法上,则返回maybetrue,如果规则定义在类上,不匹配则直接返回false
  2. 当类上返回的结果为true或者maybetrue,再进行方法的匹配。方法的匹配也是先做模糊匹配,如果返回的为nevermatch或者alwaysmatche,则得到了结果。
  3. 如果返回的结果不能确定为true或false,则需要根据实际的去验证,比如args(…)则验证方法的参数是否有指定的类型,@args则验证方法的参数是否标识了指定的注解
  4. 在方法上做模糊匹配的原因是一个类从父类继承,实现接口及自身的方法很多,如果能提前做一个大致的预估,如果预估的结果是肯定不匹配或不匹配,则可以省去对其所有方法的匹配解析。

代码块3

public abstract class AopUtils {

    public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
        if (candidateAdvisors.isEmpty()) {
            return candidateAdvisors;
        }
        List<Advisor> eligibleAdvisors = new LinkedList<Advisor>();
        for (Advisor candidate : candidateAdvisors) {
            //基于注解形式生成的Advisor均是PointcutAdvisor,所以条件均不符合
            if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
                eligibleAdvisors.add(candidate);
            }
        }
        boolean hasIntroductions = !eligibleAdvisors.isEmpty();
        for (Advisor candidate : candidateAdvisors) {
            if (candidate instanceof IntroductionAdvisor) {
                // already processed
                continue;
            }
            if (canApply(candidate, clazz, hasIntroductions)) {
                eligibleAdvisors.add(candidate);
            }
        }
        return eligibleAdvisors;
    }

    public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
        if (advisor instanceof IntroductionAdvisor) {
            return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
        }
        else if (advisor instanceof PointcutAdvisor) {
            PointcutAdvisor pca = (PointcutAdvisor) advisor;
            //pca.getPointcut()返回的是一个AspectJExpressionPointcut类型对象
            return canApply(pca.getPointcut(), targetClass, hasIntroductions);
        }
        else {
            // It doesn't have a pointcut so we assume it applies.
            return true;
        }
    }

    public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
        Assert.notNull(pc, "Pointcut must not be null");
        //先对类做匹配,如果结果为false则说明不匹配,代码详解看代码块4
        if (!pc.getClassFilter().matches(targetClass)) {
            return false;
        }
        //再对方法做匹配,有些匹配规则只作用在类上,那么方法的匹配确定返回true,
        //很多规则是针对方法的代理,所以需要继续对合适的类里的所有方法做验证
        MethodMatcher methodMatcher = pc.getMethodMatcher();
        //methodMatcher 一般是AspectJExpressionPointcut对象自身,所有此步不符合
        if (methodMatcher == MethodMatcher.TRUE) {  
            // No need to iterate the methods if we're matching any method anyway...
            return true;
        }

        IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
        if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
            introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
        }

        Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
        classes.add(targetClass);
        for (Class<?> clazz : classes) {
            Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
            for (Method method : methods) {
                //获取类所实现的所有接口和所有类层级的方法,循环验证
                if ((introductionAwareMethodMatcher != null &&
                        introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||
                        methodMatcher.matches(method, targetClass)) {
                    return true;
                }
            }
        }

        return false;
    }

    public boolean matches(Method method, Class<?> targetClass) {
        return matches(method, targetClass, false);
    }

    public boolean matches(Method method, Class<?> targetClass, boolean beanHasIntroductions) {
        checkReadyToMatch();
        Method targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
        //getShadowMatch方法的调用繁杂,有点类似Class的匹配,最终也会调用到Pointcut的match(Shadow shadow)方法
        //Pointcut对方法的匹配看下一个方法
        ShadowMatch shadowMatch = getShadowMatch(targetMethod, method);

        // 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.
        //this, target, @this, @target, @annotation这些匹配规则都是匹配类的,当类的匹配通过时,方法均通过
        if (shadowMatch.alwaysMatches()) {
            return true;
        }
        else if (shadowMatch.neverMatches()) {
            return false;
        }
        else {
            // the maybe case
            if (beanHasIntroductions) {
                return true;
            }
            //如果FuzzyBoolean 即不确定错误,也不确定是正确的,那么就需要实际的验证,
            //比如args(...)则验证方法的参数是否有指定的类型,@args则验证方法的参数是否标识了指定的注解
            // 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));
        }
    }

public abstract class Pointcut extends PatternNode {

    public final FuzzyBoolean match(Shadow shadow) {
        if (shadow.shadowId == lastMatchedShadowId) {
            return lastMatchedShadowResult;
        }
        FuzzyBoolean ret;
        // this next test will prevent a lot of un-needed matching going on....
        if (shadow.getKind().isSet(couldMatchKinds())) {
            //不同的Pointcut实例对此方法做了不同的实现,比如args,this,target等等,
            //不是实实在在的根据表达式去验证方法,而仅仅只是粗略的返回一个模糊的结果,比如可能为真,也可能为假
            ret = matchInternal(shadow);
        } else {
            ret = FuzzyBoolean.NO;
        }
        lastMatchedShadowId = shadow.shadowId;
        lastMatchedShadowResult = ret;
        return ret;
    }

}

}

表达式形式的切入点对类的匹配验证:
代码块4

public class AspectJExpressionPointcut extends AbstractExpressionPointcut implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {

    public boolean matches(Class<?> targetClass) {
        checkReadyToMatch(); //确保生成表达式解析的Context环境
        try {
            try {  //根据表达式的解析实例,验证此类是否匹配,代码解析看下一块代码(代码块6)
                return this.pointcutExpression.couldMatchJoinPointsInType(targetClass);
            }
            catch (ReflectionWorldException ex) {
                logger.debug("PointcutExpression matching rejected target class - trying fallback expression", ex);
                // Actually this is still a "maybe" - treat the pointcut as dynamic if we don't know enough yet
                PointcutExpression fallbackExpression = getFallbackPointcutExpression(targetClass);
                if (fallbackExpression != null) {
                    return fallbackExpression.couldMatchJoinPointsInType(targetClass);
                }
            }
        }
        catch (Throwable ex) {
            logger.debug("PointcutExpression matching rejected target class", ex);
        }
        return false;
    }

    private void checkReadyToMatch() {
        if (getExpression() == null) {
            throw new IllegalStateException("Must set property 'expression' before attempting to match");
        }
        if (this.pointcutExpression == null) {
            this.pointcutClassLoader = determinePointcutClassLoader();
            //这一步很重要,根据切面注解的value属性,生成切入点表达式实例,代码解析看下一块代码(代码块5)
            this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
        }
    }

}

根据切面注解的value值,比如@After(“@args(….)”),生成不同实例的切入点表达式实例,即Pointcut实现类,切入点的实现类有很多,但Spring并不是每种都支持,支持的类型可以在AspectJExpressionPointcut的SUPPORTED_PRIMITIVES属性中看到。
代码块5

public class AspectJExpressionPointcut extends AbstractExpressionPointcut implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {

    private void checkReadyToMatch() {
        if (getExpression() == null) {
            throw new IllegalStateException("Must set property 'expression' before attempting to match");
        }
        if (this.pointcutExpression == null) {
            this.pointcutClassLoader = determinePointcutClassLoader();
            //第一步
            this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
        }
    }

    private PointcutExpression buildPointcutExpression(ClassLoader classLoader) {
        PointcutParser parser = initializePointcutParser(classLoader);
        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]);
        } 
        //第二步 
        return parser.parsePointcutExpression(replaceBooleanOperators(getExpression()),
                this.pointcutDeclarationScope, pointcutParameters);
    }

}

public class PointcutParser {

    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); // again, because we have now followed any ref'd pcuts
            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;
    }

    protected Pointcut resolvePointcutExpression(String expression, Class inScope, PointcutParameter[] formalParameters) {
        try {
            PatternParser parser = new PatternParser(expression);
            parser.setPointcutDesignatorHandlers(pointcutDesignators, world);
            Pointcut pc = parser.parsePointcut();  //第四步
            validateAgainstSupportedPrimitives(pc, expression);
            IScope resolutionScope = buildResolutionScope((inScope == null ? Object.class : inScope), formalParameters);
            pc = pc.resolve(resolutionScope);
            return pc;
        } catch (ParserException pEx) {
            throw new IllegalArgumentException(buildUserMessageFromParserException(expression, pEx));
        }
    }

    public Pointcut parsePointcut() {
        //第五步,最关键的一步
        Pointcut p = parseAtomicPointcut();
        if (maybeEat("&&")) {
            p = new AndPointcut(p, parseNotOrPointcut());
        }

        if (maybeEat("||")) {
            p = new OrPointcut(p, parsePointcut());
        }

        return p;
    }
    //根据切面注解的value属性的值上的特殊字符串,解析成不同的Pointcut的实例
    private Pointcut parseAtomicPointcut() {
        if (maybeEat("!")) {  //解析字符串以"!"开头
            int startPos = tokenSource.peek(-1).getStart();
            Pointcut p = new NotPointcut(parseAtomicPointcut(), startPos);
            return p;
        }
        if (maybeEat("(")) {  //解析value值以"("开头
            Pointcut p = parsePointcut();
            eat(")");
            return p;
        }
        if (maybeEat("@")) { //解析value值以"@"开头
            int startPos = tokenSource.peek().getStart();
            Pointcut p = parseAnnotationPointcut();
            int endPos = tokenSource.peek(-1).getEnd();
            p.setLocation(sourceContext, startPos, endPos);
            return p;
        } 
        int startPos = tokenSource.peek().getStart();
        Pointcut p = parseSinglePointcut();  //如果均不是
        int endPos = tokenSource.peek(-1).getEnd();
        p.setLocation(sourceContext, startPos, endPos);
        return p;
    }
    //解析value值以"@"开头
    public Pointcut parseAnnotationPointcut() {
        int start = tokenSource.getIndex();
        IToken t = tokenSource.peek();
        String kind = parseIdentifier();
        IToken possibleTypeVariableToken = tokenSource.peek();
        String[] typeVariables = maybeParseSimpleTypeVariableList();
        if (typeVariables != null) {
            String message = "(";
            assertNoTypeVariables(typeVariables, message, possibleTypeVariableToken);
        }
        tokenSource.setIndex(start);
        if (kind.equals("annotation")) {
            return parseAtAnnotationPointcut();
        } else if (kind.equals("args")) {
            return parseArgsAnnotationPointcut();
        } else if (kind.equals("this") || kind.equals("target")) {
            return parseThisOrTargetAnnotationPointcut();
        } else if (kind.equals("within")) {
            return parseWithinAnnotationPointcut();
        } else if (kind.equals("withincode")) {
            return parseWithinCodeAnnotationPointcut();
        }
        throw new ParserException("pointcut name", t);
    }
    //解析普通的单例的切入点实例
    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();
        // IToken possibleTypeVariableToken = tokenSource.peek();
        // String[] typeVariables = maybeParseSimpleTypeVariableList();
        if (kind.equals("execution") || kind.equals("call") || kind.equals("get") || kind.equals("set")) {
            p = parseKindedPointcut(kind);
        } else if (kind.equals("args")) {
            p = parseArgsPointcut();
        } else if (kind.equals("this")) {
            p = parseThisOrTargetPointcut(kind);
        } else if (kind.equals("target")) {
            p = parseThisOrTargetPointcut(kind);
        } else if (kind.equals("within")) {
            p = parseWithinPointcut();
        } else if (kind.equals("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")) {
            // - annotation style only allows if(), if(true) or if(false)
            // - if() means the body of the annotated method represents the if expression
            // - anything else is an error because code cannot be put into the if()
            // - code style will already have been processed and the call to maybeGetParsedPointcut()
            // at the top of this method will have succeeded.
            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);
                }
                // TODO - Alex has some token stuff going on here to get a readable name in place of ""...
                p = new IfPointcut("");
            }
        } else {
            boolean matchedByExtensionDesignator = false;
            // see if a registered handler wants to parse it, otherwise
            // treat as a reference pointcut
            for (PointcutDesignatorHandler pcd : pointcutDesignatorHandlers) {
                if (pcd.getDesignatorName().equals(kind)) {
                    p = parseDesignatorPointcut(pcd);
                    matchedByExtensionDesignator = true;
                }

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

切入点表达式实例即PointcutExpression验证一个类是否匹配表达式,主要是根据其内部持有的Pointcut实例来匹配验证,而Pointcut实例则根据切面注解的value值如@After(“@args(…..)”)来生成相应的Pointcut实现类,以这个实现类来验证:
代码块6

public class PointcutExpressionImpl implements PointcutExpression {

    private Pointcut pointcut;
    private String expression;

    public boolean couldMatchJoinPointsInType(Class aClass) {
        ResolvedType matchType = world.resolve(aClass.getName());
        if (matchType.isMissing() && (world instanceof ReflectionWorld)) {
            // Class is a generated class that cannot be 'looked up' via getResource.
            // For example a proxy or lambda.
            // Use the class itself in this case
            matchType = ((ReflectionWorld)world).resolveUsingClass(aClass);
        }
        ReflectionFastMatchInfo info = new ReflectionFastMatchInfo(matchType, null, this.matchContext, world);
        //关键看这步,不同的实现类返回不同的值,maybeTrue代表是可能为真,比如很多适配的原则是在方法上的,那么这步就会返回true
        //有些规则是要在类上做匹配的,则会在此步骤做验证,通过后在方法上的验证直接返回true
        boolean couldMatch = pointcut.fastMatch(info).maybeTrue();
        if (MATCH_INFO) {
            System.out.println("MATCHINFO: fast match for '" + this.expression + "' against '" + aClass.getName() + "': "
                    + couldMatch);
        }
        return couldMatch;
    }

}
    原文作者:AOP
    原文地址: https://blog.csdn.net/qq_27529917/article/details/78454853
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞