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中
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為空的情況下,先后輸入以下鏈接:
http://localhost:8080/XX/user/check
http://localhost:8080/XX/user/login?id=1
http://localhost:8080/XX/user/check
頁面的顯示結果分別為:
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,初學而已,隨便談談,歡迎討論。