spring aop之父子容器
AuthController
@RestController public class AuthController { @PostMapping("authentication") @TokenChecker public Result getToken(String username,String password){ return null; } }
@Aspect @Component public class TokenAspect { //匹配指定類下的有指定注解的方法。只要在要切入的方法上加上注解就可以將該方法作為切入點。 @Pointcut("execution (public * com.cotroller..*.*(..)) && @annotation(com.annotation.TokenChecker)") public void addAdvice() { } @Before("addAdvice()") public void before() { System.err.println("before"); } @After("addAdvice()") public void after() { System.err.println("after"); } }
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:/applicationContext.xml</param-value> </context-param> <servlet> <servlet-name>spring-mvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:/applicationContext-mvc.xml</param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet>
applicationContext.xml關鍵配置信息
<aop:aspectj-autoproxy/> <context:component-scan base-package="com" name-generator="com.xxx"> <!-- 不掃描Controller注解 --> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" /> <context:exclude-filter type="annotation" expression="org.springframework.context.annotation.Configuration" /> </context:component-scan>
applicationContext-mvc.xml關鍵配置信息
<context:component-scan base-package="com" use-default-filters="false"> <!-- 掃描controller注解 --> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> <context:include-filter type="annotation" expression="org.springframework.context.annotation.Configuration" /> </context:component-scan>
需要具備知識:
Spring 框架允許在一個應用中創建多個上下文容器。但是建議容器之間有父子關系。可以通過 ConfigurableApplicationContext 接口中定義的 setParent 方法設置父容器。一旦設置父子關系,則可以通過子容器獲取父容器中除 PropertyPlaceHolder 以外的所有資源,父容器不能獲取子容器中的任意資源(類似 Java 中的類型繼承)。
1. 對于傳統的web項目來說,通常使用spring和springmvc,因此對于這種項目來講,他是有兩個容器的,一個是spring容器,一般我們會把Service層的東西注入到spring容器中,另一個是springmvc的容器,通常這個容器里注入的是Controller層的東西,這里我們認為spring容器是父容器,springmvc是子容器的概念,然后我們大家都知道通過父子繼承關系可知,子容器是可以讀取到父容器中的東西,但是父容器是無法讀到子容器中的內容,因此基于這個場景,有的同學,把Aop的實現類注入到了spring容器中,并且將Aop的切點表達式配置<aop:config> <aop:pointcut的execution也配到了spring容器的xml,而巧了這位同學要切的類方法,正好是Controller,也就是springmvc容器中的東西,那么這時候問題就來了,aop在初始化時會在自己的容器中尋找能夠匹配的類方法,然后給他套上一層代理,此時他在自己能夠訪問到的spring容器中根本找不到與之匹配的類和方法,因為這些類和方法是在springmvc容器中管理的,因此就沒有代理成功。
那么對于上述問題要怎么修改呢?只需要確保你要Aop切的類和方法與你Aop配置切點<aop:config> <aop:pointcut的execution表達式聲明是在同一個容器中即可,此時只需要講這個配置移到springmvc容器的xml中即可
2. 第二種情況與第一種情況有些許的類似,但并不相同,是關于重復掃描的,比如你在spring容器中配置了一個Aop,并且把他托管給spring容器管理,而且execution表達式切的也是spring容器中管理的類和方法,理論上這個時候是好用的,這批execution切到的類都被加了代理,但是巧了,springmvc容器中由于配置的是包路徑掃描,恰好把execution表達式切的這一批對象又掃了一遍,又都托管給了springmvc容器,而此時掃到的這批對象,是重新new出來交給springmvc管理的,因此并沒有被aop代理,所以在使用時,注入進來的可能是springmvc容器管理的這批對象,因此使用時發現Aop代理失效了。
這個問題的解決方案,就是避免兩個容器重復掃描。
3. 第三個問題就比較簡單了,他的現象是有些方法被Aop代理成功了,但有個別方法沒有代理成功,究其原因發現這部分沒有代理成功的方法并不是通過代理對象調用的,而是自身調用的,故被調用的方法沒有被Aop代理,無法織入橫切邏輯。
這個問題如果理解起來困難的話我舉個例子,比如A.a(),A.b()是被代理的類和方法,那么當我調用A.a()時,此時a被代理了,成功執行代理類的內容,但還沒有完,a()方法中調用了自身的方法b(),此時我們以為b也會被代理類代理,但實際上并沒有,因為他是自身方法調用了并不是通過代理類A調用的,如果通過A.b()這種調用方式,那么b是可以被成功代理的。
分析:
1.我們的Controller類,使用注解@RestController,在applicationContext.xml中不掃描Controller注解的類,在applicationContext-mvc掃描Controller注解的類,結合web.xml可知我們的Controller AuthController被子容器管理。
2.我們的切面使用的注解是@Aspect和@Component, 結合 applicationContext.xml與web.xml可知,切面實例被父容器管理。
3.解決 因為我們切面類中使用的@Component注解會被Spring父容器管理,不適用注解,在application-mvc.xml中使用xml方法方式實例化切面類。這樣Controller和切面類在同一個容器中管理,就可以正常的進行aop代理。
錯誤的嘗試:
1.Controller使用@Component注解,想讓其鬼父容器管理這樣Controller和切面就歸同一個容器管理了:這時候通過postman訪問接口請求失敗。
2.切面類,使用@Controller 還是沒有進入切面類。看到配置中不但把Controller注解還有Configuration注解都歸子容器管理,把切面類的注解改為@Configuration同樣還是沒有進入切面。
總結:
1.分析的問題的時候,注意web.xml的重要性,好多問題都可以從web.xml 這個配置文件開始往后面推。
2.深刻的理解一下父子容器的關系。哪些注解的類初始化后被父容器管理,哪些被子容器管理。
3. 動態代理 JDK與CGLIB的區別。我這個項目中就有一個被final修飾的Controller。導致在設置代理方式的出錯。在不同的配置文件中設置代理方式應該是對加載這個配置的容器管理的類有影響,對別的容器不影響。
浙公網安備 33010602011771號