<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 :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

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

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

    WEB 應(yīng)用通常會(huì)引入 Session,用來在服務(wù)端和客戶端之間保存一系列動(dòng)作/消息的狀態(tài),比如網(wǎng)上購(gòu)物維護(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,再?gòu)腍ttpSession中得到狀態(tài)信息。

    還是回到購(gòu)物車的例子,假設(shè)在 controller 某個(gè)方法(本文簡(jiǎn)稱為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)生依賴。這樣如果我們要測(cè)試 action,我們就必須針對(duì) HttpServletRequest、HttpServletResponse 和 HttpSession類提供 mock 或者 stub 實(shí)現(xiàn)。當(dāng)然現(xiàn)在已經(jīng)有很多開源的 Servlet 測(cè)試框架幫助我們減輕這個(gè)痛苦,包括 Spring 就自帶了對(duì)了這些類的 stub 實(shí)現(xiàn),但那還是太冗繁瑣碎了。那有沒有比較好的辦法來讓我們的 controller 更 POJO,讓我們的 action 脫離 Servlet API 依賴,更有益于測(cè)試和復(fù)用呢?我們來看看在 Spring2.5 中訪問 Session 屬性的幾種策略,并將在本博的后續(xù)文章繼續(xù)探究解決方案選擇后面的深層含義。


    (一)通過方法參數(shù)傳入HttpServletRequest對(duì)象或者HttpSession對(duì)象
    筆者的前一篇文章已經(jīng)簡(jiǎn)單介紹了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ì)象中的屬性,直接而簡(jiǎn)單。
    2. action 需要訪問哪些具體的 Session 屬性,是由自己控制的,真正精確到 Session 中的每個(gè)特定屬性。
    不足:
    1. 程序?qū)?Servlet API 產(chǎn)生依賴。雖然 controller 類已經(jīng)不需要從 HttpServlet 繼承,但仍需要 Servlet API 才能完成編譯運(yùn)行,乃至測(cè)試。
    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ì)象通過外界注入,測(cè)試時(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)來,在測(cè)試時(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ì)象。但是我們有沒有更簡(jiǎn)單更 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ì)引起一些爭(zhēng)議。

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


    本文演示了 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)較長(zhǎ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)航:
     
    主站蜘蛛池模板: 亚洲AV成人精品网站在线播放| 亚洲乳大丰满中文字幕| 亚洲情侣偷拍精品| 亚洲日产无码中文字幕| 久久精品国产亚洲AV无码麻豆 | 羞羞视频免费网站含羞草| 乱淫片免费影院观看| 久久国产精品免费专区| 免费无码AV片在线观看软件| 免费国产怡红院在线观看| 亚洲色中文字幕无码AV| 亚洲一卡二卡三卡| 免费无码AV一区二区| 久久精品中文字幕免费| 成熟女人牲交片免费观看视频| 亚洲AV无码之日韩精品| 亚洲韩国—中文字幕| 亚洲AV无码一区二区三区牲色| 男女一边桶一边摸一边脱视频免费| 最近2019中文字幕免费大全5| 午夜一级免费视频| 国产A在亚洲线播放| 亚洲一本到无码av中文字幕| 国产VA免费精品高清在线| 国产成人免费高清激情明星| 夜色阁亚洲一区二区三区| 婷婷亚洲久悠悠色悠在线播放| 亚洲成在人线aⅴ免费毛片| 在线观看片免费人成视频播放| 无码人妻一区二区三区免费| 亚洲中文字幕无码专区| 亚洲av成人综合网| 久久免费国产精品| 四虎免费在线观看| 久久青草亚洲AV无码麻豆| 亚洲aⅴ天堂av天堂无码麻豆| 午夜视频在线免费观看| 免费观看四虎精品国产永久| 亚洲第一香蕉视频| 国产日韩AV免费无码一区二区| 永久在线毛片免费观看|