@InitBinder
@Controller
或 @ControllerAdvice
類別可以有 @InitBinder
方法來初始化 WebDataBinder
實例,而這些實例又可以
-
將請求參數繫結到模型物件。
-
將請求值從字串轉換為物件屬性類型。
-
在呈現 HTML 表單時,將模型物件屬性格式化為字串。
在 @Controller
中,DataBinder
客製化設定在本機控制器內套用,甚至可以透過註解參照的名稱套用到特定的模型屬性。在 @ControllerAdvice
中,客製化設定可以套用到所有或部分控制器。
您可以在 DataBinder
中註冊 PropertyEditor
、Converter
和 Formatter
元件以進行類型轉換。或者,您可以使用MVC 設定,在全球共用的 FormattingConversionService
中註冊 Converter
和 Formatter
元件。
@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 請求,涉及將請求參數繫結到模型物件。預設情況下,請求參數可以繫結到模型物件的任何公用屬性,這表示惡意用戶端可以為模型物件圖形中存在的屬性提供額外的值,但這些屬性並非預期要設定的。這就是為什麼模型物件設計需要仔細考量。
模型物件及其巢狀物件圖形有時也稱為命令物件、表單後端物件或 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
模式(不區分大小寫)。但是,「允許」配置優於「不允許」,因為它更明確且不易出錯。
預設情況下,建構子和屬性繫結都會使用。如果您只想使用建構子繫結,您可以透過 @Controller
內的本機 @InitBinder
方法或透過 @ControllerAdvice
全域設定 WebDataBinder
上的 declarativeBinding
旗標。開啟此旗標可確保僅使用建構子繫結,並且除非配置了 allowedFields
模式,否則不會使用屬性繫結。例如
@Controller
public class MyController {
@InitBinder
void initBinder(WebDataBinder binder) {
binder.setDeclarativeBinding(true);
}
// @RequestMapping methods, etc.
}