@InitBinder

@Controller@ControllerAdvice 類可以包含 @InitBinder 方法,以初始化 WebDataBinder 例項,這些例項反過來可以

  • 將請求引數繫結到模型物件。

  • 將請求值從字串轉換為物件屬性型別。

  • 在渲染 HTML 表單時將模型物件屬性格式化為字串。

@Controller 中,DataBinder 的定製設定區域性應用於該控制器內,甚至可以透過註解按名稱引用特定的模型屬性。在 @ControllerAdvice 中,定製設定可以應用於所有或部分控制器。

您可以在 DataBinder 中註冊 PropertyEditorConverterFormatter 元件用於型別轉換。或者,您可以使用 MVC 配置 在全域性共享的 FormattingConversionService 中註冊 ConverterFormatter 元件。

@InitBinder 方法可以擁有許多與 @RequestMapping 方法相同的引數,但值得注意的是 @ModelAttribute 除外。通常,這類方法有一個 WebDataBinder 引數(用於註冊)和一個 void 返回值,例如

  • Java

  • Kotlin

@Controller
public class FormController {

	@InitBinder (1)
	public void initBinder(WebDataBinder binder) {
		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
		dateFormat.setLenient(false);
		binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
	}

	// ...
}
1 定義一個 @InitBinder 方法。
@Controller
class FormController {

	@InitBinder (1)
	fun initBinder(binder: WebDataBinder) {
		val dateFormat = SimpleDateFormat("yyyy-MM-dd")
		dateFormat.isLenient = false
		binder.registerCustomEditor(Date::class.java, CustomDateEditor(dateFormat, false))
	}

	// ...
}
1 定義一個 @InitBinder 方法。

或者,當您透過共享的 FormattingConversionService 使用基於 Formatter 的設定時,可以重用相同的方法並註冊特定控制器的 Formatter 實現,如下例所示

  • Java

  • Kotlin

@Controller
public class FormController {

	@InitBinder (1)
	protected void initBinder(WebDataBinder binder) {
		binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
	}

	// ...
}
1 在自定義格式化器上定義 @InitBinder 方法。
@Controller
class FormController {

	@InitBinder (1)
	protected fun initBinder(binder: WebDataBinder) {
		binder.addCustomFormatter(DateFormatter("yyyy-MM-dd"))
	}

	// ...
}
1 在自定義格式化器上定義 @InitBinder 方法。

模型設計

Web 請求的資料繫結涉及將請求引數繫結到模型物件。預設情況下,請求引數可以繫結到模型物件的任何公共屬性,這意味著惡意客戶端可以為模型物件圖中存在但未預期設定的屬性提供額外的值。這就是為什麼模型物件設計需要仔細考慮。

模型物件及其巢狀物件圖有時也稱為命令物件(command object)表單支援物件(form-backing object)POJO(Plain Old Java Object)

一個好的實踐是使用專用的模型物件,而不是暴露您的領域模型(如 JPA 或 Hibernate 實體)用於 Web 資料繫結。例如,在一個用於更改電子郵件地址的表單上,建立一個 ChangeEmailForm 模型物件,它只宣告輸入所需的屬性

public class ChangeEmailForm {

	private String oldEmailAddress;
	private String newEmailAddress;

	public void setOldEmailAddress(String oldEmailAddress) {
		this.oldEmailAddress = oldEmailAddress;
	}

	public String getOldEmailAddress() {
		return this.oldEmailAddress;
	}

	public void setNewEmailAddress(String newEmailAddress) {
		this.newEmailAddress = newEmailAddress;
	}

	public String getNewEmailAddress() {
		return this.newEmailAddress;
	}

}

另一個好的實踐是應用建構函式繫結,它只使用建構函式引數所需的請求引數,任何其他輸入都被忽略。這與屬性繫結形成對比,屬性繫結預設會繫結所有具有匹配屬性的請求引數。

如果專用模型物件和建構函式繫結都不夠用,並且您必須使用屬性繫結,我們強烈建議在 WebDataBinder 上註冊 allowedFields 模式(區分大小寫),以防止意外屬性被設定。例如

@Controller
public class ChangeEmailController {

	@InitBinder
	void initBinder(WebDataBinder binder) {
		binder.setAllowedFields("oldEmailAddress", "newEmailAddress");
	}

	// @RequestMapping methods, etc.

}

您還可以註冊 disallowedFields 模式(不區分大小寫)。但是,“允許”配置優先於“不允許”配置,因為它更明確且不易出錯。

預設情況下,建構函式繫結和屬性繫結都使用。如果您只想使用建構函式繫結,可以透過 @InitBinder 方法在控制器內部區域性設定,或者透過 @ControllerAdvice 全域性設定 WebDataBinder 上的 declarativeBinding 標誌。開啟此標誌確保僅使用建構函式繫結,並且除非配置了 allowedFields 模式,否則不使用屬性繫結。例如

@Controller
public class MyController {

	@InitBinder
	void initBinder(WebDataBinder binder) {
		binder.setDeclarativeBinding(true);
	}

	// @RequestMapping methods, etc.

}