在上文(Spring Aop之Advisor解析)中咱们讲到,Spring Aop对目标bean的代理主要分为三个步骤:获取全部的Advisor,过滤当前bean可应用的Advisor和使用Advisor为当前bean生成代理对象,而且上文咱们也讲解了Spring是如何获取全部的Advisor的。本文主要讲解这其中的第二个步骤,即Spring是如何从这些Advisor中过滤获得能够应用到当前bean的Advisor。java
在上文中咱们讲到了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
关于切点的匹配,这里主要是在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中并返回。
本文首先讲解了Spring Aop是如何解析切点表达式,将其递归的封装为Pointcut对象的,而后讲解了经过解析获得的Pointcut对象,如何递归的匹配目标属性或方法,其匹配结果也就决定了当前Advisor的切面逻辑是否可以应用到目标对象上。