DataBinder
@Controller 或 @ControllerAdvice 類可以有 @InitBinder 方法來初始化 WebDataBinder 例項,這些例項反過來可以
-
將請求引數繫結到模型物件。
-
將請求值從字串轉換為物件屬性型別。
-
在渲染 HTML 表單時將模型物件屬性格式化為字串。
在 @Controller 中,DataBinder 的定製適用於控制器內部的區域性範圍,甚至適用於透過註解按名稱引用的特定模型屬性。在 @ControllerAdvice 中,定製可以應用於所有控制器或部分控制器。
你可以在 DataBinder 中註冊 PropertyEditor、Converter 和 Formatter 元件以進行型別轉換。另外,你可以使用 WebFlux 配置 在全域性共享的 FormattingConversionService 中註冊 Converter 和 Formatter 元件。
-
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(普通 Java 物件)。 |
一個好的做法是使用專用模型物件,而不是將你的領域模型(例如 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 模式(不區分大小寫)。然而,"allowed" 配置比 "disallowed" 更受青睞,因為它更明確且不易出錯。
預設情況下,建構函式繫結和屬性繫結都使用。如果你只想使用建構函式繫結,可以透過 @InitBinder 方法在控制器內部區域性或透過 @ControllerAdvice 全域性設定 WebDataBinder 上的 declarativeBinding 標誌。啟用此標誌可確保只使用建構函式繫結,除非配置了 allowedFields 模式,否則不使用屬性繫結。例如
@Controller
public class MyController {
@InitBinder
void initBinder(WebDataBinder binder) {
binder.setDeclarativeBinding(true);
}
// @RequestMapping methods, etc.
}