@InitBinder注解會(huì)用嗎?該如何使用?
環(huán)境:SpringBoot2.7.16
1. 簡介
@Controller 或 @ControllerAdvice 類可以使用 @InitBinder 注解標(biāo)注方法來初始化 WebDataBinder 實(shí)例,WebDataBinder對(duì)象主要用來做數(shù)據(jù)綁定操作;具體使用了@InitBinder注解的方法可以做如下事情:
- 將請(qǐng)求參數(shù)(即表單或查詢數(shù)據(jù))綁定到模型對(duì)象。
- 將基于字符串的請(qǐng)求值(如請(qǐng)求參數(shù)、路徑變量、頭信息、cookie 等)轉(zhuǎn)換為控制器方法參數(shù)的目標(biāo)類型。
- 在呈現(xiàn) HTML 表單時(shí),將模型對(duì)象值格式化為字符串值。
@InitBinder 方法可以注冊(cè)特定控制器的 java.beans.PropertyEditor 或 Spring Converter 和 Formatter 組件。此外,還可以使用 MVC 配置在全局共享的 FormattingConversionService 中注冊(cè)轉(zhuǎn)換器和格式器類型。
2. 定義初始化DataBinder
被@InitBinder標(biāo)注的方法的返回值必須是void。否則將拋出異常。
@InitBinder
public void initBinder(WebDataBinder binder) {
// ...
}
源碼分析:
以有如下簡單的接口參數(shù)解析綁定進(jìn)行分析
@GetMapping("/index")
public Object index(Integer id) {
//
}
當(dāng)請(qǐng)求/index?id=666時(shí)Spring MVC會(huì)通過參數(shù)解析器RequestParamMethodArgumentResolver進(jìn)行參數(shù)類型的轉(zhuǎn)換及綁定(接收到的'666'是字符串將轉(zhuǎn)換為Integer)。
public class RequestParamMethodArgumentResolver ...{}
// 調(diào)用父類的方法
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);
}
// 初始化綁定(執(zhí)行所有被@InitBinder注解的方法)
initBinder(dataBinder, webRequest);
return dataBinder;
}
}
// 綁定工廠實(shí)現(xiàn)類
public class InitBinderDataBinderFactory extends DefaultDataBinderFactory {
public void initBinder(WebDataBinder dataBinder, NativeWebRequest request) throws Exception {
// 這里的每一個(gè)InvocableHandlerMethod都表示被@InitBinder標(biāo)準(zhǔn)的方法對(duì)象
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. 實(shí)戰(zhàn)案例
3.1 在Controller中定義
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws IllegalArgumentException {
// 為了看到效果,這里吧轉(zhuǎn)換后的值進(jìn)行+1操作
setValue(Integer.parseInt(text) + 1) ;
}
}) ;
}
定義Controller接口
@GetMapping("/index")
public String index(Integer id) {
return "轉(zhuǎn)換后id: " + id ;
}
輸出結(jié)果
圖片
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只會(huì)針對(duì)com.pack.service包中的Controller生效。
圖片
3.3 注冊(cè)為全局共享
可以使用 MVC 配置在全局共享的 FormattingConversionService 中注冊(cè)轉(zhuǎn)換器和格式器類型。
默認(rèn)情況下SpringBoot容器會(huì)注冊(cè)一個(gè)默認(rèn)的全局類型轉(zhuǎn)換器,如下:
@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;
}
也就是說我們可以吧類型轉(zhuǎn)換直接注冊(cè)到上面的FormattingConversionService 中。
@InitBinder
public void initBinder(WebDataBinder binder) {
FormattingConversionService fcs = (FormattingConversionService) binder.getConversionService();
fcs.addConverter(new Converter<String, Integer>() {
@Override
public Integer convert(String source) {
return Integer.parseInt(source) + 1 ;
}
}) ;
}
這樣注冊(cè)以后,我們可以在任意的地方從容器中獲取到ConversionService實(shí)例就可以使用到上面自定義的轉(zhuǎn)換服務(wù)。