在本人前面的文章Spring Aop原理之切点表达式解析中讲解了Spring是如何解析切点表达式的,在分析源码的时候,出现了以下将要讲述的问题,我认为是不合理的,后来本人单纯使用aspectj进行试验,发现结果与Spring源码所表现出来的状态是一致的。java
咱们首先声明一个目标类Dog
,其方法执行将会被代理,声明以下:spring
public class Dog { public void run() { System.out.println("Tidy is running."); } }
而后是切面类声明以下:app
@Aspect public class DogAspect { @Around("execution(public void Dog.*(..))") public Object aspect(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("before run. "); Object result = joinPoint.proceed(); System.out.println("after run."); return result; } }
能够看到,这里DogAspect
中声明的切面将会环绕Dog.run()
方法的执行。下面是xml配置和驱动类声明:ui
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="dog" class="Dog"/> <bean id="aspect" class="DogAspect"/> <aop:aspectj-autoproxy/> </beans>
public class DogApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Dog dog = context.getBean(Dog.class); dog.run(); } }
执行结果以下:this
before run. Tidy is running. after run.
这里咱们的切点表达式中修饰符使用的是public,而经过Spring的源码能够发现,其是支持多个修饰符的,好比以下的切点表达式:.net
@Around("execution(public protected void Dog.*(..))")
当使用该切点表达式的时候,上述程序也是能够正常运行的,可是比较奇怪的是,目标方法Dog.run()
是没有被代理的。从业务的角度来看,上述表达式理论上应该匹配使用public或者protected修饰的方法,而Dog.run()
方法是符合该条件的,可是这里却没有。代理
这里咱们仍是经过源码来分析上述问题,即当出现多个修饰符的时候Spring是如何对目标方法进行匹配的。以下是Spring Aop对修饰符解析的源码:code
public ModifiersPattern parseModifiersPattern() { // 存储修饰符的变量,使用二进制位进行标识 int requiredFlags = 0; // 存储应该被过滤的修饰符,使用二进制位进行标识 int forbiddenFlags = 0; int start; while (true) { start = tokenSource.getIndex(); boolean isForbidden = false; // 若是当前修饰符前面使用!,则表示该修饰符是须要被过滤掉的修饰符 isForbidden = maybeEat("!"); // 获取当前的修饰符 IToken t = tokenSource.next(); // 经过修饰符的名称获取其对应的一个二进制位数据。这里的ModifiersPattern其实比较简单, // 你们能够简单的将其理解为一个Map便可,即将每一个修饰符映射到惟一一个二进制位 int flag = ModifiersPattern.getModifierFlag(t.getString()); // 若是flag为-1,说明当前字符串不是修饰符,此时退出循环,进行下一步的解析 if (flag == -1) { break; } // 若是当前修饰符是应该被过滤的修饰符,则将其存储在forbiddenFlags中; // 若是当前修饰符是被须要的修饰符,则将其存储在requiredFlags中 if (isForbidden) { forbiddenFlags |= flag; } else { requiredFlags |= flag; } } tokenSource.setIndex(start); // 若是被须要的修饰符和被禁止的修饰符都不存在,说明当前切点表达式将匹配以任意类型修饰符修饰的方法 if (requiredFlags == 0 && forbiddenFlags == 0) { return ModifiersPattern.ANY; } else { // 若是有任意一个值不为0,说明当前切点表达式对修饰符有要求,于是将其封装到ModifiersPattern中 return new ModifiersPattern(requiredFlags, forbiddenFlags); } }
能够看到,Spring是将被须要的修饰符和被禁止的修饰符分别存储在两个变量中的:requiredFlags和forbiddenFlags。对于咱们上述声明的两个修饰符public和protected,其对应的flag值分别是1和4。也就是说,此时requiredFlags的值为5,而forbiddenFlags的值为0。这两个值都存储在一个ModifiersPattern类型的对象中。上文中咱们讲过,Spring Aop对目标方法的匹配是经过递归实现的,于是这里对目标方法的匹配逻辑确定是在ModifiersPattern中声明了,下面是其匹配相关的源码:xml
public class ModifiersPattern extends PatternNode { private int requiredModifiers; private int forbiddenModifiers; public ModifiersPattern(int requiredModifiers, int forbiddenModifiers) { this.requiredModifiers = requiredModifiers; this.forbiddenModifiers = forbiddenModifiers; } public boolean matches(int modifiers) { return ((modifiers & requiredModifiers) == requiredModifiers) && ((modifiers & forbiddenModifiers) == 0); } }
能够看到,ModifiersPattern.matches()
就是其匹配逻辑所在,参数modifiers就是目标方法的修饰符。在其实现逻辑中,与requiredModifiers相关的代码能够看出,若是在切点表达式中声明了两个修饰符,那么要求目标方法的修饰符也必须是至少包含这两个。对于这里的例子也就是说,目标方法必须使用至少public和protected进行修饰。这就是问题的所在,理论上Java是不容许方法拥有两个修饰符的,也就是说这里切点表达式是不管如何都没法匹配上任何方法的。对象
本人开始觉得上述问题是Spring产生的bug,后来查阅了相关文档,暂时没发现对上述问题有描述的文档。后来本人单纯使用aspectj的jar包进行实验,发现结果是一致的,使用aspectj的jar包实验方法以下面这篇博文所示:AspectJ——简介以及在IntelliJ IDEA下的配置。这说明这就是切点表达式规定的表示方式,可是本人认为这种方式是不合理的,缘由主要有两点: