摘要:和一起使用參照博文從原理層面掌握的使用一起學。至于具體原因,可以移步這里輔助理解從原理層面掌握的使用核心原理篇一起學再看下面的變種例子重要訪問。
每篇一句
每個人都應該想清楚這個問題:你是祖師爺賞飯吃的,還是靠老天爺賞飯吃的前言
上篇文章 描繪了@ModelAttribute的核心原理,這篇聚焦在場景使用上,演示@ModelAttribute在不同場景下的使用,以及注意事項(當然有些關聯的原理也會涉及)。
為了進行Demo演示,首先得再次明確一下@ModelAttribute的作用。
@ModelAttribute的作用雖然說你可能已經看過了核心原理篇,但還是可能會缺乏一些上層概念的總結。下面我以我的理解,總結一下 @ModelAttribute這個注解的作用,主要分為如下三個方面:
綁定請求參數到命令對象(入參對象):放在控制器方法的入參上時,用于將多個請求參數綁定到一個命令對象,從而簡化綁定流程,而且自動暴露為模型數據用于視圖頁面展示時使用;
暴露表單引用對象為模型數據:放在處理器的一般方法(非功能處理方法,也就是沒有@RequestMapping標注的方法)上時,是為表單準備要展示的表單引用數據對象:如注冊時需要選擇的所在城市等靜態信息。它在執行功能處理方法(@RequestMapping 注解的方法)之前,自動添加到模型對象中,用于視圖頁面展示時使用;
暴露@RequestMapping方法返回值為模型數據:放在功能處理方法的返回值上時,是暴露功能處理方法的返回值為模型數據,用于視圖頁面展示時使用。
下面針對這些使用場景,分別給出Demo用例,供以大家在實際使用中參考。
因為在原理篇里講過,自動創建模型對象的時候不僅僅可以使用空的構造函數,還可以使用java.beans.ConstructorProperties這個注解,因此有必須先把它介紹一波:
官方解釋:構造函數上的注釋,顯示該構造函數的參數如何對應于構造對象的getter方法。
// @since 1.6 @Documented @Target(CONSTRUCTOR) // 只能使用在構造器上 @Retention(RUNTIME) public @interface ConstructorProperties { String[] value(); }
如下例子:
@Getter @Setter public class Person { private String name; private Integer age; // 標注注解 @ConstructorProperties({"name", "age"}) public Person(String myName, Integer myAge) { this.name = myName; this.age = myAge; } }
這里注解上的name、age的意思是對應著Person這個JavaBean的getName()和getAge()方法。
它表示:構造器的第一個參數可以用getName()檢索,第二個參數可以用getAge()檢索,由于方法/構造器的形參名在運行期就是不可見了,所以使用該注解可以達到這個效果。
此注解它的意義何在???
其實說實話,在現在去xml,完全注解驅動的時代它的意義已經不大了。它使用得比較多的場景是之前像使用xml配置Bean這樣:
這樣
java.beans中還提供了一個注解java.beans.Transient(1.7以后提供的):指定該屬性或字段不是永久的。 它用于注釋實體類,映射超類或可嵌入類的屬性或字段。(可以標注在屬性上和get方法上)Demo Show 標注在非功能方法上
@Getter @Setter @ToString public class Person { private String name; private Integer age; public Person() { } public Person(String myName, int myAge) { this.name = myName; this.age = myAge; } } @RestController @RequestMapping public class HelloController { @ModelAttribute("myPersonAttr") public Person personModelAttr() { return new Person("非功能方法", 50); } @GetMapping("/testModelAttr") public void testModelAttr(Person person, ModelMap modelMap) { //System.out.println(modelMap.get("person")); // 若上面注解沒有指定value值,就是類名首字母小寫 System.out.println(modelMap.get("myPersonAttr")); } }
訪問:/testModelAttr?name=wo&age=10。打印輸出:
Person(name=wo, age=10) Person(name=非功能方法, age=50)
可以看到入參的Person對象即使沒有標注@ModelAttribute也是能夠正常被封裝進值的(并且還放進了ModelMap里)。
因為沒有注解也會使用空構造創建一個Person對象,再使用ServletRequestDataBinder.bind(ServletRequest request)完成數據綁定(當然還可以@Valid校驗)
有如下細節需要注意:
1、Person即使沒有空構造,借助@ConstructorProperties也能完成自動封裝
// Person只有如下一個構造函數 @ConstructorProperties({"name", "age"}) public Person(String myName, int myAge) { this.name = myName; this.age = myAge; }
打印的結果完全同上。
2、即使上面@ConstructorProperties的name寫成了myName,結果依舊正常封裝。因為只要沒有校驗bindingResult == null的時候,仍舊還會執行ServletRequestDataBinder.bind(ServletRequest request)再封裝一次的。除非加了@Valid校驗,那就只會使用@ConstructorProperties封裝一次,不會二次bind了~(因為Spring認為你已經@Valid過了,那就不要在湊進去了)
3、即使上面構造器上沒有標注@ConstructorProperties注解,也依舊是沒有問題的。原因:BeanUtils.instantiateClass(ctor, args)創建對象時最多args是[null,null]唄,也不會報錯嘛(so需要注意:如果你是入參是基本類型int那就報錯啦~~)
4、雖然說@ModelAttribute寫不寫效果一樣。但是若寫成這樣@ModelAttribute("myPersonAttr") Person person,也就是指定為上面一樣的value值,那打印的就是下面:
Person(name=wo, age=10) Person(name=wo, age=10)
至于原因,就不用再解釋了(參考原理篇)。
==另外還需要知道的是:@ModelAttribute標注在本方法上只會對本控制器有效。但若你使用在@ControllerAdvice組件上,它將是全局的。(當然可以指定basePackages來限制它的作用范圍~)==
標注在功能方法(返回值)上形如這樣:
@GetMapping("/testModelAttr") public @ModelAttribute Person testModelAttr(Person person, ModelMap modelMap) { ... }
這塊不用給具體的示例,因為比較簡單:把方法的返回值放入模型中。(注意void、null這些返回值是不會放進去的~)
標注在方法的入參上該使用方式應該是我們使用得最多的方式了,雖然原理復雜,但對使用者來說還是很簡單的,略。
和@RequestAttribute/@SessionAttribute一起使用參照博文:從原理層面掌握@RequestAttribute、@SessionAttribute的使用【一起學Spring MVC】。它倆合作使用是很順暢的,一般不會有什么問題,也沒有什么主意事項
和@SessionAttributes一起使用@ModelAttribute它本質上來說:允許我們在調用目標方法前操縱模型數據。@SessionAttributes它允許把Model數據(符合條件的)同步一份到Session里,方便多個請求之間傳遞數值。
下面通過一個使用案例來感受一把:
@RestController @RequestMapping @SessionAttributes(names = {"name", "age"}, types = Person.class) public class HelloController { @ModelAttribute public Person personModelAttr() { return new Person("非功能方法", 50); } @GetMapping("/testModelAttr") public void testModelAttr(HttpSession httpSession, ModelMap modelMap) { System.out.println(modelMap.get("person")); System.out.println(httpSession.getAttribute("person")); } }
為了看到@SessionAttributes的效果,我這里直接使用瀏覽器連續訪問兩次(同一個session)看效果:
第一次訪問打?。?/strong>
Person(name=非功能方法, age=50) null
第二次訪問打?。?/strong>
Person(name=非功能方法, age=50) Person(name=非功能方法, age=50)
可以看到@ModelAttribute結合@SessionAttributes就生效了。至于具體原因,可以移步這里輔助理解:從原理層面掌握@ModelAttribute的使用(核心原理篇)【一起學Spring MVC】
再看下面的變種例子(重要):
@RestController @RequestMapping @SessionAttributes(names = {"name", "age"}, types = Person.class) public class HelloController { @GetMapping("/testModelAttr") public void testModelAttr(@ModelAttribute Person person, HttpSession httpSession, ModelMap modelMap) { System.out.println(modelMap.get("person")); System.out.println(httpSession.getAttribute("person")); } }
訪問:/testModelAttr?name=wo&age=10。報錯了:
org.springframework.web.HttpSessionRequiredException: Expected session attribute "person" at org.springframework.web.method.annotation.ModelFactory.initModel(ModelFactory.java:117) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:869)
這個錯誤請務必重視:這是前面我特別強調的一個使用誤區,當你在@SessionAttributes和@ModelAttribute一起使用的時候,最容易犯的一個錯誤。
錯誤原因代碼如下:
public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod) throws Exception { MapsessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request); container.mergeAttributes(sessionAttributes); invokeModelAttributeMethods(request, container); // 合并完sesson的屬性,并且執行完成@ModelAttribute的方法后,會繼續去檢測 // findSessionAttributeArguments:標注有@ModelAttribute的入參 并且isHandlerSessionAttribute()是SessionAttributts能夠處理的類型的話 // 那就必須給與賦值~~~~ 注意是必須 for (String name : findSessionAttributeArguments(handlerMethod)) { // 如果model里不存在這個屬性(那就去sessionAttr里面找) // 這就是所謂的其實@ModelAttribute它是會深入到session里面去找的哦~~~不僅僅是request里 if (!container.containsAttribute(name)) { Object value = this.sessionAttributesHandler.retrieveAttribute(request, name); // 倘若session里都沒有找到,那就報錯嘍 // 注意:它并不會自己創建出一個新對象出來,然后自己填值,這就是區別。 // 至于Spring為什么這么設計 我覺得是值得思考一下子的 if (value == null) { throw new HttpSessionRequiredException("Expected session attribute "" + name + """, name); } container.addAttribute(name, value); } } }
注意,這里是initModel()的時候就報錯了喲,還沒到resolveArgument()呢。Spring這樣設計的意圖???我大膽猜測一下:控制器上標注了@SessionAttributes注解,如果你入參上還使用了@ModelAttribute,那么你肯定是希望得到綁定的,若找不到肯定是你的程序失誤有問題,所以給你拋出異常,顯示的告訴你要去排錯。
修改如下,本控制器上加上這個方法:
@ModelAttribute public Person personModelAttr() { return new Person("非功能方法", 50); }
(請注意觀察下面的幾次訪問以及對應的打印結果)
訪問:/testModelAttr
Person(name=非功能方法, age=50) null
再訪問:/testModelAttr
Person(name=非功能方法, age=50) Person(name=非功能方法, age=50)
訪問:/testModelAttr?name=wo&age=10
Person(name=wo, age=10) Person(name=wo, age=10)
注意:此時model和session里面的值都變了哦,變成了最新的的請求鏈接上的參數值(并且每次都會使用請求參數的值)。
訪問:/testModelAttr?age=11111
Person(name=wo, age=11111) Person(name=wo, age=11111)
可以看到是可以完成局部屬性修改的
再次訪問:/testModelAttr(無請求參數,相當于只執行非功能方法)
Person(name=fsx, age=18) Person(name=fsx, age=18)
可以看到這個時候model和session里的值已經不能再被非功能方法上的@ModelAttribute所改變了,這是一個重要的結論。
它的根本原理在這里:
public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod) throws Exception { ... invokeModelAttributeMethods(request, container); ... } private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container) throws Exception { while (!this.modelMethods.isEmpty()) { ... // 若model里已經存在此key 直接continue了 if (container.containsAttribute(ann.name())) { ... continue; } // 執行方法 Object returnValue = modelMethod.invokeForRequest(request, container); // 注意:這里只判斷了不為void,因此即使你的returnValue=null也是會進來的 if (!modelMethod.isVoid()){ ... // 也是只有屬性不存在 才會生效哦~~~~ if (!container.containsAttribute(returnValueName)) { container.addAttribute(returnValueName, returnValue); } } } }
因此最終對于@ModelAttribute和@SessionAttributes共同的使用的時候務必要注意的結論:已經添加進session的數據,在沒用使用SessionStatus清除過之前,@ModelAttribute標注的非功能方法的返回值并不會被再次更新進session內
所以@ModelAttribute標注的非功能方法有點初始值的意思哈~,當然你可以手動SessionStatus清楚后它又會生效了總結
任何技術最終都會落到使用上,本文主要是介紹了@ModelAttribute各種使用case的示例,同時也指出了它和@SessionAttributes一起使用的坑。
@ModelAttribute這個注解相對來說還是使用較為頻繁,并且功能強大,也是最近講的最為重要的一個注解,因此花的篇幅較多,希望對小伙伴們的實際工作中帶來幫助,帶來代碼之美~
從原理層面掌握HandlerMethod、InvocableHandlerMethod、ServletInvocableHandlerMethod的使用【一起學Spring MVC】
從原理層面掌握@SessionAttributes的使用【一起學Spring MVC】
從原理層面掌握@ModelAttribute的使用(核心原理篇)【一起學Spring MVC】
==The last:如果覺得本文對你有幫助,不妨點個贊唄。當然分享到你的朋友圈讓更多小伙伴看到也是被作者本人許可的~==
**若對技術內容感興趣可以加入wx群交流:Java高工、架構師3群。
若群二維碼失效,請加wx號:fsx641385712(或者掃描下方wx二維碼)。并且備注:"java入群" 字樣,會手動邀請入群**
若文章格式混亂或者圖片裂開,請點擊`:原文鏈接-原文鏈接-原文鏈接
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/76200.html
摘要:雖然它不是必須,但是它是個很好的輔助官方解釋首先看看官方的對它怎么說它將方法參數方法返回值綁定到的里面。解析注解標注的方法參數,并處理標注的方法返回值。 每篇一句 我們應該做一個:胸中有藍圖,腳底有計劃的人 前言 Spring MVC提供的基于注釋的編程模型,極大的簡化了web應用的開發,我們都是受益者。比如我們在@RestController標注的Controller控制器組件上用@...
摘要:同時另外一個目的是希望完全屏蔽掉源生,增加它的擴展性。本文我以為例進行講解,因為也是后推出的注解不管從使用和原理上都是一模一樣的。作用從中取對應的屬性值。 每篇一句 改我們就改得:取其精華,去其糟粕。否則木有意義 前言 如果說知道@SessionAttributes這個注解的人已經很少了,那么不需要統計我就可以確定的說:知道@RequestAttribute注解的更是少之又少。我覺得主...
摘要:見名之意,它是處理器,也就是解析這個注解的核心。管理通過標注了的特定會話屬性,存儲最終是委托了來實現。只會清楚注解放進去的,并不清除放進去的它的唯一實現類實現也簡單。在更新時,模型屬性與會話同步,如果缺少,還將添加屬性。 每篇一句 不是你當上了火影大家就認可你,而是大家都認可你才能當上火影 前言 該注解顧名思義,作用是將Model中的屬性同步到session會話當中,方便在下一次請求中...
摘要:并且,并且如果或者不為空不為且不為,將中斷處理直接返回不再渲染頁面對返回值的處理對返回值的處理是使用完成的對異步處理結果的處理使用示例文首說了,作為一個非公開,如果你要直接使用起來,還是稍微要費點勁的。 每篇一句 想當火影的人沒有近道可尋,當上火影的人同樣無路可退 前言 HandlerMethod它作為Spring MVC的非公開API,可能絕大多數小伙伴都對它比較陌生,但我相信你對它...
摘要:并且,并且如果或者不為空不為且不為,將中斷處理直接返回不再渲染頁面對返回值的處理對返回值的處理是使用完成的對異步處理結果的處理使用示例文首說了,作為一個非公開,如果你要直接使用起來,還是稍微要費點勁的。 每篇一句 想當火影的人沒有近道可尋,當上火影的人同樣無路可退 前言 HandlerMethod它作為Spring MVC的非公開API,可能絕大多數小伙伴都對它比較陌生,但我相信你對它...
閱讀 3249·2023-04-25 20:35
閱讀 3612·2019-08-30 15:54
閱讀 1991·2019-08-30 15:43
閱讀 2181·2019-08-29 15:14
閱讀 1888·2019-08-29 11:17
閱讀 3378·2019-08-26 13:36
閱讀 693·2019-08-26 10:15
閱讀 2832·2019-08-23 15:41