@InitBinder注解会用吗?该如何使用?

2024年 3月 7日 66.3k 0

环境: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实例就可以使用到上面自定义的转换服务。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论