1. 程式人生 > >Spring MVC 中Session的用法

Spring MVC 中Session的用法

Spring MVC是個非常優秀的框架,其優秀之處繼承自Spring本身依賴注入(Dependency Injection)的強大的模組化和可配置性,其設計處處透露著易用性、可複用性與易整合性。優良的設計模式遍及各處,使得其框架雖然學習曲線陡峭,但一旦掌握則欲罷不能。初學者並不需要過多瞭解框架的實現原理,隨便搜一下如何使用“基於註解的controller”就能很快上手,而一些書籍諸如“spring in action”也給上手提供了非常優良的選擇。

網上的帖子多如牛毛,中文的快速上手,英文的深入淺出。這樣看來,Spring的學習簡直是一個輕鬆愉快的過程。

但是!!

關於Spring中session的使用,大部分資料都諱莫如深。也許這個問題太過容易推斷出?大部分資料都沒有包括我下面所將要陳述的內容。關於Spring中session的正確使用方法,

這裡甚至建議直接使用HttpSession。但這種方法顯然違背了Spring “technology agnostic” (這個名詞我理解意思就是無論你是在什麼具體的應用中使用類似的控制邏輯,servlet、一個本地JVM程式或者其他,你的Controller都可以得到複用)的初衷。

於是我開始從龐大的網路資源和書籍中搜索關於Session的正確用法及Spring MVC處理Session的機制,其中講得最深入而且清楚的算是這一篇。從上文的內容,及我所查閱的比如官方文件這種資料中,我可以大約推斷出幾個要點:

1. Spring框架會在呼叫完Controller之後、渲染View之前檢查Model的資訊,並把@SessionAttributes()註釋標明的屬性加入session中(@SessionAttributes()註釋標明的屬性也就是在Model中存在的屬性,如果Model中不存在該屬性,session也不會注入成功。

2. @ModelAttribute在宣告Controller的引數的時候,可以用來表明此引數引用某個存在在Model中的物件,如果這個物件已經存在於Model中的話(Model可以在呼叫Controller之前就已經儲存有資料,這應該不僅僅因為HandlerInterceptor或者@ModelAttribute標記的方法已經顯式的將一些物件加入到了Model物件中,也因為Spring會預設將一些物件加入到Model中,這一點很重要)。

3. 如果Session中已經存在某個物件,那麼可以直接使用ModelAttribute宣告Controller的引數,在Controller中可以直接使用它。

其中1很明確,我提到的那篇文章主要就在說明這一點。而從2和3我們也許可以大膽地推出一個結論:

    Spring會在呼叫Controller之前將session中的物件填入Model中(前提是Model中有session中對應的屬性物件)

因為想從2得到3,這個結論就顯得比較自然。那麼事實上是不是如此呢?可以做一個小實驗。仿效我所引用的那篇文章,我寫了如下程式碼:

package com.watson.common.session;

import java.util.Enumeration;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes;

@Controller
@RequestMapping("/user")
//@SessionAttributes("userId")註解會將Model中的userId屬性自動加入到Session中,並且會在下次訪問時自動將Session中userId的值賦予Model.
@SessionAttributes("userId")
public class UserController {
    
    @RequestMapping(value="/login", method=RequestMethod.GET)
    public String login (
            int id, Model model, HttpServletRequest request, HttpSession session) {
        
        model.addAttribute("userId", id);
        
        System.out.println("");
        System.out.println("");
        System.out.println("inside login");
        
        System.out.println("");
        System.out.println("--- Model data ---");
        Map modelMap = model.asMap();
        for (Object modelKey : modelMap.keySet()) {
            Object modelValue = modelMap.get(modelKey);
            System.out.println(modelKey + " -- " + modelValue);
        }

        System.out.println("");
        System.out.println("*** Session data ***");
        Enumeration<String> e = session.getAttributeNames();
        while (e.hasMoreElements()) {
            String s = e.nextElement();
            System.out.println(s + " == " + session.getAttribute(s));
        }

        return "/session/test";
    }
    
    @RequestMapping(value="/check", method=RequestMethod.GET)
    public String check (
            Model model, HttpServletRequest request, HttpSession session) {
        
        System.out.println("");
        System.out.println("");
        System.out.println("inside check");
        
        System.out.println("");
        System.out.println("--- Model data ---");
        Map modelMap = model.asMap();
        for (Object modelKey : modelMap.keySet()) {
            Object modelValue = modelMap.get(modelKey);
            System.out.println(modelKey + " -- " + modelValue);
        }
        
        System.out.println("");
        System.out.println("*** Session data ***");
        Enumeration<String> e = session.getAttributeNames();
        while (e.hasMoreElements()) {
            String s = e.nextElement();
            System.out.println(s + " == " + session.getAttribute(s));
        }

        return "/session/test";
    }
}

test.jsp程式碼如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<html>

<body>
<h3>Session Scope (key==values)</h3>
<%
  java.util.Enumeration<String> sessEnum = request.getSession()
	.getAttributeNames();
  while (sessEnum.hasMoreElements()) {
	String s = sessEnum.nextElement();
	out.print(s);
	out.println("==" + request.getSession().getAttribute(s));
%><br />
<%
  }
%>
</body>
</html>


而test.jsp的作用就是把Session中的物件打印出來。

呼叫的順序是,在首先保證Session為空的情況下,先後輸入以下連結:

頁面的顯示結果分別為:

1

2

3

而Tomcat的輸出結果為:

inside check

--- Model data ---

*** Session data ***


inside login

--- Model data ---
userId -- 1

*** Session data ***


inside check

--- Model data ---
userId -- 1

*** Session data ***
userId == 1

結果如我所料。首先Session中並沒有userId屬性,在某個Controller加入了它之後,隨後的Controller中的Model會自動加入已經存在於Session的物件。雖然確實有很多很多帖子提到了@SessionAttributes並不是使用session的正確方法,但是如實驗所得,使用它使得最終屬性都加入到了HttpSession物件中,夫復何求?(這裡也許需要更多的討論,我倒希望能有什麼更值得信服的說法讓我乖乖用回HttpSession)。那麼,在Spring中使用Session的一個相對比較“technology agnostic”的方法就是:

1 使用@SessionAttributes提示框架哪些屬性需要存在Session中

2 在某些Controller中將這些屬性加入到Model中

3 在另外一些Controler中直接使用這些屬性

4 在其他Controller中判斷Model中是否存在相應屬性,以確定Session中是否已經註冊了這個屬性