Spring注解@ModelAttribute

参考一:

@ModelAttribute使用详解

1.@ModelAttribute注释方法
    例子(1),(2),(3)类似,被@ModelAttribute注释的方法会在此controller每个方法执行前被执行,因此对于一个controller映射多个URL的用法来说,要谨慎使用。

(1)@ModelAttribute注释void返回值的方法

 

 这个例子,在获得请求/helloWorld 后,populateModel方法在helloWorld方法之前先被调用,它把请求参数(/helloWorld?abc=text)加入到一个名为attributeName的model属性中,在它执行后helloWorld被调用,返回视图名helloWorld和model已由@ModelAttribute方法生产好了。
这个例子中model属性名称和model属性对象有model.addAttribute()实现,不过前提是要在方法中加入一个Model类型的参数。
    当URL或者post中不包含次参数时,会报错,其实不需要这个方法,完全可以把请求的方法写成,这样缺少此参数也不会出错

 (2)@ModelAttribute注释返回具体类的方法

 

 这种情况,model属性的名称没有指定,它由返回类型隐含表示,如这个方法返回Account类型,那么这个model属性的名称是account。
    这个例子中model属性名称有返回对象类型隐含表示,model属性对象就是方法的返回值。它无须要特定的参数。

(3)@ModelAttribute(value="")注释返回具体类的方法

 

 这个例子中使用@ModelAttribute注释的value属性,来指定model属性的名称。model属性对象就是方法的返回值。它无须要特定的参数。

  (4)@ModelAttribute和@RequestMapping同时注释一个方法

 

 这时这个方法的返回值并不是表示一个视图名称,而是model属性的值,视图名称由RequestToViewNameTranslator根据请求"/helloWorld.do"转换为逻辑视图helloWorld。
    Model属性名称有@ModelAttribute(value=””)指定,相当于在request中封装了key=attributeName,value=hi。

2.@ModelAttribute注释一个方法的参数

(1)从model中获取    

 

 在这个例子里,@ModelAttribute("user") User user注释方法参数,参数user的值来源于addAccount()方法中的model属性。
    此时如果方法体没有标注@SessionAttributes("user"),那么scope为request,如果标注了,那么scope为session

(2)从Form表单或URL参数中获取(实际上,不做此注释也能拿到user对象)

 

 注意这时候这个User类一定要有没有参数的构造函数。

参考二:

一、@ModelAttribute 注解

对方法标注 @ModelAttribute 注解,在调用各个目标方法前都会去调用 @ModelAttribute 标记的注解。本质上来说,允许我们在调用目标方法前操纵模型数据。

1.在 @ModelAttribute 标注的方法处向模型中存入数据

说明一下:在@ModelAttribute 标注的方法处,可以入参的类型和目标方法处允许的入参类型一致,如 @RequestParam 标注的请求参数等等。

有两种方式:

目标方法:

@RequestMapping("/updateStudent")
public String update(Student student) {
    System.out.println("student: " + student);
    return "success";
}

(1)通过向入参处添加 Model 类型或 Map 类型的参数(不推荐)

复制代码
@ModelAttribute
public void getStudent(@RequestParam(value = "id", required = false) String idStr, Map<String, Object> map) {
    try {
        Integer id = Integer.parseInt(idStr);
        System.out.println("student id: " + id);
        map.put("student", new Student(1, "lisi", 23));
    } catch(NumberFormatException ignored) {
    }
}
复制代码

在调用目标方法前,"student" 会被放入到 Model 中。至于说为什么不推荐此种用法,是因为,最终还会向 model 中添加一个 key 为 void,值为 null 的数据。如图:

 

 (2)通过 @ModelAttribute 注解的 value 属性和 @ModelAttribute 标注的方法返回值(推荐)

复制代码
@ModelAttribute("student")
public Student getStudent(@RequestParam(value = "id", required = false) String idStr, Map<String, Object> map) {
    Student student = null;
    try {
        Integer id = Integer.parseInt(idStr);
        System.out.println("student id: " + id);
        student = new Student(1, "lisi", 23);
    } catch(NumberFormatException ignored) {
    }
    return student;
}
复制代码

在调用目标方法前,model 中的数据:

model 中只有一个键值对。这种写法更加优雅。

总结:SpringMVC 在调用目标方法前,将 @ModelAttribute 注解的 value 属性值作为 key , 返回值作为 value,存入到 model 中。

源码分析:

org.springframework.web.bind.annotation.support.HandlerMethodInvoker#invokeHandlerMethod

复制代码
public final Object invokeHandlerMethod(Method handlerMethod, Object handler,
 2             NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {
 3 
 4     Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod);
 5     try {
 6         boolean debug = logger.isDebugEnabled();
 7         for (String attrName : this.methodResolver.getActualSessionAttributeNames()) {
 8             Object attrValue = this.sessionAttributeStore.retrieveAttribute(webRequest, attrName);
 9             if (attrValue != null) {
10                 implicitModel.addAttribute(attrName, attrValue);
11             }
12         }
13         //开始调用标注有 @ModelAttribute 注解的方法
14         for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) {
15             Method attributeMethodToInvoke = BridgeMethodResolver.findBridgedMethod(attributeMethod);
16             Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel);
17             if (debug) {
18                 logger.debug("Invoking model attribute method: " + attributeMethodToInvoke);
19             }
20             String attrName = AnnotationUtils.findAnnotation(attributeMethod, ModelAttribute.class).value();
21             if (!"".equals(attrName) && implicitModel.containsAttribute(attrName)) {
22                 continue;
23             }
24             ReflectionUtils.makeAccessible(attributeMethodToInvoke);
25             Object attrValue = attributeMethodToInvoke.invoke(handler, args);
26             if ("".equals(attrName)) {
27                 Class<?> resolvedType = GenericTypeResolver.resolveReturnType(attributeMethodToInvoke, handler.getClass());
28                 attrName = Conventions.getVariableNameForReturnType(attributeMethodToInvoke, resolvedType, attrValue);
29             }
30             if (!implicitModel.containsAttribute(attrName)) {
31                 implicitModel.addAttribute(attrName, attrValue);
32             }
33         }
34         Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel);
35         if (debug) {
36             logger.debug("Invoking request handler method: " + handlerMethodToInvoke);
37         }
38         ReflectionUtils.makeAccessible(handlerMethodToInvoke);
39         //调用目标方法
40         return handlerMethodToInvoke.invoke(handler, args);
41     }
42     catch (IllegalStateException ex) {
43         // Internal assertion failed (e.g. invalid signature):
44         // throw exception with full handler method context...
45         throw new HandlerMethodInvocationException(handlerMethodToInvoke, ex);
46     }
47     catch (InvocationTargetException ex) {
48         // User-defined @ModelAttribute/@InitBinder/@RequestMapping method threw an exception...
49         ReflectionUtils.rethrowException(ex.getTargetException());
50         return null;
51     }
52 }
复制代码

行号14 处的 for 循环就是处理 @ModleAttribute 标注的方法的,在40行处调用目标方法——在调用目标方法前调用 @ModelAttribute 标注的方法。

在 16 行处已经对请求参数做了一次解析——在@ModelAttribute 标注的方法处,可以入参的类型和目标方法处允许的入参类型一致

 20行、25行、31行——第二种方式,同时也明白如果 model 中包含相同的 key 时,是不会替换的。

2.在目标方法处读取模型中的数据

复制代码
@ModelAttribute("student")
public Student getStudent() {
    return new Student(1, "lisi", 23);
}

@ModelAttribute("student2")
public Student getStudent2() {
    return new Student(2, "wangwu", 33);
}
复制代码

(1)在目标方法入参处不使用 @ModelAttribute 注解

@RequestMapping("/updateStudent")
public String update(Student student2) {
    System.out.println("student: " + student2);
    return "success";
}

控制台输出:

student: Student{id=23, studentName='lisi', age=23}

(2)在目标方法入参处使用 @ModelAttribute 注解

@RequestMapping("/updateStudent")
public String update(@ModelAttribute("student2") Student student2) {
    System.out.println("student: " + student2);
    return "success";
}

控制台输出:

student: Student{id=23, studentName='wangwu', age=33}

(3)源码分析

org.springframework.web.bind.annotation.support.HandlerMethodInvoker#resolveHandlerArguments

这个方法行数太多了,我们只看关注点:

289行:如果目标方法入参有标记 @ModelAttribute ,获取它 的 value 属性。

else if (ModelAttribute.class.isInstance(paramAnn)) {
    ModelAttribute attr = (ModelAttribute) paramAnn;
    attrName = attr.value();
    annotationsFound++;
}

361行:

复制代码
else if (attrName != null) {
    WebDataBinder binder =
            resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);
    boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]));
    if (binder.getTarget() != null) {
        doBind(binder, webRequest, validate, validationHints, !assignBindingResult);
    }
    args[i] = binder.getTarget();
    if (assignBindingResult) {
        args[i + 1] = binder.getBindingResult();
        i++;
    }
    implicitModel.putAll(binder.getBindingResult().getModel());
}
复制代码

不论是对目标方法入参有没有标注 @ModelAttribute 注解,最终都会执行到这里。

看标红的地方:在这里进行解析的。

复制代码
private WebDataBinder resolveModelAttribute(String attrName, MethodParameter methodParam,
            ExtendedModelMap implicitModel, NativeWebRequest webRequest, Object handler) throws Exception {

    // Bind request parameter onto object...
    String name = attrName;
    if ("".equals(name)) {
        name = Conventions.getVariableNameForParameter(methodParam);
    }
    Class<?> paramType = methodParam.getParameterType();
    Object bindObject;
    if (implicitModel.containsKey(name)) {
        bindObject = implicitModel.get(name);
    }
    else if (this.methodResolver.isSessionAttribute(name, paramType)) {
        bindObject = this.sessionAttributeStore.retrieveAttribute(webRequest, name);
        if (bindObject == null) {
            raiseSessionRequiredException("Session attribute '" + name + "' required - not found in session");
        }
    }
    else {
        bindObject = BeanUtils.instantiateClass(paramType);
    }
    WebDataBinder binder = createBinder(webRequest, bindObject, name);
    initBinder(handler, name, binder, webRequest);
    return binder;
}
复制代码

注意:

String name = attrName;
if ("".equals(name)) {
  name = Conventions.getVariableNameForParameter(methodParam);
}

如果没有指定,则通过  Conventions.getVariableNameForParameter(methodParam) 获取一个默认值。

if (implicitModel.containsKey(name)) {
  bindObject = implicitModel.get(name);
}

从 model中获取,最后执行绑定。

(4)总结:使用在目标方法入参处的 @ModelAttribute 只能起到一个 指定 attrName 的作用,即从 Model 获取数据的 key。

<1>目标方法处的实体形参命名与 @ModelAttribute 方法标注的方法返回值之间没有任何关系,只是类型有关系。

<2>在目标方法入参处不使用 @ModelAttribute 注解的情况:

不需要通过 @ModelAttribute 注解来指定需要使用哪个 @ModelAttribute 标注的方法的 value 属性值。存在多个的话,使用默认值。

<3>在目标方法入参处需要使用 @ModelAttribute 注解的情况:

存在多个 @ModelAttribute 标注的方法,返回值为同一个类型A,且 @ModelAttribute 的 value 属性值不同,在目标方法处,需要以 A 实体作为入参,但是需要不使用默认的 a ,而是需要使用指定

的 a2。这个时候,就需要在目标方法的入参处使用 @ModelAttribute,通过 value 属性来指定使用哪个。

二、@SessionAttribute

2.对 SessionAttribute 这里有篇帖子总结的非常好,我这里就不再赘述。

https://www.cnblogs.com/javaxubo/p/15747051.html

3.我自己的理解:

@SessionAttribute 指的是 springmvc 的 session。向其中添加值得时候,同时会向 http session 中添加一条。在 sessionStatus.setComplete(); 的时候,会清空 sprinmvc

的 session,同时清除对应键的 http session 内容,但是通过,request.getSession.setAttribute() 方式添加的内容不会被清除掉。

其他情况下,springmvc session 和 http session使用情况相同。

 

 

参考:

http://www.hellojava.com/a/89390.html

https://www.cnblogs.com/solverpeng/p/5753033.html

posted @   哩个啷个波  阅读(1686)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示