關於Spring Aop存在的一點問題的思考

       在本人前面的文章Spring Aop原理之切點表達式解析中講解了Spring是如何解析切點表達式的,在分析源碼的時候,出現了以下將要講述的問題,我認爲是不合理的,後來本人單純使用aspectj進行試驗,發現結果與Spring源碼所表現出來的狀態是一致的。java

1. 現象

       咱們首先聲明一個目標類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.

2. 問題闡述

       這裏咱們的切點表達式中修飾符使用的是public,而經過Spring的源碼能夠發現,其是支持多個修飾符的,好比以下的切點表達式:.net

@Around("execution(public protected void Dog.*(..))")

       當使用該切點表達式的時候,上述程序也是能夠正常運行的,可是比較奇怪的是,目標方法Dog.run()是沒有被代理的。從業務的角度來看,上述表達式理論上應該匹配使用public或者protected修飾的方法,而Dog.run()方法是符合該條件的,可是這裏卻沒有。代理

3. 緣由分析

       這裏咱們仍是經過源碼來分析上述問題,即當出現多個修飾符的時候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是不容許方法擁有兩個修飾符的,也就是說這裏切點表達式是不管如何都沒法匹配上任何方法的。對象

4. 我的觀點

       本人開始覺得上述問題是Spring產生的bug,後來查閱了相關文檔,暫時沒發現對上述問題有描述的文檔。後來本人單純使用aspectj的jar包進行實驗,發現結果是一致的,使用aspectj的jar包實驗方法以下面這篇博文所示:AspectJ——簡介以及在IntelliJ IDEA下的配置。這說明這就是切點表達式規定的表示方式,可是本人認爲這種方式是不合理的,緣由主要有兩點:

  • 從用戶的角度來說,當切點表達式中使用了兩個修飾符時,通常的思考方向就是這種寫法應該是或的關係,即將匹配使用其中任意一種修飾符的目標對象;
  • 從Java語法的角度來說,Java是不容許一個類或方法同時使用兩種修飾符的,於是對於上述使用兩種修飾符的切點表達式,其將匹配不到任何方法,既然匹配不到任何方法,那爲何還容許這麼寫呢?
相關文章
相關標籤/搜索