OOPAA

          Focusing on OO, Patterns, Architecture, and Agile
          posts - 29, comments - 75, trackbacks - 0, articles - 0
            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          Spring2.5 訪問 Session 屬性的四種策略

          Posted on 2008-10-12 16:57 mingj 閱讀(4348) 評(píng)論(4)  編輯  收藏 所屬分類: Spring

          WEB 應(yīng)用通常會(huì)引入 Session,用來在服務(wù)端和客戶端之間保存一系列動(dòng)作/消息的狀態(tài),比如網(wǎng)上購物維護(hù) user 登錄信息直到 user 退出。在 user 登錄后,Session 周期里有很多 action 都需要從 Session 中得到 user,再驗(yàn)證身份權(quán)限,或者進(jìn)行其他的操作。這其中就會(huì)涉及到程序去訪問 Session屬性的問題。在java中,Servlet 規(guī)范提供了 HttpSession對(duì)象來滿足這種需求。開發(fā)人員可以從 HttpServletRquest對(duì)象得到 HttpSession,再從HttpSession中得到狀態(tài)信息。

          還是回到購物車的例子,假設(shè)在 controller 某個(gè)方法(本文簡稱為action)中我們要從HttpSession中取到user對(duì)象。如果基于Servlet,標(biāo)準(zhǔn)的代碼會(huì)是這樣的:

          public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
           User user 
          = (User)req.getSession().getAttribute("currentUser");
           
          //
          }

          這樣的代碼在傳統(tǒng)的Servlet程序中是很常見的:因?yàn)槭褂昧?Servlet API,從而對(duì) Servlet API產(chǎn)生依賴。這樣如果我們要測試 action,我們就必須針對(duì) HttpServletRequest、HttpServletResponse 和 HttpSession類提供 mock 或者 stub 實(shí)現(xiàn)。當(dāng)然現(xiàn)在已經(jīng)有很多開源的 Servlet 測試框架幫助我們減輕這個(gè)痛苦,包括 Spring 就自帶了對(duì)了這些類的 stub 實(shí)現(xiàn),但那還是太冗繁瑣碎了。那有沒有比較好的辦法來讓我們的 controller 更 POJO,讓我們的 action 脫離 Servlet API 依賴,更有益于測試和復(fù)用呢?我們來看看在 Spring2.5 中訪問 Session 屬性的幾種策略,并將在本博的后續(xù)文章繼續(xù)探究解決方案選擇后面的深層含義。


          (一)通過方法參數(shù)傳入HttpServletRequest對(duì)象或者HttpSession對(duì)象
          筆者的前一篇文章已經(jīng)簡單介紹了Spring2.5的annotation使得 controller 擺脫了 Servlet API 對(duì)方法參數(shù)的限制,這里就不贅述了。有興趣的同學(xué)可以參考這里。Spring對(duì)annotationed的 action 的參數(shù)提供自動(dòng)綁定支持的參數(shù)類型包括 Servlet API 里面的 Request/Response/HttpSession(包含Request、Response在Servlet API 中聲明的具體子類)。于是開發(fā)人員可以通過在 action 參數(shù)中聲明 Request 對(duì)象或者 HttpSession 對(duì)象,來讓容器注入相應(yīng)的對(duì)象。

          action 的代碼如下:

          @RequestMapping
          public void hello(HttpSession session){
           User user 
          = (User)session.getAttribute("currentUser");
           
          //
          }

          優(yōu)點(diǎn):
          1. 程序中直接得到底層的 Request/HttpSession 對(duì)象,直接使用 Servlet API 規(guī)范中定義的方法操作這些對(duì)象中的屬性,直接而簡單。
          2. action 需要訪問哪些具體的 Session 屬性,是由自己控制的,真正精確到 Session 中的每個(gè)特定屬性。
          不足:
          1. 程序?qū)?Servlet API 產(chǎn)生依賴。雖然 controller 類已經(jīng)不需要從 HttpServlet 繼承,但仍需要 Servlet API 才能完成編譯運(yùn)行,乃至測試。
          2. 暴露了底層 Servlet API,暴露了很多并不需要的底層方法和類,開發(fā)人員容易濫用這些 API。

          (二)通過定制攔截器(Interceptor)在controller類級(jí)別注入需要的User對(duì)象
          Interceptor 是 Spring 提供的擴(kuò)展點(diǎn)之一,SpringMVC 會(huì)在 handle 某個(gè) request 前后調(diào)用在配置中定義的 Interceptor 完成一些切面的工作,比如驗(yàn)證用戶權(quán)限、處理分發(fā)等,類似于 AOP。那么,我們可以提取這樣一個(gè)“橫切點(diǎn)”,在 SpringMVC 調(diào)用 action 前,在 Interceptor 的 preHandle 方法中給 controller 注入 User 成員變量,使之具有當(dāng)前登錄的 User 對(duì)象。

          此外還需要給這些特定 controller 聲明一類 interface,比如 IUserAware。這樣開發(fā)人員就可以只針對(duì)這些需要注入 User 對(duì)象的 controller 進(jìn)行注入增強(qiáng)。

          IUserAware 的代碼:

          public interface IUserAware {
           
          public void setUser();
          }

          controller 的代碼:

          @Controller
          public GreetingController implements IUserAware {
           
          private User user;
           
          public void setUser(User user){
            
          this.user = user;
           }

           
           @RequestMapping
           
          public void hello(){
            
          //user.sayHello();
           }

           
          //
          }

          Interceptor 的代碼:

          public class UserInjectInterceptor extends HandlerInterceptorAdapter {
           @Override
              
          public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
                  
          if (handler.isAssignableFrom(IUserAware)){
                   User user 
          = (User)httpServletRequest.getSession().getAttribute("currentUser");
                   IUserAware userAware 
          = (IUserAware) handler;
                   userAware.setUser(user);
                  }

                  
          return super.preHandle(httpServletRequest, httpServletResponse, handler);
              }

              
          //
          }

          為了讓 SpringMVC 能調(diào)用我們定義的 Interceptor,我們還需要在 SpringMVC 配置文件中聲明該 Interceptor,比如:

          <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
              
          <property name="interceptors">
                  
          <list>
                      
          <ref bean="userInjectInterceptor"/><!-- userInjectInterceptor bean 的聲明省略-->
                  
          </list>
              
          </property>
          </bean>

          優(yōu)點(diǎn):
          1. 對(duì) Servlet API 的訪問被移到了自 SpringMVC API 擴(kuò)展的 Interceptor,controller 不需要關(guān)心 User 如何得到。
          2. 開發(fā)人員可以通過隨時(shí)添加或移除 Interceptor 來完成對(duì)不同參數(shù)在某一類型 controller 上的注入。
          3. controller 的 User 對(duì)象通過外界注入,測試時(shí)開發(fā)人員可以很容易地注入自己想要的 User 對(duì)象。
          4. controller 類去掉了對(duì) Servlet API 的依賴,更 POJO 和通用。
          5. controller 類是通過對(duì) interface 的聲明來輔助完成注入的,并不存在任何繼承依賴。
          不足:
          1. SpringMVC 對(duì) controller 默認(rèn)是按照單例(singleton)處理的,在 controller 類中添加一個(gè)成員變量,可能會(huì)引起多線程的安全問題。
          2. 因?yàn)?User 對(duì)象是定義為 controller 的成員變量,而且是通過 setter 注入進(jìn)來,在測試時(shí)需要很小心地保證對(duì)controller 注入了 User 對(duì)象,否則有可能我們拿到的就不一定是一個(gè)“好公民”(Good Citizen)。

          其實(shí),一言而蔽之,這些不足之所以出現(xiàn),是因?yàn)槲覀儼涯硞€(gè) action 級(jí)別需要的 User 對(duì)象上提到 controller 級(jí)別,破壞了 the convention of stateless for controller classes,而 setter 方式的注入又帶來了一些隱含的繁瑣和不足。當(dāng)然,我們可以通過把 controller 聲明為“prototype”來繞過 stateless 的約定,也可以保證每次 new 一個(gè) controller 的同時(shí)給其注入一個(gè) User 對(duì)象。但是我們有沒有更簡單更 OO 的方式來實(shí)現(xiàn)呢?答案是有的。

          (三)通過方法參數(shù)處理類(MethodArgumentResolver)在方法級(jí)別注入U(xiǎn)ser對(duì)象
          正如前面所看到的,SpringMVC 提供了不少擴(kuò)展點(diǎn)給開發(fā)人員擴(kuò)展,讓開發(fā)人員可以按需索取,plugin 上自定義的類或 handler。那么,在 controller 類的層次上,SpringMVC 提供了 Interceptor 擴(kuò)展,在 action 上有沒有提供相應(yīng)的 handler 呢?如果我們能夠?qū)?action 實(shí)現(xiàn)注入,出現(xiàn)的種種不足了。

          通過查閱 SpringMVC API 文檔,SpringMVC 其實(shí)也為 action 級(jí)別提供了方法參數(shù)注入的 Resolver 擴(kuò)展,允許開發(fā)人員給 HandlerMapper 類 set 自定義的 MethodArgumentResolver。

          action 的代碼如下:

          @RequestMapping
          public void hello(User user){
           
          //user.sayHello()
          }

          Resolver 的代碼如下:

          public class UserArgumentResolver implements WebArgumentResolver {

              
          public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception {
                  
          if (methodParameter.getParameterType().equals(User.class)) {
                      
          return webRequest.getAttribute("currentUser", RequestAttributes.SCOPE_SESSION);
                  }

                  
          return UNRESOLVED;
              }

          }


          配置文件的相關(guān)配置如下:

          <bean class="org.springframework.web.servlet.mvc.annotation.OwnAnnotationMethodHandlerAdapter">
              
          <property name="customArgumentResolver">
                  
          <ref bean="userArgumentResolver"/><!-- userArgumentResolver bean 的定義省略 -->
              
          </property>
          </bean>

          優(yōu)點(diǎn):
          1. 具備第二種方案的所有優(yōu)點(diǎn)
          2. 真正做到了按需分配,只在真正需要對(duì)象的位置注入具體的對(duì)象,減少其他地方對(duì)該對(duì)象的依賴。
          3. 其他人能很容易地從 action 的參數(shù)列表得知 action 所需要的依賴,API 更清晰易懂。
          4. 對(duì)于很多 action 需要的某一類參數(shù),可以在唯一的設(shè)置點(diǎn)用很方便一致的方式進(jìn)行注入。
          不足:
          1. 對(duì)象依賴注入是針對(duì)所有 action, 注入粒度還是較粗。不能做到具體 action 訪問具體的 Session 屬性

          (四)通過 SpringMVC 的 SessionAttributes Annotation 關(guān)聯(lián) User 屬性
          SpringMVC 文檔提到了 @SessionAttributes annotation,和 @ModelAttribute 配合使用可以往 Session 中存或者從 Session 中取指定屬性名的具體對(duì)象。文檔里說;

          The type-level @SessionAttributes annotation declares session attributes used by a specific handler. This will typically list the names of model attributes which should be transparently stored in the session or some conversational storage, serving as form-backing beans between subsequent requests.

          很明顯,@SessionAttributes 是用來在 controller 內(nèi)部共享 model 屬性的。從文檔自帶的例子來看,標(biāo)注成 @SessionAttributes 屬性的對(duì)象,會(huì)一直保留在 Session 或者其他會(huì)話存儲(chǔ)中,直到 SessionStatus 被顯式 setComplete()。那這個(gè) annotation 對(duì)我們有什么幫助呢?

          答案就是我們可以在需要訪問 Session 屬性的 controller 上加上 @SessionAttributes,然后在 action 需要的 User 參數(shù)上加上 @ModelAttribute,并保證兩者的屬性名稱一致。SpringMVC 就會(huì)自動(dòng)將 @SessionAttributes 定義的屬性注入到 ModelMap 對(duì)象,在 setup action 的參數(shù)列表時(shí),去 ModelMap 中取到這樣的對(duì)象,再添加到參數(shù)列表。只要我們不去調(diào)用 SessionStatus 的 setComplete() 方法,這個(gè)對(duì)象就會(huì)一直保留在 Session 中,從而實(shí)現(xiàn) Session 信息的共享。

          controller的代碼如下:

          @Controller
          @SessionAttributes(
          "currentUser")
          public class GreetingController{
           @RequestMapping
           
          public void hello(@ModelAttribute("currentUser") User user){
            
          //user.sayHello()
           }

           
          //
          }


          使用這種方案,還需要在 SpringMVC 配置文件的 ViewResolver 定義處,加上 p:allowSessionOverride="true",這樣如果你對(duì) User 對(duì)象做了修改,SpringMVC 就會(huì)在渲染 View 的同時(shí)覆寫 Session 中的相關(guān)屬性。

          優(yōu)點(diǎn):
          1. 具備第二種方案的所有優(yōu)點(diǎn)
          2. 使用 Annotation 聲明對(duì) Session 特定屬性的存取,每個(gè) action 只需要聲明自己想要的 Session 屬性。
          3. 其他人能很容易地從 action 的參數(shù)列表得知 action 所需要的依賴,API 更清晰易懂。
          不足:
          1. 對(duì)于相同屬性的 Session 對(duì)象,需要在每個(gè) action 上定義。
          2. 這種方案并不是 SpringMVC 的初衷,因此有可能會(huì)引起一些爭議。

          縱觀這四類方法,我們可以看出我們對(duì) Session 屬性的訪問控制設(shè)置,是從所有 Servlet,到某一類型的 controller 的成員變量,到所有 action 的某一類型參數(shù),再到具體 action 的具體對(duì)象。每種方案都有各自的優(yōu)點(diǎn)和不足:第一種方案雖然精確,但可惜引入了對(duì) Servlet API 的依賴,不利于 controller 的測試和邏輯復(fù)用。第二、三種方案雖然解決了對(duì) Servlet API 的依賴,也分別在 controller 和 action 級(jí)別上提供了對(duì) Session 屬性的訪問,但注入粒度在一定程度上還是不夠細(xì),要想對(duì)具體屬性進(jìn)行訪問可能會(huì)比較繁瑣。不過,這在另一方面也提供了簡便而統(tǒng)一的方法來對(duì)一系列相同類型的參數(shù)進(jìn)行注入。第四種方案通過使用 Annotation,不僅擺脫了 Servlet API 的依賴,而且在 action 級(jí)別上提供了對(duì) Session 具體屬性的訪問控制。但是這種訪問有可能會(huì)粒度過細(xì),需要在很多不同 action 上聲明相同的 annotation。而且,畢竟這種用法并不是 SpringMVC 的初衷和推薦的,可能會(huì)帶來一些爭議。


          本文演示了 Spring2.5 訪問 Session 屬性的幾種不同解決方案,并分析了各自的優(yōu)點(diǎn)和不足。本文并不打算對(duì)這些解決方案評(píng)出對(duì)錯(cuò),只是試圖列出在選擇方案時(shí)的思維過程以及選擇標(biāo)準(zhǔn)。每種方案都能滿足某一類上下文的需求,在特定的開發(fā)環(huán)境和團(tuán)隊(duì)中都可能會(huì)是最優(yōu)的選擇。但是筆者還是發(fā)現(xiàn),整個(gè)過程中,一些平常容易忽視的 OOP 的準(zhǔn)則或者原則在發(fā)揮著效應(yīng),鑒于本文篇幅已經(jīng)較長,就留到后續(xù)文章中繼續(xù)探討解決方案選擇背后的深層含義,敬請(qǐng)期待。


          評(píng)論

          # re: Spring2.5 訪問 Session 屬性的四種策略  回復(fù)  更多評(píng)論   

          2008-10-13 09:35 by 一臉大鼻涕
          不錯(cuò),我覺得springMVC沒比struts差哪,就是生不逢時(shí)

          # re: Spring2.5 訪問 Session 屬性的四種策略  回復(fù)  更多評(píng)論   

          2008-11-08 23:49 by mingj
          @一臉大鼻涕
          springmvc 的確很不錯(cuò)
          其實(shí)看rod johnson的 without ejb,就知道他對(duì)web開發(fā)的理解多深刻
          但畢竟不是專門做這塊的, springmvc還是存在幾個(gè)硬傷, 以后詳加解釋

          # re: Spring2.5 訪問 Session 屬性的四種策略  回復(fù)  更多評(píng)論   

          2009-01-17 15:59 by leekiang
          期待你講spring mvc的硬傷

          # re: Spring2.5 訪問 Session 屬性的四種策略  回復(fù)  更多評(píng)論   

          2009-05-23 17:41 by 王兵
          好文章

          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 古丈县| 花莲市| 灵丘县| 柳河县| 蛟河市| 长寿区| 榆中县| 浪卡子县| 博乐市| 宁南县| 林周县| 壤塘县| 莱州市| 喀喇沁旗| 临沧市| 红安县| 抚宁县| 广南县| 建昌县| 花垣县| 林西县| 齐河县| 平塘县| 丽水市| 金川县| 陆良县| 山丹县| 宝应县| 永昌县| 贵南县| 廊坊市| 乌兰浩特市| 石首市| 兖州市| 岑溪市| 保康县| 江山市| 南投市| 大关县| 永春县| 石景山区|