@InitBinder

@Controller@ControllerAdvice 类可以有 @InitBinder 方法来初始化 WebDataBinder 实例,而这些实例进而可以:

  • 将请求参数绑定到模型对象。

  • 将请求值从字符串转换为对象属性类型。

  • 在呈现 HTML 表单时,将模型对象属性格式化为字符串。

@Controller 中,DataBinder 自定义在控制器中局部应用,甚至应用于通过注释按名称引用的特定模型属性。在 @ControllerAdvice 中,自定义可以应用于全部或部分控制器。 你可以在 DataBinder 中注册 PropertyEditorConverterFormatter 组件用于类型转换。或者,你可以在一个全局共享的 FormattingConversionService 中使用 MVC config 注册 ConverterFormatter 组件。 @InitBinder 方法可以具有许多与 @RequestMapping 方法相同的参数,但一个值得注意的例外是 @ModelAttribute。此类方法通常具有一个 WebDataBinder 参数(用于注册)和一个 void 返回值,例如:

Java
@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 Defining an @InitBinder method.
Kotlin
@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))
	}

	// ...
}
2 Defining an @InitBinder method.

或者,当通过共享的 FormattingConversionService 使用基于 Formatter 的设置时,可以重复使用相同的方法并注册特定于控制器的 Formatter 实现,如下例所示:

Java
@Controller
public class FormController {

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

	// ...
}
1 在自定义格式化程序上定义`@InitBinder`方法。
Kotlin
@Controller
class FormController {

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

	// ...
}
2 在自定义格式化程序上定义`@InitBinder`方法。

Model Design

适用于 Web 请求的 Data binding 涉及将请求参数绑定到一个模型对象。默认情况下,请求参数可以绑定到模型对象的任何公共属性,这意味着恶意客户端可以为模型对象图中存在的属性(但不是预期设置的属性)提供额外的值。这就是为什么模型对象设计需要仔细考虑的原因。

模型对象及其嵌套对象的图形有时也称为 command objectform-backing objectPOJO(普通旧 Java 对象)。

一个好做法是使用 专用模型对象,而不是公开你的域模型,例如用于web数据绑定的JPA或Hibernate实体。例如,在更改电子邮件地址的表单上,创建一个仅声明输入所需属性的`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;
	}

}

另一个好习惯是应用 constructor binding,它只使用构造函数参数所需的请求参数,而忽略其他任何输入。这与属性绑定形成对比,后者默认绑定有匹配属性的每个请求参数。

如果使用专用的模型对象或构造函数绑定都不够,而必须使用属性绑定,我们强烈建议为 WebDataBinder 注册 allowedFields 模式(区分大小写),以防止设置意外的属性。例如:

@Controller
public class ChangeEmailController {

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

	// @RequestMapping methods, etc.

}

您还可以注册 disallowedFields 模式(不区分大小写)。但是,“allowed”配置要优于“disallowed”配置,因为它更加明确而且不易出错。

默认情况下,同时使用构造函数和属性绑定。如果您只想使用构造函数绑定,可以在控制器中本地或全局地通过 @ControllerAdviceWebDataBinder 上设置 declarativeBinding 标志。打开此标志可确保仅使用构造函数绑定,并且在未配置 allowedFields 模式的情况下不使用属性绑定。例如:

@Controller
public class MyController {

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

	// @RequestMapping methods, etc.

}