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”的名字.

 

, 在每个@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都要执行一遍,有点多余哈,还不能指定只执行一个,有点搞不明白能用来做啥┭┮﹏┭┮.

人已赞赏
随笔日记

通过GeneXus如何快速构建微服务架构

2020-11-9 4:01:27

随笔日记

html+css制作五环(代码极简)

2020-11-9 4:01:29

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索