在本人前面的文章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下的配置。這說明這就是切點表達式規定的表示方式,可是本人認爲這種方式是不合理的,緣由主要有兩點: