Spring @ModelAttribute
正文开始之前,先介绍个东西,Spring能够自动将请求参数封装到对应JavaBean上!
代码比较简单,也没有什么配置要记录,只是开启了<mvc:annotation-driven/>,可以看到达到了这样的效果:
请求中属性name age 自动映射到 User对象上,返回视图时 属性又自动封装填充到 request属性域中. 填充的属性 键值key默认为类名首字母小写.
记录下,请求中参数是如何绑定到User对象上并且填充到request属性域中.
Spring抽象出来一个接口HandlerMethodArgumentResolver,这个接口主要实现两个功能:判断我们能否完成对这种参数类型的转换工作,以及我们如何去完成参数类型转换的工作? 等等,完成什么参数类型转换工作? 当然是完成@RequestMapping方法入参的参数转换工作啊!
那我们肯定需要一堆的HandlerMethodArgumentResolver这个接口实现类吧,肯定要有顺序吧,肯定用ArrayList来存储这个实现类。
Spring<mvc:annotation-driven/>肯定偷偷帮我们注册了一系列的HandlerMethodArgumentResolver实现类吧!没错,SpringMvc偷偷注册了十来个这样的HandlerMethodArgumentResolver,具体怎么工作的这里不叙述了,
其中专门用来解析自定义对象JavaBean的是ArrayList的最后一个HandlerMethodArgumentResolver :ServletModelAttributeMethodProcessor, 前面HandlerMethodArgumentResolver 都没用,才考虑到我,┭┮﹏┭┮
(其实也不是最后一个,因为注册了两个ServletModelAttributeMethodProcessor,只是这种情况我们用的是最后一个).
上面提及的两个工作,肯定是先判断我们能否完成对这种参数类型的转换工作:
我们现在讨论的不带 ModelAttribute注解,所以前面条件返回false , 后面条件 : this指代ServletModelAttributeMethodProcessor,annotationNotRequired为 true (这就是注册两个ServletModelAttributeMethodProcessor的原因,排在前面只 解析@ModelAttribute的参数,而排在后面的解析自定义 JavaBean对象的转换)
annotationNotRequired为true的情况下,后面条件判断的是 进一步限定 参数类型, 不是Date、数组、URI、URL、Number这种类型的,我才有必要进行 属性映射到对象上;
代码片段位于:org.springframework.web.method.annotation.ModelAttributeMethodProcessor#supportsParameter
这么看来 自定义对象完全满足使用 ServletModelAttributeMethodProcessor 来进行参数转换,下一步就是请求参数 转换到 JavaBean对象的事儿了.
逐行简单介绍下作用:Line100 检测方法入参parameter上是否有ModelAttribute注解,有就以@ModelAttribute(value=”xx”)的value作为name,没有就以parameter的class属性,类名转小写作为name 例如User—>user
Line101. 首先判断现在的ModelMap中是否有该name属性, 有就直接取出来作为attribute(ModelMap可以通过@ModelAttribute标注在普通方法上预先绑定一些属性);
没有就尝试创建,首先看是不是可以通过 URI中的PathVariable、request中直接getAttribute获取啊,都行不通的情况就直接构造这个class的实例,构造总要有构造器,采用默认的空参构造器,你没有空参构造器就会抛出异常!
这也告诉我们,参数类型需要有默认的空参构造器;
Line104 判断下这个属性是不是被列入黑名单,bindingDisabled, 加入黑名单方式目前暂知 通过@ModelAttribute(binding=false)设置,暂时不考虑这种情况;
Line111 WebDataBinder对象,这里也涉及了@InitBinder注解的解析,可以看之前的博客记录; target就是存的上面的attribute
Line112 attribute不为空,Line114行完成了属性的绑定,name、age映射到User上,映射方式按照名称映射,支持 . 进行级联等复杂映射,映射方式具体API可以看我下面的例子.
这里完成调用了ConversionService进行一定的转换,比如支持@DateTimeFormat等等.
Line116 @Valid注解解析过程,这里跳过; Line127 底层使用converter进行类型转换,我想不明白为啥还要再转换一次? 上面已经完成了请求参数绑定到JavaBean中.
完成请求方法参数—>JavaBean以后,ModelMap中的值最终都会存到ModelAndView视图的model属性, InternalResourceView的renderMergedOutputModel方法进行将 model属性一个个存入request的属性中;
跳转到对应的视图比如jsp一般都是request.getRequestDispatcher进行跳转; 假如@RequestMapping方法返回值不是前往某个视图JSP, 不会将属性存入request中。
另外注意:存储在request中的 key为 name,当前情况也就是 类名首字母小写。
代码片段位于:org.springframework.web.method.annotation.ModelAttributeMethodProcessor#resolveArgument
展示下如何利用Spring Api完成这样的请求参数绑定生成具体的实例
public class SimpleTest { public static void main(String[] args) { //接受Pojo对象 BeanWrapperImpl bw = new BeanWrapperImpl(new Form7Pojo()); PropertyValues pvs=new MutablePropertyValues(); ((MutablePropertyValues) pvs).add("name","HelloWorld BeanWrapper"); ((MutablePropertyValues) pvs).add("age",24); bw.setPropertyValues(pvs,false,false); Object target = bw.getWrappedInstance(); System.out.println(target); BeanWrapperImpl bw2 = new BeanWrapperImpl(new Address()); bw2.setAutoGrowNestedPaths(true); PropertyValues pvs2=new MutablePropertyValues(); ((MutablePropertyValues) pvs2).add("city.name","南京"); ((MutablePropertyValues) pvs2).add("location","鼓楼区"); bw2.setPropertyValues(pvs2,false,false); Object target2 = bw2.getWrappedInstance(); System.out.println(target2); } } @Setter @Getter @ToString public class Form7Pojo { private String name; private Integer age; } @Setter @Getter @ToString public class Address { private City city; private String location; } @Setter @ToString public class City { private String name; }
记录@ModelAttribute用法:
用法一: 单单用在 @RequestMapping中的方法参数中, 作用将当前对象以 “user2”的名字存入request ,展示在视图界面,默认是 “user”的名字.
用法二. 单单用在 @Controller方法中,不加@RequestMapping注解 ,作用: 该@ModelAttribute方法会在每个@RequestMapping方法执行之前执行一次, 在每个@RequestMapping方法中获取的方式:注入modelMap属性,modelMap.get(“skill”)的方式获取属性。
效果等同于如下方法:
用法三: 这种方式比较奇葩,先直接说结论,跳转到默认的JSP页面,比如我的默认JSP页面就是 /WEB-INF/jsp/demo5.jsp,request中存储属性{professionalSkill=hello}.
这个时候返回值类型String已经不重要了,反正都要存到request的attribute属性域中,key就是 @ModelAttribute注解的 value , value就是@RequestMapping方法返回值;
至于为啥跳转到/WEB-INF/jsp/demo5.jsp呢, DispatcherServlet的applyDefaultViewName方法是计算默认展示界面
SpringMvc计算默认展示界面规则:视图解析器前缀 + 请求相对路径 + 视图解析器后缀 ,请求相对路径就是/demo5
验证:下面是一个不含视图名的ModelAndView返回给前端,结果如下(因为Controller上有@RequestMapping为/modelAttri ):
用法四. 比如demoa方法,入参a就是demoaaaaaaa,其实通过注入ModelMap,然后get(“strA”)方法一样可以达到效果; 但是JSP页面上仍然可以使用 ${strA} ${strB}访问到对应属性;
简单记录下:SpringMvc @ModelAttribute注解解析位置:
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getModelFactory
总结:
感觉@ModelAttribute不是个很实用的注解,映射到JavaBean的时候吧,不需要它也能存入到request中,用它大概就是为了自定义存入request的key值吧.
在每个@RequestMapping方法之前做些处理吧,感觉有点像拦截器,但是每个@ModelAttribute都要执行一遍,有点多余哈,还不能指定只执行一个,有点搞不明白能用来做啥┭┮﹏┭┮.