HandlerAdapter做爲SpringMVC中最複雜的一部分, 在真正的分析其源碼以前, 咱們有必要對一些前置知識點進行了 解, 不然在看源碼的過程當中會遇到盲點.....java
先說說這個註解的做用吧, 被@ModelAttribute標註的方法, 在Controller方法執行前, 都會執行一次該註解標註的
方法, 下面先來看一個例子:
@Controller
public class TestController {
@PostMapping( "/test" )
public String test() {
System.out.println( request );
return "index";
}
@ModelAttribute
public void testModelAttribute1 () {
System.out.println( "Hello" );
}
}
分析:
當咱們每次請求這個/test的時候, 在SpringMVC調用test方法以前, 都會調用一次testModelAttribute1方法,
固然, 這僅僅知識@ModelAttribute註解的一個小功能而已
@ModelAttribute:
對Model的屬性進行定義、操做, Model是一個貫穿整個SpringMVC的東西, 相信你們接觸該框架時必然少不了對
Model的學習, 其實Model的本質就是一個Map而已, 在咱們進行參數綁定到Controller方法的時候, 數據的來源
有好幾個地方, 好比Request請求參數, session的Atrribute中, 以及這個Model(是一個Map),
@ModelAttribute的註解的做用有兩個, 看成用在方法上的時候, 方法的返回值就會被放入
到這個所謂的Model中, 若是指定了@ModelAttribute註解的value屬性, 那麼該value屬性就做爲Model中的key
返回值做爲值, 若是沒有指定, 那麼就會按照必定的生成規則生成key, 看成用在方法參數上的時候, 表示從
Model中取出參數並賦值給該參數, 若是指定了@ModelAttribute註解的value屬性, 那麼就從Model中取得對應
的key所在的value, 下面咱們舉個例子來講明:
例子:
@GetMapping( "/test" )
public String test (@ModelAttribute( "username" ) String username, @ModelAttribute List<String> list, Model model) {
System.out.println( model );
System.out.println( username );
System.out.println( list );
return "index";
}
@ModelAttribute( "username" )
public String testModelAttribute1 () {
return "zhangsan";
}
@ModelAttribute
public List<String> testModelAttribute2 () {
return Arrays.asList( "a", "b" );
}
輸出:
{username=zhangsan, stringList=[a, b]}
zhangsan
[a, b]
分析:
結合前面所說的, 被@ModelAttribute註解標註的方法, 在每次執行該Controller的方法(即test方法)前都會
執行一次, 而且將返回值放入到Model對象中, 而咱們的hanler(以後請求映射方法如test都稱爲handler)在被
執行的時候, @ModelAtrribute方法標註的參數就會從Model中獲取, 能夠看到, 輸出Model的時候, 裏面是保存
了兩個值的, 咱們返回的集合其對應的key即stringList, 在沒有主動指定該註解的value的狀況下, key是按照
必定規則生成的, 當咱們參數綁定的時候, 若是沒有主動指定該註解的值, 也會自動從Model中取出一個最合適的
小小的總結:
@ModelAttribute的做用有兩個, 被該註解標註的方法在每次其所在的Controller的hanler執行前都會執行一次
而且將返回值放入到Model中, 被該註解標註的hanler的屬性, 在獲取值的時候就會從Model中獲取, 那如何作到
僅僅從Model中獲取呢?這個就跟HandlerAdapter中的參數解析器ArgumentResolver有關係了, 若是是從下一篇
文章過來的同窗, 筆者應該是先把參數解析器說明了才引入ModelAttribute的, 因此這裏應該不會迷惑什麼是參
數解析器了, 固然, 不懂也不要緊, 這裏僅僅須要瞭解這個註解的做用就行了, 其餘的看到後面天然會豁然開朗
複製代碼
@SessionAttributes的做用其實跟上面咱們所說的Model有點關係, 咱們知道, Model實際上是一個Map,
@ModelAttribute的做用就是往這個Map中放入key-value或者根據key從這個Map中取得值, 可是貌似只能做用在同一
個請求中, 由於@ModelAttribute中的Model僅僅是會在一個請求中有效而已, 若是想要在多個請求中實現參數的傳遞
你們應該能夠想到, session就可以實現了, session中保存的屬性是一次會話中會持續有效的, 因此若是有多個請求
的話, 就能利用session來傳遞數據, 而@SessionAttribute的做用就是往session中放入數據,
SessionAttributes這個註解只能做用在一個類上, 他的做用其實很簡單, 當請求結束的時候, 會將Model這個map中
的數據所有的保存到session中, 而在請求來的時候, Model中的數據除了會從@ModelAttribute中放入外, 還會從
session中取出@SessionAttributes規定的數據, 可能這樣說比較抽象, 下面咱們利用兩個handler來證實一下:
@Controller
@SessionAttributes( names={"username"} )
public class TestController1 {
@GetMapping( "/request1" )
public String request1 (Model model) {
model.addAttribute( "username", "lisi" );
return "index";
}
@GetMapping( "/request2" )
public String request2 (@ModelAttribute( "username" ) String username) {
System.out.println( username );
return "index";
}
}
分析:
先請求/request1, 再請求/request2, 會發現, 第二次請求打印了lisi, 其實很簡單, 當咱們執行第一次請求
即/request1的時候, 往Model中放入了username->lisi, 因爲@SessionAttributes中指定了name爲username
因此在hanlder執行完畢時, 就會將Model中key爲username的鍵值對放入到session中, 當第二次請求過來的時
候, 除了會將@ModelAttribute做用在方法的返回值放入Model外, 還會從session中將@SessionAttributes指
定的key找到value, 而後放入到Model中, 當第二次請求執行到handler中時, Model中已經存在username這個
鍵值對了
應用場景:
須要注意的是, @SessionAtrributes的做用域但是一個類, 即僅僅會將該類中的請求中Model的指定數據放入
session中, 同時, 也僅僅會從session中取出該類定義的@SessionAttributes中規定的參數而已, 而且能夠看
到, 因爲保存到session中, 因此能夠在多個請求中傳遞參數, 而咱們重定向redirect是無法傳遞參數的, 能夠
經過這個方式來實現參數的傳遞, 下面來看一個例子
@Controller
@SessionAttributes( names={"username"} )
public class TestController1 {
@GetMapping( "/request1" )
public String request1 (Model model) {
model.addAttribute( "username", "lisi" );
return "redirect:request2";
}
@GetMapping( "/request2" )
public String request2 (String username, SessionStatus sessionStatus) {
System.out.println( username );
sessionStatus.setComplete();
return "index";
}
}
分析:
和前面的例子相似, 不一樣的是增長了一個SessionStatus類型的參數, 這個參數SpringMVC會自動幫咱們注入,
其實仍是利用了參數解析器, 能夠發現, /request1往model中放入了username->lisi, 同時
@SessionAttributes指定了username會放入session中, 因而重定向到/request2時候, username的值就能從
model中得到, 可是咱們調用sessionStatus.setComplete方法, 這個方法的意義是清空@SessionAttributes
中保存到session中的數據
到此, @SessionAttributes的做用就說完了, 這樣咱們就能夠在以前的源碼中看到, SpringMVC是如何完成這些操做
的
複製代碼
在上篇文章中, 咱們瞭解到了, BeanWrapper對屬性的設置原理實際上是依賴於Java內省機制的PropertyDescriptor的
同時其提供了自定義解析器的功能, 經過registerCustomEditor方法往BeanWrapper中注入PropertyEditor的實現
類來完成的, 一般狀況下是字符串轉對象, 此時會採用PropertyEditorSupport這個類, 重寫其setAsText或者
getAsText方法便可, 從而完成了轉換, 本節講解的DataBinder跟BeanWrapper相似, 由於DataBinder所作的任何事
情都是基於BeanWrapper來完成的, 先來看一個例子吧:
public static void main(String[] args) {
Customer customer = new Customer();
DataBinder binder = new DataBinder( customer );
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add( "birth", new Date() );
binder.bind( propertyValues );
System.out.println( customer );
}
分析:
能夠看到, 將MutablePropertyValues中的屬性綁定到對象Customer中, 是否是跟BeanWrapper有點相似呢?因
爲Customer的birth屬性是Date類型的, 因此直接綁定沒有關係, 那麼假設我提供的是字符串就綁定不了, 因而
跟BeanWrapper相似, 能夠經過registerCustomEditor方法來註冊一個自定義的解析器, 好比字符串解析器:
public static void main(String[] args) {
Customer customer = new Customer();
DataBinder binder = new DataBinder( customer );
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add( "birth", "2020-06-26 14:33:22" );
binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws IllegalArgumentException {
SimpleDateFormat dateFormat = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
setValue( dateFormat.parse( text ) );
}
});
binder.bind( propertyValues );
System.out.println( customer );
}
輸出結果跟上面的同樣, 可是咱們傳入的確是字符串.........這個就是DataBinder的做用, 對一個對象提供
PropertyValues來進行綁定, 同時能夠提供驗證器Validator, 這個就不進行分析了, 有興趣能夠研究下
複製代碼
DataBinder是Spring框架自己自帶的數據綁定工具, 底層用的是BeanWrapper, 而BeanWrapper的底層又是
PropertyDesciptor以及PropertyEdtitor, 因而在SpringMVC中, 爲了更加方便Request中數據到對象的綁定, 在
基於DataBinder上又進一步進行了實現, 以下圖所示, WebDataBinder繼承了DataBinder, 在其源碼中, 其實就是
增長了一些判斷而已,. 有一個doBind方法, 是WebDataBinder進行綁定的入口, 在該方法中, 作了一些判斷, 最後
仍是調用了父類DataBinder的doBind方法, 再日後就是ServletRequestDataBinder以及
ExtendedServletRequestDataBinder, 這兩個binder完成的任務就是將request對象中的param所有取出來並封裝成
PropertyVlues, 而後調用父類的doBind的方法的時候傳入從而進行綁定, 這裏就不進行深刻分析了, 你們只須要清楚
當咱們在利用HttpServletRequest對象的getParameterValues獲取到值後, 對handler進行綁定其實就是建立一個
ExtendedServletRequestDataBinder, 而後將handler中的參數對象以及HttpServletRequest對象傳入進入就能夠
完成綁定了, 下面是僞代碼:
@RequestMapping( "/test" )
public String test (Customer customer) {
System.out.println( customer );
return "index"
}
當在執行參數綁定的時候, 僞代碼以下:
HttpServletRequest request = getHttpServletRequest();
Customer customer = new Customer();
ServletRequestDataBinder binder = new ServletRequestDataBinder(customer);
binder.bind(request);
複製代碼
先來看一個例子吧:
@Controller
public class TestController1 {
@InitBinder
public void initBinder (WebDataBinder webDataBinder) {
webDataBinder.registerCustomEditor( Date.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws IllegalArgumentException {
SimpleDateFormat dateFormat = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
try {
setValue( dateFormat.parse( text ) );
} catch (ParseException e) {
e.printStackTrace();
}
}
});
}
@GetMapping( "/request1" )
public String request1 (Date date) {
System.out.println( date );
return "index";
}
}
分析:
咱們請求的url: 127.0.0.1/request1?date=1997-11-23 14:00:00
當請求過來的時候, 正常狀況下是無法將"1997-11-23 14:00:00"轉爲Date對象的, 可是咱們手動往
WebDataBinder中註冊了一個PropertyEditor, 同時指定類型爲Date類型, 因此當其綁定Date類型的時候就會
利用這個PropertyEditor進行轉換了
那麼@InitBinder的意思就很明顯了, 在每次請求執行前都會執行@InitBinder對應的方法, 咱們能夠經過這種
方式手動註冊本身須要的參數解析器, 從總體來看, 貌似跟@ModelAttribute的做用時機是差很少的, 都是請求
執行前會執行一次, 而@ModelAttribute做用在方法上的時候, 會將返回值放入Model中, 而@InitBinder則是
容許咱們初始化WebDataBinder, 以後的源碼分析也能夠清晰的看到這兩點
複製代碼
在上面的分析中, 咱們主要引入了三個註解, 分別是@ModelAttribute、@SessionAttributes、@InitBinder, 這
三個註解中, @ModelAttribute註解能做用在方法和handler的方法參數上, @SessionAttributes只能做用在類上,
而且是以類爲做用域的, 不一樣類以前不能共享, @InitBinder只能做用在方法上, 以上三個註解都是對本身所在的
Controller生效, 那麼問題來了, 若是有多個Controller, 每一個Controller都須要執行同樣的@ModelAttribute
和@InitBinder標註的方法, 那麼這兩個註解標註的方法就會被複制多份並放到不一樣的類中, 因而爲了防止這樣的狀況
出現, SpringMVC提供了一個全局的方式, 只須要建立一個@ControllerAdvice註解標註的類, 而後將這兩個註解標註
的方法放到該類中, 就實現了全局的操做了, 而不用每一個類放置一份, 同時, @ControllerAdvice還提供了卓多屬性,
使得咱們可以靈活的配置做用的包、類等
複製代碼