zoukankan      html  css  js  c++  java
  • SpringMVC基础——@ModelAttribute和@SessionAttribute

    一、@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

     1 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

    1.官方说明

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

    http://blog.sina.com.cn/s/blog_6d3c1ec601018cx1.html

    3.我自己的理解:

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

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

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

  • 相关阅读:
    Java编译器API简介
    liblinear和libsvm区别
    spark和hadoop比较
    maxout激活函数
    FTRL算法
    NLP里面好的学习资料
    阿里妈妈MLR模型(论文)
    FM的推导原理--推荐系统
    GBDT+LR simple例子
    深度学习最全优化方法---来源于知乎
  • 原文地址:https://www.cnblogs.com/solverpeng/p/5753033.html
Copyright © 2011-2022 走看看