Spring MVC中Session的正確用法之我見(轉)
Spring MVC是個非常優秀的框架,其優秀之處繼承自Spring本身依賴注入(Dependency Injection)的強大的模組化和可配置性,其設計處處透露著易用性、可複用性與易整合性。優良的設計模式遍及各處,使得其框架雖然學習曲線陡峭,但一旦掌握則欲罷不能。初學者並不需要過多瞭解框架的實現原理,隨便搜一下如何使用“基於註解的controller”就能很快上手,而一些書籍諸如“spring in action”也給上手提供了非常優良的選擇。
網上的帖子多如牛毛,中文的快速上手,英文的深入淺出。這樣看來,Spring的學習簡直是一個輕鬆愉快的過程。
但是!!
關於Spring中session的使用,大部分資料都諱莫如深。也許這個問題太過容易推斷出?大部分資料都沒有包括我下面所將要陳述的內容。關於Spring中session的正確使用方法,
於是我開始從龐大的網路資源和書籍中搜索關於Session的正確用法及Spring MVC處理Session的機制,其中講得最深入而且清楚的算是這一篇。從上文的內容,及我所查閱的比如官方文件這種資料中,我可以大約推斷出幾個要點:
1. Spring框架會在呼叫完Controller之後、渲染View之前檢查Model的資訊,並把@SessionAttributes()註釋標明的屬性加入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中
因為想從2得到3,這個結論就顯得比較自然。那麼事實上是不是如此呢?可以做一個小實驗。仿效我所引用的那篇文章,我寫了如下程式碼:
@Controller @RequestMapping("/user") @SessionAttributes("userId") public class UserController { @RequestMapping(value="/login", method=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 "/test"; } @RequestMapping(value="/check", method=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 "/test"; } }
而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中是否已經註冊了這個屬性
Done,初學而已,隨便談談,歡迎討論。