【Spring MVC研究】聊聊web绑定器(WebDataBinder、@InitBinder)

这篇具有很好参考价值的文章主要介绍了【Spring MVC研究】聊聊web绑定器(WebDataBinder、@InitBinder)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


本文主要介绍@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、看一下参数解析器的体系吧
参数解析器的体系如下:非常庞大
webdatabinder,Spring MVC,spring,mvc
通过观察发现有的解析器用到了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 是有相似点的。

3、找到转换器 editor 之后,就开始一步一步执行转换。直到应用到我们定义的@InitBinder定义的“类型转换器”。文章来源地址https://www.toymoban.com/news/detail-813485.html

到了这里,关于【Spring MVC研究】聊聊web绑定器(WebDataBinder、@InitBinder)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Spring Web MVC入门(1)

    定义:Spring Web MVC是基于Servlet构建的原始Web框架, 从一开始就包含在Spring框架中 .它的正式名称\\\"Spring Web MVC\\\"来自其源模块的名称(Spring-webmvc),但是它通常被称为\\\"Spring MVC\\\". 什么是Servlet? Servlet是一种实现动态页面的技术.准确地来讲 Servlet是一套Java Web开发的规范 ,或者是一套Java We

    2024年03月17日
    浏览(54)
  • Spring Web MVC入门(1)

    目录 一、什么是 Spring Web MVC? 二、MVC的定义 三、什么是Spring MVC? Spring Boot 和 Spring MVC的关系: 四、建立连接 1、@RequestMapping注解介绍 @RequestController的作用 2、@RequestMapping的使用 3、@RequestMapping是get还是post请求 (1)GET请求 (2)POST请求 (3)指定GET/POST方法类型 五、Postman的

    2024年04月27日
    浏览(34)
  • 【JavaEE】Spring Web-MVC

    目录 Spring Web MVC 是什么 什么是Serlet 什么是MVC 什么是Spring MVC 使用Spring MVC 建立连接 @RequestMapping 请求 传递单个参数 传递多个参数 传递对象 后端参数重命名  传递数组 传递集合 传递JSON数据 获取url参数-@pathvariable 上传文件@RequestPart 获取Cookie Session 获取Header 响应 返回静态页

    2024年04月12日
    浏览(31)
  • 【SpringMVC】Spring Web MVC入门(一)

    前面我们了解了什么是Spring,那么今天我将为大家分享一种在日常网站开发中使用非常广泛的框架——Spring Web MVC。 先来看看官方解释。 Spring Web MVC是Spring Framework提供的Web组件,它是一个MVC设计模式的框架,主要用于开发灵活、松散耦合的Web应用程序。它提供了模型-视图-控

    2024年02月05日
    浏览(44)
  • 【JavaEE进阶】 初识Spring Web MVC

    Spring Web MVC 是 基于 Servlet API 构建的原始 Web 框架 ,从⼀开始就包含在Spring框架中。它的正式名称“Spring Web MVC”来⾃其源模块的名称(Spring-webmvc),但它通常被称为)Spring MVC). Servlet是⼀种实现动态⻚⾯的技术.准确来讲Servlet是⼀套Java Web 开发的规范,或者说是⼀套Java Web 开发的

    2024年02月03日
    浏览(45)
  • JavaWeb编程面试题——Spring Web MVC

    面试题==知识点,这里所记录的面试题并不针对于面试者,而是将这些面试题作为技能知识点来看待。不以刷题进大厂为目的,而是以学习为目的。这里的知识点会持续更新,目录也会随时进行调整。 关注公众号:编程火箭车 。在【粉丝福利】中点击【面题大全】,其中的

    2024年02月06日
    浏览(40)
  • 【Java EE】初识Spring Web MVC

    Spring Web MVC 是 基于 Servlet API 构建的原始 Web 框架 ,从⼀开始就包含在Spring框架中。它的正式名称“Spring Web MVC”来⾃其源模块的名称(Spring-webmvc),但它通常被称为)Spring MVC). Servlet是⼀种实现动态⻚⾯的技术.准确来讲Servlet是⼀套Java Web 开发的规范,或者说是⼀套Java Web 开发的

    2024年04月10日
    浏览(56)
  • 一次搞清Spring 、Spring Boot、Spring Web MVC、Spring WebFlux

    介绍Spring生态系统,辨析Spring、Spring Boot、Spring Web MVC和Spring WebFlux。 微信搜索关注《Java学研大本营》 在软件开发中,应用框架为代码库提供基础设施支持,使编程更容易。Spring是Java领域最受欢迎的开源应用框架。Spring由多个模块和附加组件组成,术语“Spring”通常用来指代

    2024年02月19日
    浏览(40)
  • 深入探索 Spring MVC:构建优雅的Web应用

    Spring MVC 是 Java 中最受欢迎的 Web 框架之一,它为开发人员提供的强大的工具和灵活的架构,帮助构建高效、可扩展且易于维护的 Web 应用程序。本文将深入探讨 Spring MVC 的核心概念、使用方法以及实践操作。 在了解什么是 Spring MVC之前,我们首先有必要先了解其中的 MVC 到底

    2024年02月13日
    浏览(49)
  • 【SpringMVC】基于 Spring 的 Web 层MVC 框架

    🎄欢迎来到@边境矢梦°的csdn博文🎄 🎄本文主要梳理SpringMVC : 基于 Spring 的 Web 层MVC 框架 🎄 🌈我是边境矢梦°,一个正在为秋招和算法竞赛做准备的学生🌈 🎆喜欢的朋友可以关注一下 🫰🫰🫰 ,下次更新不迷路🎆 Ps: 月亮越亮说明知识点越重要 (重要性或者难度越大

    2024年02月08日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包