本文主要介绍@InitBinder 的用法和原理,用法主要就是在 Controller 的基类上注册一些“属性编辑器”,跟 Spring 的 ConversionService 作用类似。
1. 绑定器的作用
- WebDataBinder 的作用
在WebDataBinder类的注释上描述了,他的作用:“把 request 的请求参数绑定到 JavaBean 对象”。注释说的不是很好懂,翻译一下:
1、首先使用参数解析器从 request 中解析得到“解析器解析后的参数值”。
2、绑定器把“解析器解析后的参数值”转换为“Controller 方法需要的目标值”。
- WebDataBinder 与 conversionService 异同
作者个人理解:WebDataBinder 的作用跟 conversionService� 的转换服务类似。
相同点:
在MVC的绑定参数中,WebDataBinder 调用了conversionService 来进行数据绑定。
不同点:
1、WebDataBinder 专职与“web 数据绑定”。@InitBinder更加适合做"跟web相关的定制化的转换",而ConversionService适合"通用的转换"
2、conversionService 作为 Spring 的转换服务,似乎用途更广。
3、一般使用,@InitBinder 要定义在 Controller 中。
2. 使用方式(测试代码)@InitBinder
如下的测试代码是 Controller 的基类,所有的自定义 Controller 都要求继承这个 Controller**。如果不继承则会失去 BaseController 的绑定器功能。**
public class BaseController
{
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 将前台传递过来的日期格式的字符串,自动转化为Date类型
*/
@InitBinder
public void initBinder(WebDataBinder binder)
{
// Date 类型转换
binder.registerCustomEditor(Date.class, new PropertyEditorSupport()
{
@Override
public void setAsText(String text)
{
setValue(DateUtils.parseDate(text));
}
});
}
}
以上代码完成的功能是:注册了一个原始字符串 text 到 Date 类型的转换服务。
注意:
1、绑定器是定义在基础 Controller 中,BaseController 中
2、如果我们的 Controller 不是继承 BaseController,是不是就失去了字符串 text 到 Date 类型的转换。。。还真有可能是的。。。读者自行尝试。。
。。答案:不会生效。因为根据普通的Controller没有继承BaseController根据Controller的类类型是找不到@InitBinder方法,所以就不会生效。
3. 相关的几个核心类的真实类型
- WebDataBinderFactory(真实类型是ServletRequestDataBinderFactory)
- WebBindingInitializer(真实类型要看RequestMappingHandlerAdapter的创建过程)
纯 MVC 真实类型是:ConfigurableWebBindingInitializer。可能 Spring Boot 有拓展。
- WebDataBinder(真实类型是ExtendedServletRequestDataBinder)
- SimpleTypeConverter�(真实类型是SimpleTypeConverter��)
- PropertyEditorRegistrySupport�(属性编辑器注册表支持)
其中存储了很多自定义属性编辑器
4. 原理
前提:对 DispatcherServlet 的 doDispatcher 方法必须有了解
参考:https://www.yuque.com/yuchangyuan/kkc8mp/hvq3485beg7e4eoz
分析原理的方法是:采用 正向推理
和 反向推理
,如果找到结合点那就推理完成啦!
正向推理:从DispatcherServlet 处理请求的 doDispatcher 方法开始
反向推理:看哪里用到了 InitBinder
注解。
4.1. 正向推理
1、假设读者了解了doDispatcher 方法。既然是数据绑定,即把数据绑定到 Controller 的方法参数上。作用的位置一定是在doDispatcher 流程的处理方法参数中。我们不废话,直接定位到处理参数的代码。org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues
方法。
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
.......
for (int i = 0; i < parameters.length; i++) {
......
// <1> 是否支持参数
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
// <2> 解析器具体的解析参数
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
.......
}
return args;
}
看到在解析器解析参数中用到了 dataBinderFactory
。
2、再看看 dataBinderFactory
的来源是RequestMappingHandlerAdapter类,如下:
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
// getDataBinderFactory方法
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
.....
}
}
3、继续看getDataBinderFactory 方法
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
Class<?> handlerType = handlerMethod.getBeanType();
// <1> 遍历handlerType子类父类接口,看看有没有标注@InitBinder注解
Set<Method> methods = this.initBinderCache.get(handlerType);
if (methods == null) {
methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
this.initBinderCache.put(handlerType, methods);
}
.......
// <2> 根据methods创建initBinderMethods方法
for (Method method : methods) {
Object bean = handlerMethod.getBean();
initBinderMethods.add(createInitBinderMethod(bean, method));
}
return createDataBinderFactory(initBinderMethods);
}
在<1>处,看到了遍历 Controller 所在类的父类父接口,看看有没有标注了@InitBinder注解的方法。
注意:遍历 Controller 及其父类父接口,是不是意味着如果我们的 Controller 不是继承 BaseController,是不是就失去了字符串 text 到 Date 类型的转换。。。还真有可能是的。。。读者自行尝试。。。
在<2>处,把标注了@InitBinder注解的方法都转换为InvocableHandlerMethod,最后存储在了ServletRequestDataBinderFactory类中。
4、正向推理临时先到这里,再看反向推理。
4.2. 反向推理
从@InitBinder的注释上看到了,注释跟WebDataBinder 有关、而WebDataBinder又跟WebDataBinderFactory�有关。
4.3. 正向反向推理结合分析
1、现在来看,正向推理和反向推理的连接点似乎就是WebDataBinderFactory。继续之前的正向过程,this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
这个代码进入到具体的“参数解析器”环节了,不同的解析器情况不同
而“绑定器
dataBinderFactory
”作为一个参数,这不会意味着“解析器可能使用或不使用绑定器”把。
2、看一下参数解析器的体系吧
参数解析器的体系如下:非常庞大
通过观察发现有的解析器用到了dataBinderFactory
,有的没有用到,但是大部分我们常用的解析器都用到了。
注意:有的用到了,有的没用到,是否意味着,绑定器只适用于部分场景。这值得思考。
实际情况:绑定器的涵盖范围广,不仅仅是 web,web 场景给我放心大胆的用
3、我们看常用情况,即AbstractNamedValueMethodArgumentResolver类:
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
.......
// <1> 调用解析器的方法解析得到“参数值”
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
......
// <2> 应用绑定器
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
.......
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
在注释的<1>处,先调用“参数解析器”解析得到了“参数值”。
在注释的<2>处,转换参数值的类型为目标类型,此处充分体现了 WebDataBinder 的作用是“转换参数为目标类型”
4.4. 重点来了(如果前后衔接是接4.3章节)
4.1、4.2、4.3 章节属于交代前因后果,属于补充上下文。
4.4 章节属于纯粹聊@InitBinder 注解。
4.4.1. @InitBinder注解的注册
继续看AbstractNamedValueMethodArgumentResolver 类的WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
代码
- createBinder 方法(ServletRequestDataBinderFactory 类)
1、binderFactory 的实际类型是ServletRequestDataBinderFactory,这点是从 4.1 章节知道的。
public final WebDataBinder createBinder(
NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {
// <1> 返回ExtendedServletRequestDataBinder
WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
if (this.initializer != null) {
// initializer类型是:ConfigurableWebBindingInitializer
this.initializer.initBinder(dataBinder, webRequest);
}
// <3> 注册BaseController 中的注解
initBinder(dataBinder, webRequest);
return dataBinder;
}
在<1>处,返回的实际类型是ExtendedServletRequestDataBinder。
在<2>处,其实也没有做什么
在<3>处,注册 BaseController 中的注解。还记得在 4.1 章节解析到的@InitBinder注解的信息存储在ServletRequestDataBinderFactory 吗???
2、看initBinder 方法(ServletRequestDataBinderFactory 类)
public void initBinder(WebDataBinder dataBinder, NativeWebRequest request) throws Exception {
for (InvocableHandlerMethod binderMethod : this.binderMethods) {
if (isBinderMethodApplicable(binderMethod, dataBinder)) {
// 执行binderMethod
Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder);
if (returnValue != null) {
throw new IllegalStateException(
"@InitBinder methods must not return a value (should be void): " + binderMethod);
}
}
}
}
执行binderMethod 方法,此时的binderMethod 方法就是@InitBinder注解的方法。直接执行。
3、执行注解的方法(binder 的类型是ExtendedServletRequestDataBinder)
@InitBinder
public void initBinder(WebDataBinder binder)
{
// Date 类型转换
binder.registerCustomEditor(Date.class, new PropertyEditorSupport()
{
@Override
public void setAsText(String text)
{
setValue(DateUtils.parseDate(text));
}
});
}
@Override
public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
getPropertyEditorRegistry().registerCustomEditor(requiredType, propertyEditor);
}
target 是 null 呀。返回类型SimpleTypeConverter。
4、然后调用registerCustomEditor注册方法
@Override
public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
registerCustomEditor(requiredType, null, propertyEditor);
}
@Override
public void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor) {
if (requiredType == null && propertyPath == null) {
throw new IllegalArgumentException("Either requiredType or propertyPath is required");
}
if (propertyPath != null) {
if (this.customEditorsForPath == null) {
this.customEditorsForPath = new LinkedHashMap<>(16);
}
this.customEditorsForPath.put(propertyPath, new CustomEditorHolder(propertyEditor, requiredType));
}
else {
if (this.customEditors == null) {
this.customEditors = new LinkedHashMap<>(16);
}
// 注册
this.customEditors.put(requiredType, propertyEditor);
this.customEditorCache = null;
}
}
@InitBinder注解被注册到了this.customEditors。
4.4.2. 执行参数绑定
接着AbstractNamedValueMethodArgumentResolver 的arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);�
代码执行 Controller 方法参数的绑定。
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
@Nullable MethodParameter methodParam) throws TypeMismatchException {
return getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
}
1、getTypeConverter()方法返回 4.1 章节的SimpleTypeConverter。
SimpleTypeConverter —> TypeConverterDelegate
2、执行convertIfNecessary 方法(委派给TypeConverterDelegate 类了)
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
// Custom editor for this type?
// <1> 获取到 注册的自定义属性编辑器
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
ConversionFailedException conversionAttemptEx = null;
// No custom editor but custom ConversionService specified?
// <2> 应用spring的ConversionService服务
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
try {
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
catch (ConversionFailedException ex) {
// fallback to default conversion logic below
conversionAttemptEx = ex;
}
}
}
.......
.......
}
上面的代码职责是整个 Spring 的“类型转换”。
在<1>处,获取到 4.4.1 章节注册到的自定义类型转换。即从SimpleTypeConverter的 this.customEditors 获取。跟 4.4.1 章节对应起来了。
在<2>处,如果没有自定义的属性编辑器editor,就用 Spring 提供的ConversionService。spring 默认提供了很多种“类型转换器”。
备注:从这一点也可以知道。WebDataBinder 跟 ConversionService 是有相似点的。文章来源:https://www.toymoban.com/news/detail-813485.html
3、找到转换器 editor 之后,就开始一步一步执行转换。直到应用到我们定义的@InitBinder定义的“类型转换器”。文章来源地址https://www.toymoban.com/news/detail-813485.html
到了这里,关于【Spring MVC研究】聊聊web绑定器(WebDataBinder、@InitBinder)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!