※@SessionAttribute
@Controller @RequestMapping("/ooo/xxx/") @SessionAttributes(value = {"xxx", "ooo"}, types = Date.class) public class XxxAction { @RequestMapping(value = "session/*.mvc") public String session(Model model) { model.addAttribute("xxx", 5); model.addAttribute("ooo", "o"); model.addAttribute("aaa", new Date(100,1,1)); model.addAttribute("bbb", new Date(101,12,12)); return "hello"; } }
※value 是 key 的名稱,type 表示某個類型的也會放入 session
※只能寫在 type 上,是針對整個 controller
※參數裡,Model、Map 都可以,其他沒試過
※測試
---------- index.jsp ---------- <a href="ooo/xxx/session/attr.mvc">session</a> ---------- hello.jsp ---------- request xxx:${requestScope.xxx}<br /> request ooo:${requestScope.ooo}<br /> request aaa:${requestScope.aaa}<br /> request bbb:${requestScope.bbb}<br /> <br /> session xxx:${sessionScope.xxx}<br /> session ooo:${sessionScope.ooo}<br /> session aaa:${sessionScope.aaa}<br /> session bbb:${sessionScope.bbb}<br />
※
※常見錯誤
@SessionAttributes("book") public class XxxAction { @RequestMapping(value = "session/*.mvc") public String session(Model model, Book book) { model.addAttribute("xxx", 5); // model.addAttribute("book", "b"); return "hello"; } }
※不管有沒有註解那一行,都會出「org.springframework.web.HttpSessionRequiredException: Session attribute 'book' required - not found in session」的錯
※因為參數 Book,有一個隱藏的 @ModelAttribute("book"),只要將裡面的值和 @SessionAttributes 的屬性值不一樣即可
※另外一個解決辦法就是使用下面要介紹的 @ModelAttribute,Map 裡放 book 也可以解決
※@ModelAttribute
※這個 annotation 是針對所有的 controller,每次都會先調用,所以要考慮清楚再寫,調用完後,才執行前端的方法※只有一個屬性 value,是 String,而不是 String[],所以是 1 對 1 的關係,可以在 controller 裡寫很多的 @ModelAttribute,都會在開始前被調用
※可以寫在方法上和參數裡
方法上的 value:沒寫為 void
參數裡的 value:沒寫為參數型態的駝峰命名
参數上:req 的參數依名稱注入到指定物件裡,而且還會將這個物件自動加入 Model
方法上:@RequestMapping 方法前執行,如有返回值,會自動將返回值加入到 Model
public class Book { private Integer id; private String name; private Integer price; // setter/getter... public Book() {} public Book(Integer id, String name, Integer price) { this.id = id; this.name = name; this.price = price; } }
※
※controller
@ModelAttribute public void getBook(@RequestParam(value="id", required=true) Integer id, Integer price, Model model) { model.addAttribute("book", new Book(id, "bruce", 1111)); // 模擬 DB } @RequestMapping("book") public String pojo(Book book) { System.out.println(book.getId()); System.out.println(book.getPrice()); System.out.println(book.getName()); return "hello"; }
※注意 model 的 key 必需是類名的駝峰命名法,如 ComicBook,那就要取名為 comicBook
※如果想自己取名,如 model.addAttribute("uuu", ...); 那 pojo 的參數也要宣告 @ModelAttribute("uuu"),也就是說,pojo 不寫 @ModelAttribute("uuu"),預設是@ModelAttribute("參數名的駝峰命名"),兩個對應就可以,沒對應不會報錯,只是替換沒成功
※測試
---------- index.jsp ---------- <a href="ooo/xxx/book.mvc?id=99&price=77">modelAttr</a> ---------- hello.jsp ---------- ${requestScope.book.id}<br /> ${requestScope.book.price}<br /> ${requestScope.book.name}<br />
※此例是模擬前端傳 id 到 DB 取出對應的值,但我只想改 price,name 並沒有給,所以是 null,但我不給的意思是想要和 DB 一樣,所以就寫了@ModelAttribute
DB 取出來的 price 是 1111,然後再到 pojo 方法,此時會將前端傳過來的值覆蓋 DB 的值
※@ModelAttribute 的屬性
只有一個屬性 value,它的值可以用在前端@ModelAttribute("abc") public Book getBook() { return new Book(88, "bruce", 100); } @RequestMapping("book") public String pojo(Book book) { return "hello"; }
※必須要 return 才行
※測試
---------- index.jsp ---------- <a href="ooo/xxx/book.mvc">modelAttr</a> ---------- hello.jsp ---------- ${abc.id}<br /> ${abc.price}<br /> ${abc.name}<br />
※ 好文章
※原理
在 Map 的地方下斷點,debug 調試後,HandlerMethodInvoker.java 有二個較重要的方法如下:一、invokeHandlerMethod
有兩個迴圈,這個方法主要在處理 annotation第一個迴圈:
如果有 @SessionAttributes 的 value 和 types 就將它們放入 ExtendedModelMap 裡,所以至少要執行過一次,瀏覽器才有
第二個迴圈:
@ModelAttribute 可以寫在方法上和參數裡,這個方法裡都是方法上的,有兩個重要變數
⑴ attrName:@ModelAttribute 的 value 屬性,沒有為 void,最後成為 ExtendedModelMap 的 key
⑵ attrValue:回傳的內容,沒回傳為 null,最後成為 ExtendedModelMap 的 value
1.resolveHandlerArguments 取得的是前端傳過來的資料,但 @ModelAttribute 裡的參數要有,不然是空,看方法二
2.如果 attrName 在 ExtendedModelMap 找到就下一個迴圈了,不會再繼續執行 3 之後的程式碼
3.回 controller 執行此迴圈的 @ModelAttribute 方法得到 attrValue
4.如果 @ModelAttribute 沒寫 value,那值就是 void
5.如果 ExtendedModelMap 沒有 attrName,就塞入
迴圈結束
再呼叫一次第二個迴圈裡的 1 方法,但是是針對 @RequestMapping 的,肯定只有一個,所以寫在迴圈外,回傳的是前端覆蓋 @ModelAttribute 方法裡的值
二、resolveHandlerArguments
迴圈裡還有迴圈,這個方法主要在處理方法的參數的 annotation外層迴圈:方法有幾個參數就跑幾次,整個方法除了 return 外,都是外層迴圈
內層迴圈:每一個參數有幾個 annotation 就跑幾次,只有以下 8 種 annotation 才會處理
1.RequestParam
2.RequestHeader
3.RequestBody
4.CookieValue
5.PathVariable
6.ModelAttribute
7.Value
8.Valid 開頭的,spring3 只有 Validated
內層迴圈結束
以下還是在外層迴圈內,內層迴圈外
1~6 的 annotation,每一個參數最多只能給一個,否則會報「Handler parameter annotations are exclusive choices - do not specify more than one such annotation on the same parameter: 」 + 方法名
如果參數沒有 1~6 的 annotation,還有三個判斷
第一個我沒看懂
第二個是判斷有 @Valid 的 value 屬性就將參數塞到回傳的變數 args 裡
如果不是1、2 就是這一個,又分成 6 個判斷
⑴ 參數型態是 Model 或 Map :是的話再判斷是不是 ExtendedModelMap
是就將 ExtendedModelMap 塞到回傳的變數 args 裡
不是就拋「Argument [參數型態] is of type Model or Map but is not assignable from the actual model. You may need to switch newer MVC infrastructure classes to use this argument.」
⑵是 SessionStatus 或其父類,將變數 sessionStatus 塞到 args 裡
⑶是 HttpEntity 或其父類,呼叫 resolveHttpEntityRequest 後回傳給 args
⑷是 Errors 或其父類,拋「Errors/BindingResult argument declared without preceding model attribute. Check your handler method signature!」
⑸基本類型和Wrapper、enum、CharSequence、Number、Date、URI、URL、Locale、Class,只要是其中一個,就將變數 paramName 置為空
⑹都不是變數attrName為空
最後的部分還有 6 個判斷,前 5 個都是針對 annotation 的 value 屬性,有就呼叫相對應的方法後,回傳到 args
第 6 個是 @ModelAttribute 沒寫在參數裡或者寫了 value 值才會進去,
主要就是取得 WebDataBinder 後,有個 getTarget 方法,塞到 args 裡
沒有留言:
張貼留言