环境:SpringBoot2.7.16
1. 简介
@Controller 或 @ControllerAdvice 类可以使用 @InitBinder 注解标注方法来初始化 WebDataBinder 实例,WebDataBinder对象主要用来做数据绑定操作;具体使用了@InitBinder注解的方法可以做如下事情:
- 将请求参数(即表单或查询数据)绑定到模型对象。
- 将基于字符串的请求值(如请求参数、路径变量、头信息、cookie 等)转换为控制器方法参数的目标类型。
- 在呈现 HTML 表单时,将模型对象值格式化为字符串值。
@InitBinder 方法可以注册特定控制器的 java.beans.PropertyEditor 或 Spring Converter 和 Formatter 组件。此外,还可以使用 MVC 配置在全局共享的 FormattingConversionService 中注册转换器和格式器类型。
2. 定义初始化DataBinder
被@InitBinder标注的方法的返回值必须是void。否则将抛出异常。
@InitBinder
public void initBinder(WebDataBinder binder) {
// ...
}
源码分析:
以有如下简单的接口参数解析绑定进行分析
@GetMapping("/index")
public Object index(Integer id) {
//
}
当请求/index?id=666时Spring MVC会通过参数解析器RequestParamMethodArgumentResolver进行参数类型的转换及绑定(接收到的'666'是字符串将转换为Integer)。
public class RequestParamMethodArgumentResolver ...{}
// 调用父类的方法
public abstract class AbstractNamedValueMethodArgumentResolver {
public final Object resolveArgument(...) {
// ...
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
}
// ...
}
}
// 绑定工厂
public class DefaultDataBinderFactory {
public final WebDataBinder createBinder(...) {
WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
if (this.initializer != null) {
this.initializer.initBinder(dataBinder, webRequest);
}
// 初始化绑定(执行所有被@InitBinder注解的方法)
initBinder(dataBinder, webRequest);
return dataBinder;
}
}
// 绑定工厂实现类
public class InitBinderDataBinderFactory extends DefaultDataBinderFactory {
public void initBinder(WebDataBinder dataBinder, NativeWebRequest request) throws Exception {
// 这里的每一个InvocableHandlerMethod都表示被@InitBinder标准的方法对象
for (InvocableHandlerMethod binderMethod : this.binderMethods) {
if (isBinderMethodApplicable(binderMethod, dataBinder)) {
Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder);
// 如果存在返回值将抛出异常
if (returnValue != null) {
throw new IllegalStateException(
"@InitBinder methods must not return a value (should be void): " + binderMethod);
}
}
}
}
}
3. 实战案例
3.1 在Controller中定义
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws IllegalArgumentException {
// 为了看到效果,这里吧转换后的值进行+1操作
setValue(Integer.parseInt(text) + 1) ;
}
}) ;
}
定义Controller接口
@GetMapping("/index")
public String index(Integer id) {
return "转换后id: " + id ;
}
输出结果
图片
3.2 在ControllerAdvice中定义
在@ControllerAdvice中定义的@InitBinder方法将在所有或部分Controller中生效。
@ControllerAdvice()
public class BinderControllerAdvice {
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Integer.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws IllegalArgumentException {
System.out.println("原始值:" + text) ;
setValue(Integer.parseInt(text) + 1) ;
}
}) ;
}
}
为什么说也可以是部分Controller呢?
在@ControllerAdvice注解的属性中我们可以通过配置包,类及注解等信息。
@ControllerAdvice(basePackages = {"com.pack.service"})
public class BinderControllerAdvice {
// ...
}
如上配置后,该类中的所有@InitBinder只会针对com.pack.service包中的Controller生效。
图片
3.3 注册为全局共享
可以使用 MVC 配置在全局共享的 FormattingConversionService 中注册转换器和格式器类型。
默认情况下SpringBoot容器会注册一个默认的全局类型转换器,如下:
@Bean
@Override
public FormattingConversionService mvcConversionService() {
Format format = this.mvcProperties.getFormat();
WebConversionService conversionService = new WebConversionService(
new DateTimeFormatters().dateFormat(format.getDate())
.timeFormat(format.getTime())
.dateTimeFormat(format.getDateTime()));
addFormatters(conversionService);
return conversionService;
}
也就是说我们可以吧类型转换直接注册到上面的FormattingConversionService 中。
@InitBinder
public void initBinder(WebDataBinder binder) {
FormattingConversionService fcs = (FormattingConversionService) binder.getConversionService();
fcs.addConverter(new Converter() {
@Override
public Integer convert(String source) {
return Integer.parseInt(source) + 1 ;
}
}) ;
}
这样注册以后,我们可以在任意的地方从容器中获取到ConversionService实例就可以使用到上面自定义的转换服务。