羅瑞明
關(guān)鍵詞:Spring;面向切面編程;Request;Session;切面;通知;目標對象
0 引言
AOP(Aspect Oriented Programming) 是一種面向切面的編程思想。面向切面編程是將程序抽象成各個切面,即解剖對象的內(nèi)部,將那些影響了多個類的公共行為抽取到一個可重用模塊里,AOP的使用,使開發(fā)人員在編寫業(yè)務(wù)邏輯時可以專心于核心業(yè)務(wù),而不用過多地關(guān)注于其他業(yè)務(wù)邏輯的實現(xiàn),這不但提高了開發(fā)效率,而且增強了代碼的可維護性[1]。
本文討論的是在Spring框架下,編寫AOP切面程序時,如何訪問與具體業(yè)務(wù)無關(guān)的客戶端瀏覽器請求和會話的問題。
1 正文
1.1 問題描述
舉一個簡單實例,比如在一個程序應(yīng)用中,多處需要判斷用戶是否已經(jīng)登錄,這種與具體業(yè)務(wù)無關(guān)的邏輯或責任可以單獨封裝起來并被多個業(yè)務(wù)模塊共同調(diào)用。通過Spring AOP面向切片編程技術(shù)來實現(xiàn),可以降低業(yè)務(wù)邏輯之間的耦合性,提高程序的可重用性和開發(fā)效率。
1.2 分析設(shè)計
通常用戶在應(yīng)用程序的登錄模塊中完成登錄后,其登錄狀態(tài)需要在執(zhí)行其他應(yīng)用程序模塊時一直被檢查。這種信息通常被存儲在Session對象中,并在Web頁面進行跳轉(zhuǎn)的過程中,可以保存數(shù)據(jù)[2]。因此,在AOP切面類中需要放置與具體業(yè)務(wù)無直接耦合關(guān)系的代碼,以檢查用戶是否登錄,并在業(yè)務(wù)模塊運行中切入執(zhí)行,從而實現(xiàn)完整的業(yè)務(wù)邏輯。
如何在切面類中獲取存儲用戶登錄信息的Ses?sion對象是實現(xiàn)該AOP應(yīng)用的關(guān)鍵問題。本文將以此作為例子來探討解決這個問題的幾種思路和方法,并供讀者進行研究和交流。圖1展示了本示例應(yīng)用的界面:
該應(yīng)用包含兩個主要的業(yè)務(wù)模塊:第一個是用戶登錄模塊,用戶需要輸入用戶名和密碼才能登錄;另一個是用戶查詢模塊,可以根據(jù)用戶ID查詢用戶信息。用戶查詢模塊只有在成功登錄后才能進行,否則將被禁止查詢。用戶查詢模塊的界面參見圖2。
應(yīng)用主要由以下組件組成:
1) POJO實體類Customer。
2) 基于Customer 實體類的DAO 接口和Mapping映射文件。
3) 控制器CustomerController類,主要包含用戶登錄和根據(jù)用戶ID查詢用戶的方法。
4) Service接口和實現(xiàn)類,主要包含根據(jù)ID查找用戶和根據(jù)用戶名及密碼查找用戶的業(yè)務(wù)邏輯方法。
5) 一個切面(Aspect) 類,包含用于檢查用戶是否登錄的通知(Advice) 方法。
6) 目標(Target) 對象可以是Controller類或者Ser?vice 類。根據(jù)目標對象的實際情況,對應(yīng)的連接點(JoinPoint) 和切入點(Pointcut) 也不同。
1.3 方法實現(xiàn)
實現(xiàn)Spring AOP有多種方式,其中兩種主要方式分別是通過Spring API和AspectJ框架實現(xiàn)AOP。在AspectJ框架中,可以采用基于XML和基于注解(Anno?tation) 兩種配置方式。使用注解方式的配置,無須配置文件,只需要通過添加“注釋代碼”來完成,簡化了Spring的開發(fā),容易對方法進行攔截[3]。本文中的AOP實現(xiàn)以基于AspectJ框架的注解配置方式為例,使用Spring MVC作為控制層框架,MyBatis作為持久層框架,討論如何通過面向切面編程,在各業(yè)務(wù)模塊中實現(xiàn)自動檢查用戶是否已登錄。同時,提出了幾種在切面類中獲取Session對象的方法。
1) 通過目標方法所帶參數(shù)獲取Session對象
以下為CustomerController類的主要代碼,其用戶登錄業(yè)務(wù)方法login()會通過Service的findCustomerBy?NamePwd()方法來查詢用戶名和密碼,如果查詢到用戶信息則將其存入Session對象中,否則會返回異常頁面。另一個方法findCustomerById()則是按照用戶編號查詢用戶信息的業(yè)務(wù)方法。其代碼如下所示:
根據(jù)前面的分析,用戶查詢模塊必須先要求用戶登錄以便進行查詢操作。因此,需要通過AOP切面類獲取Session對象的信息來判斷用戶是否已經(jīng)登錄。切面類的通知(Advice) 方法可以利用JoinPoint或Pro?ceedingJoinPoint類型的參數(shù)獲取目標方法的參數(shù)信息,因此,可以設(shè)計目標方法增加Session類型的參數(shù),使切面類的通知(Advice) 方法獲得Session對象,進而完成登錄判斷。
Spring MVC 前端控制器(DispatcherServlet) 可以攔截和處理HTTP請求,并將請求發(fā)送給指定的Con?troller方法,請求會將其負載的參數(shù)傳遞給該方法,并等待方法處理這些信息[4]。因此,在控制器中的find?CustomerById()方法中,可以添加一個Session參數(shù)來接收前端控制器轉(zhuǎn)發(fā)的客戶端會話請求。然后在切面類的通知方法中,就可以獲取目標方法中的Session參數(shù)信息。
目標方法增加了一個類型為HttpSession的形參,用于供切面類的通知方法獲取Session信息。盡管該形參在用戶查詢業(yè)務(wù)中并沒有實際作用,但在切面類中卻能發(fā)揮重要作用。
通知方法需要根據(jù)用戶登錄狀態(tài)的判斷來決定是否繼續(xù)執(zhí)行目標方法。因此,在編寫切面類MyAs?pect時,應(yīng)該采用環(huán)繞通知(around) 方法來實現(xiàn)。代碼如下所示:
上述代碼中,首先使用@Aspect注解定義了切面類,該類作為Spring 中的組件需添加@Component 注解。接著,使用@Pointcut注解來配置和定義切入點。然后,通過@Around注解定義環(huán)繞通知,并通過Pro?ceedingJoinPoint類型的參數(shù)調(diào)用getArgs()方法獲取目標方法的參數(shù)對象列表,其中應(yīng)該包括Session對象。最后,對參數(shù)類型進行判斷,確定所需的Session對象,并通過Session對象中的loginUser屬性值來判斷用戶是否已經(jīng)登錄。
2) 通過RequestContextHolder類獲取Session對象在上一種方法中,切面類植入的是控制層Con?troller 類的方法,因為在控制層中獲取Request,Re?sponse和Session是很方便的,所以切面類的通知方法可方便地通過目標方法的參數(shù)獲取Session對象。但是如果切面類織入的是Service層的方法,那么獲取session對象將更加困難。這是本例根據(jù)ID查詢用戶的Service方法:
Service 層無法直接獲取Request 對象,Spring 的Web模塊提供了一個工具類RequestContextHolder來處理這個問題。RequestContextHolder持有上下文中的Request容器,提供了兩個ThreadLocal對象,用于保存當前線程下的Request??梢酝ㄟ^調(diào)用RequestCon?textHolder的靜態(tài)方法getRequestAttributes()獲得Serv?letRequestAttributes 對象,然后得到Request 對象和Session對象。修改切面類代碼如下所示:
3) 在切面類中直接注入Request、Response和Ses?sion對象
Spring 中AOP 代理由Spring 的IOC 容器負責生成、管理,其依賴關(guān)系也由IOC容器負責管理。因此,AOP代理可以直接使用容器中的其他bean實例作為目標,這種關(guān)系可由IOC容器的依賴注入提供[5]。既然Spring提供了工具類RequestContextHolder 來訪問外部對象Request,那么在切面類中直接注入Request等對象來實現(xiàn)訪問是否可行?經(jīng)研究發(fā)現(xiàn),Spring確實可以像注入普通bean一樣注入Request等實例,通過代理的方式,在啟動時注冊Web 環(huán)境相關(guān)的依賴對象。上面的切面類代碼修改后如下所示:
在上述代碼中,切面類通過@Autowired注解,直接注入HttpSession,在環(huán)繞通知(Advice) 中即可直接使用,非常方便。
2 總結(jié)
在Spring框架下,進行面向切面的編程(AOP) 時,切面類有時候需要進行一些與具體業(yè)務(wù)無關(guān)的操作,例如讀取用戶登錄信息、將用戶信息寫入日志、獲取客戶端IP等。為此,切面類需要使用Request、Session等對象。獲取這些對象的方法可以有兩種思路:通過目標方法獲取或者通過Spring框架提供的API獲取。
具體有如下幾種方法:
1) 通過訪問目標方法的參數(shù)來獲取。這要求目標方法的參數(shù)列表中應(yīng)該含有HttpSession 或HttpServletRequest類型的參數(shù),通常目標方法應(yīng)該在Controller控制層。
2) 通過Spring框架提供的工具類RequestContext?Holder來獲取。
3) 在切面類中直接注入(本質(zhì)上也是通過Spring框架提供的API獲?。?。
本文雖然以Spring MVC控制層框架為例,講解如何使用AOP切面類訪問Session對象,但對于使用其他MVC 控制層框架的開發(fā)也有一定參考價值。例如,Struts和Struts2等框架也可以通過目標方法參數(shù)或框架底層API獲取Request和Session等對象。