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

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

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

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


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

    action 的代碼如下:

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

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

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

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

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

    (三)通過方法參數(shù)處理類(MethodArgumentResolver)在方法級別注入User對象
    正如前面所看到的,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 級別提供了方法參數(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. 真正做到了按需分配,只在真正需要對象的位置注入具體的對象,減少其他地方對該對象的依賴。
    3. 其他人能很容易地從 action 的參數(shù)列表得知 action 所需要的依賴,API 更清晰易懂。
    4. 對于很多 action 需要的某一類參數(shù),可以在唯一的設(shè)置點(diǎn)用很方便一致的方式進(jìn)行注入。
    不足:
    1. 對象依賴注入是針對所有 action, 注入粒度還是較粗。不能做到具體 action 訪問具體的 Session 屬性

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

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

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

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


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


    評論

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

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

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

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

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

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

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

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

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


    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 亚洲一卡2卡3卡4卡乱码 在线| eeuss影院ss奇兵免费com| 日韩精品无码免费专区午夜不卡| 欧美男同gv免费网站观看| 国产亚洲成av片在线观看| 亚洲AV噜噜一区二区三区| 16女性下面扒开无遮挡免费| 亚洲综合精品网站| 一本天堂ⅴ无码亚洲道久久| 无码国产精品一区二区免费模式| 亚洲av日韩片在线观看| 狠狠色伊人亚洲综合网站色| 免费国产黄网站在线观看| 亚洲日本一区二区三区在线不卡| 亚洲AV无码成人专区| 久久永久免费人妻精品| 爱情岛论坛网亚洲品质自拍| 亚洲精品无码av中文字幕| 亚洲免费在线观看视频| 亚洲乱码国产一区三区| 一区二区三区精品高清视频免费在线播放| 免费人成视频在线| 亚洲视频免费在线看| 成人影片一区免费观看| 亚洲精品专区在线观看| 色天使亚洲综合一区二区| 在线天堂免费观看.WWW| 亚洲精品成人图区| 午夜无码A级毛片免费视频| 精品亚洲成α人无码成α在线观看| 青青青亚洲精品国产| 四虎成人免费大片在线| 亚洲最大黄色网站| 日本免费人成视频在线观看| 亚洲国产精品无码成人片久久| 中文字幕乱理片免费完整的| 亚洲综合久久夜AV | 国产99久久久国产精免费| 亚洲无码视频在线| 特级毛片爽www免费版| 免费国产成人高清视频网站|