【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
在上文(Spring Aop之Advisor解析)中我们讲到,Spring Aop对目标bean的代理主要分为三个步骤:获取所有的Advisor,过滤当前bean可应用的Advisor和使用Advisor为当前bean生成代理对象,并且上文我们也讲解了Spring是如何获取所有的Advisor的。本文主要讲解这其中的第二个步骤,即Spring是如何从这些Advisor中过滤得到可以应用到当前bean的Advisor。
1. 切点表达式解析
在上文中我们讲到了AbstractAdvisorAutoProxyCreator.findEligibleAdvisors()
方法,该方法中首先会获取到所有的Advisor,然后会过滤得到可应用的Advisor。如下是该方法的实现:
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()
方法的源码,这里我们直接看其最终调用的方法:
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()
方法的实现:
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()
的实现源码:
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()
的实现原理:
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()
方法中,我们继续看该方法的实现:
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));
}
}
可以看到,这里对切点表达式的解析主要分为两个部分,第一部分是对切点表达式的解析,第二部分是对指定的参数进行解析。由于切点表达式和参数的绑定解析比较复杂,我们将在下一篇文章中进行讲解。
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的切面逻辑是否能够应用到目标对象上。