SpringMVC基礎——@ModelAttribute和@SessionAttribute

1、@ModelAttribute 註解

對方法標註 @ModelAttribute 註解,在調用各個目標方法前都會去調用 @ModelAttribute 標記的註解。本質上來講,容許咱們在調用目標方法前操縱模型數據。html

1.在 @ModelAttribute 標註的方法處向模型中存入數據java

說明一下:在@ModelAttribute 標註的方法處,能夠入參的類型和目標方法處容許的入參類型一致,如 @RequestParam 標註的請求參數等等。web

有兩種方式:spring

目標方法:session

@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 的數據。如圖:mvc

 

(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 中的數據:app

model 中只有一個鍵值對。這種寫法更加優雅。源碼分析

總結:SpringMVC 在調用目標方法前,將 @ModelAttribute 註解的 value 屬性值做爲 key , 返回值做爲 value,存入到 model 中。ui

源碼分析:this

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

public final Object invokeHandlerMethod(Method handlerMethod, Object handler,
            NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {

    Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod);
    try {
        boolean debug = logger.isDebugEnabled();
        for (String attrName : this.methodResolver.getActualSessionAttributeNames()) {
            Object attrValue = this.sessionAttributeStore.retrieveAttribute(webRequest, attrName);
            if (attrValue != null) {
                implicitModel.addAttribute(attrName, attrValue);
            }
        }
        //開始調用標註有 @ModelAttribute 註解的方法
        for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) {
            Method attributeMethodToInvoke = BridgeMethodResolver.findBridgedMethod(attributeMethod);
            Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel);
            if (debug) {
                logger.debug("Invoking model attribute method: " + attributeMethodToInvoke);
            }
            String attrName = AnnotationUtils.findAnnotation(attributeMethod, ModelAttribute.class).value();
            if (!"".equals(attrName) && implicitModel.containsAttribute(attrName)) {
                continue;
            }
            ReflectionUtils.makeAccessible(attributeMethodToInvoke);
            Object attrValue = attributeMethodToInvoke.invoke(handler, args);
            if ("".equals(attrName)) {
                Class<?> resolvedType = GenericTypeResolver.resolveReturnType(attributeMethodToInvoke, handler.getClass());
                attrName = Conventions.getVariableNameForReturnType(attributeMethodToInvoke, resolvedType, attrValue);
            }
            if (!implicitModel.containsAttribute(attrName)) {
                implicitModel.addAttribute(attrName, attrValue);
            }
        }
        Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel);
        if (debug) {
            logger.debug("Invoking request handler method: " + handlerMethodToInvoke);
        }
        ReflectionUtils.makeAccessible(handlerMethodToInvoke);
        //調用目標方法
        return handlerMethodToInvoke.invoke(handler, args);
    }
    catch (IllegalStateException ex) {
        // Internal assertion failed (e.g. invalid signature):
        // throw exception with full handler method context...
        throw new HandlerMethodInvocationException(handlerMethodToInvoke, ex);
    }
    catch (InvocationTargetException ex) {
        // User-defined @ModelAttribute/@InitBinder/@RequestMapping method threw an exception...
        ReflectionUtils.rethrowException(ex.getTargetException());
        return null;
    }
}

 

行號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 屬性來指定使用哪一個。

 

2、@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使用狀況相同。

相關文章
相關標籤/搜索