@ModelAttribute 的使用

檸檬可樂小布丁發表於2020-04-20

@ModelAttribute註解可被應用在 方法方法引數 上。

對方法使用 @ModelAttribute 註解:

註解在方法上的@ModelAttribute說明了方法的作用是用於新增一個或多個屬性到model上。這樣的方法能接受與@RequestMapping註解相同的引數型別,只不過不能直接被對映到具體的請求上。

@ModelAttribute 方法會先被呼叫

在同一個控制器中,註解了@ModelAttribute的方法實際上會在@RequestMapping方法之前被呼叫。以下是幾個例子:

package com.pudding.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ModelAttributeController {

	@ModelAttribute
	public void init(Model model) {
		System.out.println("@RequestMapping方法");
	}

	@RequestMapping("/model-attribute")
	public String get() {
		System.out.println("@ModelAttribute方法");

		return "model-attribute";
	}

}

控制檯輸出的結果如下:

無返回值的 @ModelAttribute 方法

一般被 @ModelAttribute 註解的無返回值控制器方法被用來向 Model 物件中設定引數,在控制器跳轉的頁面中可以取得 Model 中設定的引數:

ModelAttributeController.java:

package com.pudding.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ModelAttributeController {

	@ModelAttribute
	public void init(Model model) {
		model.addAttribute("arg", "model中設定的引數");
	}

	@RequestMapping("/model-attribute")
	public String get() {

		return "model-attribute";
	}

}

model-attribute.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<input type="text" value=${ arg } />
</body>
</html>

訪問路徑:http://localhost:8080/model-attribute,頁面效果如下:

可以發現,我們在被 @RequestMapping 標註的方法中並沒有設定任何引數,但是頁面卻取得了在 @ModelAtribute 標註的方法設定的引數。由此可知,可以使用 @ModelAttribute 標註的方法來設定其他 @ReqeustMapping 方法的公用引數,這樣就不用每一個方法都設定共同的 Model 引數。

package com.pudding.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ModelAttributeController {

	@ModelAttribute
	public void init(Model model) {
		model.addAttribute("arg", "model中設定的引數");
	}

	@RequestMapping("/model-attribute")
	public String get() {

		return "model-attribute";
	}

	@RequestMapping("/demo")
	public String demo() {
		return "demo";
	}

}

在 demo.jsp 和 model-attribute.jsp 中都可以獲得名為 "arg" 的 Model 引數。

有返回值的 @ModelAttribute 方法

有返回值的 @ModelAttribute 方法和沒有返回值的 @ModelAttribute 方法的區別就是,沒有返回值的 @ModelAttribute 方法使用 model.addAttribute(String key, Object value); 來向 Model 中增加引數。而有返回值的 @ModelAttribute 方法則直接將需要增加的引數返回。

package com.pudding.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

import com.pudding.bean.User;

@Controller
public class ModelAttributeController {

	@ModelAttribute
	public User init(Model model) {
		User user = new User("小明", 18);
		return user;
	}

	@RequestMapping("/model-attribute")
	public String get() {

		return "model-attribute";
	}

}

上面的程式碼的作用是,向 Model 中增加了 key 為 "user" value 為 user 物件的鍵值對。這個 user 物件中有 username = "小明",age = 18 的屬性。

使用 @ModelAttribute("key") 來顯示指定屬性名

屬性名沒有被顯式指定的時候又當如何呢?在這種情況下,框架將根據屬性的型別給予一個預設名稱。舉個例子,若方法返回一個User型別的物件,則預設的屬性名為"user"。你可以通過設定@ModelAttribute註解的值來改變預設值。當向Model中直接新增屬性時,請使用合適的過載方法addAttribute(..)-即,帶或不帶屬性名的方法。

以下程式碼將會向 Model 中設定一個 key 為 "person" ,value 為 user物件的鍵值對:

package com.pudding.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

import com.pudding.bean.User;

@Controller
public class ModelAttributeController {

	@ModelAttribute("person")
	public User init(Model model) {
		User user = new User("小明", 18);
		return user;
	}

	@RequestMapping("/model-attribute")
	public String get() {

		return "model-attribute";
	}

}

@ModelAttribute 和 @RequestMapping 註解在同一個方法上

如果 @ModelAttribute 和 @RequestMapping 註解在同一個方法上,那麼代表給這個請求單獨設定 Model 引數。此時返回的值是 Model 的引數值,而不是跳轉的地址。跳轉的地址是根據請求的 url 自動轉換而來的。比如下面的例子中跳轉頁面不是 HelloWorld.jsp 而是 model-attribute.jsp。並且引數只有在 model-attribute.jsp 中能夠取得,而 demo.jsp 中不能取得。

package com.pudding.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ModelAttributeController {

	@ModelAttribute("message")
	@RequestMapping("/model-attribute")
	public String init(Model model) {
		return "HelloWorld";
	}

	@RequestMapping("/demo")
	public String get() {

		return "demo";
	}

}

在方法引數上使用 @ModelAttribute 註解:

如上一小節所解釋,@ModelAttribute註解既可以被用在方法上,也可以被用在方法引數上。這一小節將介紹它註解在方法引數上時的用法。

資料繫結

註解在方法引數上的@ModelAttribute說明了該方法引數的值將由model中取得。如果model中找不到,那麼該引數會先被例項化,然後被新增到model中。在model中存在以後,請求中所有名稱匹配的引數都會填充到該引數中。這在Spring MVC中被稱為資料繫結,一個非常有用的特性,節約了你每次都需要手動從表格資料中轉換這些欄位資料的時間。

和 BindingResult 配合使用

使用 @ModelAttribute 進行資料繫結之後,可以使用 BindingResult 來返回資料驗證結果。資料驗證可以使用 hibernate validation@Valid 標籤或者 spring Validator 校驗機制的 @Validated 配合 BindingResult 使用。 或者自定義校驗器來返回 BindingResult 物件來進行校驗。你可以通過Spring的 <errors> 表單標籤來在同一個表單上顯示錯誤資訊。

@Valid 校驗器:

@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {

    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}

@Validated 校驗器:

@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@Validated @ModelAttribute("pet") Pet pet, BindingResult result) {

    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}

自定義校驗器:

@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {

    // 自己編寫一個校驗方法來處理 result 物件
    new PetValidator().validate(pet, result);
    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}