1. 程式人生 > >springmvc中aop對controller切面程式設計

springmvc中aop對controller切面程式設計

概述:

最近在開發一個基礎應用服務系統,利用加密的token標識來校驗訪問者的身份。幾乎每一個介面都需要校驗token。故打算採用aop面向切面程式設計,一次性對所有介面進行身份認證;

遇見的問題:

切面配置沒有問題的情況下,junit單元測試呼叫controller裡面的方法,可以觸發切點,實現切面程式設計。但是web部署到tomcat後,直接url訪問觸發切點失敗!

詳情描述:

切面類

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import
org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** * * @Description: TODO( token的校驗 ) * @author: mayao * @date 2016年10月20日 下午5:47:37 */ @Aspect @Component public class
UserTokenInterceptor {
//controller包的子包裡面任何方法 @Pointcut("execution(public * com.test.controller.*.*(..))") public void checkToken(){ } @Before("checkToken()") public void beforeCheckToken(){ System.out.println("呼叫方法之前。。。。"); } @AfterReturning("checkToken()") public
void afterCheckToken(){ System.out.println("呼叫方法結束之後。。。。"); } //丟擲異常時才呼叫 @AfterThrowing("checkToken()") public void afterThrowing() { System.out.println("校驗token出現異常了......"); } }

控制器

/**
 * 
 * @Description: TODO( 請求token )
 * @author: mayao
 * @date 2016年10月19日 下午5:11:25
 */

@Controller
@RequestMapping("/mayao")
public class TokenController {

    @RequestMapping(value="/test",method=RequestMethod.GET)
    public void test(){
        System.out.println("呼叫controller裡面的方法!!!");
    }

}

配置檔案

applicationContext.xml 部分程式碼片段
    <!-- 自動掃描專案下面的包 ,將帶有註解的類 納入spring容器管理   掃描service、dao -->
    <context:component-scan base-package="com.test"></context:component-scan>
    <!-- 配置使Spring採用CGLIB代理 -->
    <aop:aspectj-autoproxy proxy-target-class="true" />
spring-mvc.xml 部分程式碼片段
    <!-- 預設的註解對映的支援 -->
    <mvc:annotation-driven />

    <!-- 自動掃描該包,使SpringMVC認為包下用了@controller註解的類是控制器 -->
    <context:component-scan base-package="com.test.controller" />

junit測試程式碼

    @Test
    public void controllerAOPTest(){
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:applicationContext.xml");
        ctx.start();
        TokenController token = (TokenController)ctx.getBean(TokenController.class);
        token.test();
    }

測試結果

呼叫方法之前。。。。
呼叫controller裡面的方法!!!
呼叫方法結束之後。。。。

測試時成功的!!

然後啟動tomcat,訪問連結 http://localhost/專案名/mayao/test,結果是:呼叫controller裡面的方法!!!
切點沒有觸發!!

原因:

經過查詢資料及自己驗證得出:
1.是父子容器的問題
2.我的切面程式碼和連線點,通知都沒有問題,問題出在了配置資訊上面。

    <!-- 配置使Spring採用CGLIB代理 -->
    <aop:aspectj-autoproxy proxy-target-class="true" /> 

(部分借鑑)CGLIB代理配置在了applicationContext.xml 核心配置檔案中,該配置檔案會被ContextLoaderListenerclass載入,Spring會建立一個WebApplicationContext上下文,稱為父上下文(父容器) ,儲存在ServletContext中,key為WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的值。

而spring-mvc.xml是DispatcherServlet,可以同時配置多個,每個 DispatcherServlet有一個自己的上下文物件(WebApplicationContext),稱為子上下文(子容器),子上下文可以訪問父上下文中的內容,但父上下文不能訪問子上下文中的內容。 它也儲存在 ServletContext中,key是”org.springframework.web.servlet.FrameworkServlet.CONTEXT”+Servlet名稱

當spring加在父容器的時候就會去找切入點,但是這個時候切入的controller是在子容器中的,父容器是無法訪問子容器,所以就攔截不到。如果將上述的配置檔案放到spring-mvc.xml中,那麼問題就解決了。我已經測試可以通過URL訪問觸發切點了。

解決方法:

將配置資訊:
    <!-- 配置使Spring採用CGLIB代理 -->
    <aop:aspectj-autoproxy proxy-target-class="true" /> 
從applicationContext.xml移到spring-mvc.xml中就可以了

總結:

歸根結底來說,其實是bean的代理問題,涉及普通的bean,service,controller。下面的要注意。

Spring MVC 和 Spring 整合的時候,SpringMVC的spring-mvc.xml檔案中配置掃描包,不要包含 service的註解,Spring的applicationContext.xml檔案中配置掃描包時,不要包含controller的註解。

錯誤如下:

<!-- spring-mvc.xml  -->
<context:component-scan base-package="com.test">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    </context:component-scan>

<!-- applicationContext.xml  -->
 <context:component-scan base-package="com.test">           
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

Spring MVC啟動時的配置檔案,包含元件掃描、url對映以及設定freemarker引數,讓spring不掃描帶有@Service註解的類。為什麼要這樣設定?

因為spring-mvc.xml與applicationContext.xml不是同時載入,如果不進行這樣的設定,那麼,spring就會將所有帶@Service註解的類都掃描到容器中,等到載入applicationContext.xml的時候,會因為容器已經存在Service類,使得cglib將不對Service進行代理,直接導致的結果就是在applicationContext 中的事務配置不起作用,發生異常時,無法對資料進行回滾。以上就是原因所在。

不過不必太當心這個,實際應用中這樣的配置(指定service,controller掃描的)也很少。