<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    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 閱讀(4347) 評論(4)  編輯  收藏 所屬分類: Spring

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

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

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

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


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

    action 的代碼如下:

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

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

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

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

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

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

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

    通過查閱 SpringMVC API 文檔,SpringMVC 其實也為 action 級別提供了方法參數注入的 Resolver 擴展,允許開發(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;
        }

    }


    配置文件的相關配置如下:

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

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

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

    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 內部共享 model 屬性的。從文檔自帶的例子來看,標注成 @SessionAttributes 屬性的對象,會一直保留在 Session 或者其他會話存儲中,直到 SessionStatus 被顯式 setComplete()。那這個 annotation 對我們有什么幫助呢?

    答案就是我們可以在需要訪問 Session 屬性的 controller 上加上 @SessionAttributes,然后在 action 需要的 User 參數上加上 @ModelAttribute,并保證兩者的屬性名稱一致。SpringMVC 就會自動將 @SessionAttributes 定義的屬性注入到 ModelMap 對象,在 setup action 的參數列表時,去 ModelMap 中取到這樣的對象,再添加到參數列表。只要我們不去調用 SessionStatus 的 setComplete() 方法,這個對象就會一直保留在 Session 中,從而實現(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",這樣如果你對 User 對象做了修改,SpringMVC 就會在渲染 View 的同時覆寫 Session 中的相關屬性。

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

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


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


    評論

    # re: Spring2.5 訪問 Session 屬性的四種策略  回復  更多評論   

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

    # re: Spring2.5 訪問 Session 屬性的四種策略  回復  更多評論   

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

    # re: Spring2.5 訪問 Session 屬性的四種策略  回復  更多評論   

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

    # re: Spring2.5 訪問 Session 屬性的四種策略  回復  更多評論   

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

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


    網站導航:
     
    主站蜘蛛池模板: 国产成人精品日本亚洲网址| 91麻豆国产免费观看| 亚洲av午夜精品无码专区| 中国人免费观看高清在线观看二区| 亚洲午夜福利精品久久 | 午夜不卡久久精品无码免费| 国产偷国产偷亚洲清高动态图| 午夜无码A级毛片免费视频| 222www免费视频| 亚洲AV无码码潮喷在线观看| 久久精品免费观看| 亚洲VA中文字幕无码一二三区| 一二三四在线观看免费高清中文在线观看| 亚洲AV综合色区无码二区爱AV| 亚洲中文字幕无码久久精品1| 免费一级国产生活片| 99re在线这里只有精品免费| 亚洲精品视频在线| 成人女人A级毛片免费软件| 一级女性全黄生活片免费看| 国产亚洲精久久久久久无码AV | 国产黄在线观看免费观看不卡| 亚洲成年轻人电影网站www | a级日本高清免费看| 久久久高清日本道免费观看| 亚洲免费一级视频| 日韩黄色免费观看| 亚洲精品99久久久久中文字幕| 亚洲精品无码久久久久| 久久青青草原亚洲av无码app| 亚洲AV无码一区二区三区在线 | 伊伊人成亚洲综合人网7777| 中文字幕不卡高清免费| 免费网站观看WWW在线观看| 亚洲成A人片在线播放器| 亚洲女同成人AⅤ人片在线观看| 久久精品成人免费观看| 亚洲综合精品伊人久久| 国产美女亚洲精品久久久综合| 1000部啪啪未满十八勿入免费| 免费看美女午夜大片|