1. 程式人生 > 其它 >Spring 註解面面通 之 @ModelAttribute 模型屬性繫結全解

Spring 註解面面通 之 @ModelAttribute 模型屬性繫結全解

技術標籤:Spring 全面解析SpringModelAttribute註解示例

  @ModelAttribute用於將方法引數或返回值繫結到Model屬性上,並公開給Web檢視。支援使用@RequestMapping註釋的Controller類。

  註解解析

  ① value

    待繫結到Model屬性的鍵名稱。

    預設Model屬性鍵名稱根據非限定類名從宣告的屬性型別(即方法引數型別或方法返回型別)推斷而來:

    mypackage.OrderAddress型別對應的鍵名稱是orderAddress

    List<mypackage.OrderAddress>

型別對應的鍵名稱是orderAddressList

  ② name

    繫結的引數名稱,引數值為String型別。namevalue可以同時使用,但兩者的值需一致,否則會出現錯誤。

attribute 'name' and its alias 'value' are present with values of [XXX] and [XXX], but only one is permitted

  ③ binding

    允許直接在@ModelAttribute註釋的方法引數或@ModelAttribute註釋的方法返回值上宣告禁用資料繫結,這兩種方法都將阻止資料繫結。

    預設情況下,binding

被設定為true,此時將正常進行資料繫結。將binding設定為false,將禁用資料繫結。

  註解應用

  1) @ModelAttribute註釋方法。

  @ModelAttribute註釋方法將在Controller@RequestMapping註釋方法前被呼叫。對於@ModelAttribute註釋方法有以下幾種情況:

  ① @ModelAttribute註釋void返回型別方法。

  此種情況下,由於方法返回值為void型別,並不會做其他處理。因此若需要向ModeMap中新增屬性,需要通過方法引數ModelMap來完成。

@ModelAttribute
public void
modelAttributeWithVoidMethod(ModelMap modelMap) { modelMap.addAttribute("modelAttributeWithVoidMethod", "@ModelAttribute註釋在void返回型別的方法上."); }

  ② @ModelAttribute註釋非void返回型別方法。

  此種情況下,中所使用操作ModelMap方式仍然有效,需要通過方法引數ModelMap來新增屬性。

  同時,以返回值型別推斷出的鍵、返回值作為鍵值對新增到ModelMap中。

@ModelAttribute
public String modelAttributeWithStringMethod() {
    return "@ModelAttribute註釋String返回型別方法,會自動將返回值新增到ModelMap中,鍵根據返回型別生成.";
}

  ③ @ModelAttribute註釋非void返回型別方法,並指定其namevalue屬性。

​  此種情況下,中所使用操作ModelMap方式仍然有效,需要通過方法引數ModelMap來新增屬性。

  同時,以@ModelAttribute註解namevalue屬性值、返回值作為鍵值對新增到ModelMap中。

@ModelAttribute("defModelAttributeName")
public String modelAttributeWithStringMethodDefName(ModelMap modelMap) {
    return "@ModelAttribute註釋String型別返回值方法,會自動將返回值新增到ModelMap中,鍵是@ModelAttribute的name或value屬性值.";
}

  ④ @ModelAttribute@RequestMapping註釋同一方法。

  此種情況下,@ModelAttribute@RequestMapping相互作用,會使@RequestMapping表現出稍許差異。@ModelAttribute會使得@RequestMapping註釋方法的某些型別返回值不會使用對應HandlerMethodReturnValueHandler,而是由ModelAttributeMethodProcessor解析。

  ModelAttributeMethodProcessor會針對返回值與中進行同樣處理。檢視名稱由RequestToViewNameTranslator根據請求/ModelAttributeWithRequestMapping.do轉換為邏輯檢視ModelAttributeWithRequestMapping

  不受影響的返回型別包括:ModelAndViewModelViewResponseBodyEmitterStreamingResponseBodyHttpEntityHttpHeadersCallableDeferredResultAsyncTask

@ModelAttribute
@RequestMapping(
    value = "/ModelAttributeWithRequestMapping.do",
    method = RequestMethod.GET)
public String modelAttributeWithRequestMapping(ModelMap modelMap) throws Exception {
    logger.info("@ModelAttribute與@RequestMapping共同作用在一個方法.");
    return "webannotations/ModelAttribute";
}

  2) @ModelAttribute註釋引數。

  ① @ModelAttribute註釋方法引數。

  此種情況下,@ModelAttribute註解用於ModelMap資料對映到控制器處理方法的引數中。

@RequestMapping(
    value = "/ModelAttributeParameters.do",
    method = RequestMethod.GET)
public ModelAndView modelAttributeParameters(@ModelAttribute("defModelAttributeName") String modelAttr,
                                             ModelMap modelMap) throws Exception {
    logger.info("@ModelAttribute註釋方法引數,從ModelMap中取值:[key=defModelAttributeName, value=" + modelAttr + "].");
    return new ModelAndView("webannotations/ModelAttribute", modelMap);
}

  註解示例

  1)Controller,用來演示@ModelAttribute使用方法。

package com.arhorchin.securitit.webannotations;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

/**
 * @author Securitit.
 * @note 演示@ModelAttribute使用方法.
 */
@Controller
@RequestMapping("/WebAnnotations")
public class ModelAttributeController {

    /**
     * logger.
     */
    private Logger logger = LoggerFactory.getLogger(ModelAttributeController.class);

    /**
     * @ModelAttribute註釋
     *  void型別返回值方法. 
     *  需要手動ModelMap中新增資料.
     */
    @ModelAttribute
    public void modelAttributeWithVoidMethod(ModelMap modelMap) {
        modelMap.addAttribute("modelAttributeWithVoidMethod",
                "@ModelAttribute註釋在void返回型別的方法上.");
    }

    /**
     * @ModelAttribute註釋
     *  String返回型別方法. 
     *  會自動將返回值新增到ModelMap中,鍵根據返回型別生成.
     */
    @ModelAttribute
    public String modelAttributeWithStringMethod() {
        return "@ModelAttribute註釋String返回型別方法,會自動將返回值新增到ModelMap中,鍵根據返回型別生成.";
    }

    /**
     * @ModelAttribute註釋
     *  String型別返回值方法. 
     *  會自動將返回值新增到ModelMap中,鍵是@ModelAttribute的name或value屬性值.
     */
    @ModelAttribute("defModelAttributeName")
    public String modelAttributeWithStringMethodDefName() {
        return "@ModelAttribute註釋String型別返回值方法,會自動將返回值新增到ModelMap中,鍵是@ModelAttribute的name或value屬性值.";
    }

    /**
     * ModelAttribute.do.
     */
    @RequestMapping(
            value = "/ModelAttribute.do",
            method = RequestMethod.GET)
    public ModelAndView modelAttribute(ModelMap modelMap) throws Exception {
        logger.info("@ModelAttribute註解測試.");
        return new ModelAndView("webannotations/ModelAttribute", modelMap);
    }
    
    /**
     * ModelAttribute.do.
     * @ModelAttribute與@RequestMapping共同註釋同一方法測試.
     */
    @ModelAttribute
    @RequestMapping(
            value = "/ModelAttributeWithRequestMapping.do",
            method = RequestMethod.GET)
    public String modelAttributeWithRequestMapping() throws Exception {
        logger.info("@ModelAttribute與@RequestMapping共同作用在一個方法.");
        return "webannotations/ModelAttribute";
    }
    

    /**
     * ModelAttributeParameters.do.
     * @ModelAttribute註釋引數,可以從ModelMap中取指定引數值.
     */
    @RequestMapping(
            value = "/ModelAttributeParameters.do",
            method = RequestMethod.GET)
    public ModelAndView modelAttributeParameters(@ModelAttribute("defModelAttributeName") String modelAttr,
            ModelMap modelMap) throws Exception {
        logger.info("@ModelAttribute註釋方法引數,從ModelMap中取值:[key=defModelAttributeName, value=" + modelAttr + "].");
        return new ModelAndView("webannotations/ModelAttribute", modelMap);
    }

}

  2)ModelAttributeHandlerInterceptor,用來列印演示ModelMap的值。

package com.arhorchin.securitit.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.alibaba.fastjson.JSON;

/**
 * @author Securitit.
 * @note @ModelAttribute檢視ModelMap測試.
 */
public class ModelAttributeHandlerInterceptor implements HandlerInterceptor {

    /**
     * logger.
     */
    private Logger logger = LoggerFactory.getLogger(ModelAttributeHandlerInterceptor.class);

    /**
     * 在HandlerAdapter實際呼叫處理程式之後呼叫,但在DispatcherServlet呈現檢視之前呼叫.
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable ModelAndView modelAndView) throws Exception {
        logger.info("==============================ModelAttributeHandlerInterceptor postHandle==============================\n"
                + JSON.toJSONString(modelAndView.getModelMap(), true));
    }

}

  3) 啟動服務,訪問http://localhost:9199/spring-annotations/WebAnnotations/xxxxx.do,用來檢視@ModelAttribute各種使用方法。

  ① 訪問http://localhost:9199/spring-annotations/WebAnnotations/ModelAttribute.do,演示@ModelAttribute註釋單獨方法。

  控制檯輸出:

2020-12-16 16:04:26 INFO [c.a.s.i.ModelAttributeHandlerInterceptor] ==============================ModelAttributeHandlerInterceptor postHandle==============================
{
	"defModelAttributeName":"@ModelAttribute註釋String型別返回值方法,會自動將返回值新增到ModelMap中,鍵是@ModelAttribute的name或value屬性值.",
	"string":"@ModelAttribute註釋String返回型別方法,會自動將返回值新增到ModelMap中,鍵根據返回型別生成.",
	"modelAttributeWithVoidMethod":"@ModelAttribute註釋在void返回型別的方法上."
}

  可以看到,@ModelAttribute註釋的modelAttributeWithVoidMethod(...)modelAttributeWithStringMethod()modelAttributeWithStringMethodDefName(),按照解析的語義已經執行。

  ② 訪問http://localhost:9199/spring-annotations/WebAnnotations/ModelAttributeWithRequestMapping.do,演示@ModelAttribute@RequestMapping共同註釋一個方法。

  控制檯輸出:

2020-12-16 16:11:37 INFO [c.a.s.i.ModelAttributeHandlerInterceptor] ==============================ModelAttributeHandlerInterceptor postHandle==============================
{
	"defModelAttributeName":"@ModelAttribute註釋String型別返回值方法,會自動將返回值新增到ModelMap中,鍵是@ModelAttribute的name或value屬性值.",
	"modelAttributeWithVoidMethod":"@ModelAttribute註釋在void返回型別的方法上.",
	"string":"webannotations/ModelAttribute"
}

  瀏覽器響應:

在這裡插入圖片描述

  除了中所示語義外,還改變了@RequestMapping註釋方法返回值的語義,將返回值按照規則新增到ModelMap中,檢視則是由/ModelAttributeWithRequestMapping.do來確定的。

  ③ 訪問http://localhost:9199/spring-annotations/WebAnnotations/ModelAttributeParameters.do,演示@ModelAttribute註釋方法引數。

2020-12-16 16:31:23 INFO [c.a.s.i.ModelAttributeHandlerInterceptor] ==============================ModelAttributeHandlerInterceptor postHandle==============================
{
	"defModelAttributeName":"@ModelAttribute註釋String型別返回值方法,會自動將返回值新增到ModelMap中,鍵是@ModelAttribute的name或value屬性值.",
	"modelAttributeWithVoidMethod":"@ModelAttribute註釋在void返回型別的方法上.",
	"string":"@ModelAttribute註釋String返回型別方法,會自動將返回值新增到ModelMap中,鍵根據返回型別生成."
}
2020-12-16 16:31:41 INFO [c.a.s.w.ModelAttributeController] @ModelAttribute註釋方法引數,從ModelMap中取值:[key=defModelAttributeName, value=@ModelAttribute註釋String型別返回值方法,會自動將返回值新增到ModelMap中,鍵是@ModelAttribute的name或value屬性值.].

  可以看到,@ModelAttribute註釋方法將從ModelMap中獲取值,繫結到方法引數上。

  總結

  @ModelAttribute主要針對ModelMap進行操作,對於傳統的、前後端未分離的應用來說,用處還是很大的。

  原始碼解析基於spring-framework-5.0.5.RELEASE版本原始碼。

  若文中存在錯誤和不足,歡迎指正!