DataBinder

@Controller@ControllerAdvice 類別可以具有 @InitBinder 方法來初始化 WebDataBinder 實例,而這些實例又可以

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

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

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

@Controller 中,DataBinder 自訂設定在本機套用於控制器內,甚至套用於透過註解依名稱參照的特定模型屬性。在 @ControllerAdvice 中,自訂設定可以套用於所有或部分控制器。

您可以在 DataBinder 中註冊 PropertyEditorConverterFormatter 組件以進行類型轉換。或者,您可以使用WebFlux 組態,在全球共用的 FormattingConversionService 中註冊 ConverterFormatter 組件。

  • 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
	protected void initBinder(WebDataBinder binder) {
		binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd")); (1)
	}

	// ...
}
1 新增自訂格式器(在本例中為 DateFormatter)。
@Controller
class FormController {

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

	// ...
}
1 新增自訂格式器(在本例中為 DateFormatter)。

模型設計

Web 請求的資料繫結 涉及將請求參數繫結到模型物件。依預設,請求參數可以繫結到模型物件的任何公用屬性,這表示惡意用戶端可以為模型物件圖形中存在但預期不會設定的屬性提供額外的值。這就是模型物件設計需要仔細考量的原因。

模型物件及其巢狀物件圖形有時也稱為命令物件表單支援物件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 模式(不區分大小寫)。但是,相較於「不允許」組態,最好使用「允許」組態,因為它更明確且較不易出錯。

依預設,建構子和屬性繫結都會使用。如果您只想使用建構子繫結,您可以透過 @ControllerAdvice 在控制器本機或全域設定 @InitBinder 方法,在 WebDataBinder 上設定 declarativeBinding 旗標。開啟此旗標可確保僅使用建構子繫結,並且除非組態了 allowedFields 模式,否則不會使用屬性繫結。例如

@Controller
public class MyController {

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

	// @RequestMapping methods, etc.

}