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

news2024/9/25 11:16:17

文章目录

  • 1. 绑定器的作用
  • 2. 使用方式(测试代码)`@InitBinder`
  • 3. 相关的几个核心类的真实类型
  • 4. 原理
    • 4.1. 正向推理
    • 4.2. 反向推理
    • 4.3. 正向反向推理结合分析
    • 4.4. 重点来了(如果前后衔接是接4.3章节)
      • 4.4.1. @InitBinder注解的注册
      • 4.4.2. 执行参数绑定

本文主要介绍@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、看一下参数解析器的体系吧
参数解析器的体系如下:非常庞大
image.png
通过观察发现有的解析器用到了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定义的“类型转换器”。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1074458.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue学习-12路由组件的基本使用

vue的路由是vue,js官方的路由管理器&#xff0c;其主要用于构建单页应用程序&#xff0c;允许你通过定义路由来管理不同页面之间的导航。 1.引入路由 在使用vue的路由之前&#xff0c;一般我们在创建vue项目的时候&#xff0c;是可以选择添加路由的&#xff0c;只要你选择了y…

antd树型表格的逐级展开折叠(每次展开都只展开到当前未展开的最小层级,每次折叠都只折叠到当前未折叠的最大层级)

需求有些变态&#xff0c;我们用一段话和一张图来演示下 效果如下&#xff1a; 如遇到每级展开层级不一致的&#xff0c;如【2级2】展开到第3级&#xff0c;那此时点击展开&#xff0c;所有已展开的不动&#xff0c;将未展开到第3级的其他元素全部展开到第3级 效果如下&…

东土科技与诺贝尔物理学奖2006年度得主斯穆特签约,加快布局工业AI

近日&#xff0c;诺贝尔物理学奖2006年度得主乔治.斯穆特教授与东土科技正式签约&#xff0c;成为东土科技工业人工智能顾问。 乔治斯穆特&#xff08;George Fitzgerald Smoot&#xff09;教授也曾获得爱因斯坦奖&#xff0c;在宇宙学、大数据、生物医学诊断仪器以及人工智能…

第一章:随机过程预备知识

第一章&#xff1a;随机过程预备知识 随机过程属于概率论的分支学科。概率论注重结果&#xff1a;上涨的概率&#xff0c;下跌的概率。随机过程注重过程&#xff0c;随着时间的推移&#xff0c;结果的演化过程。 1.1 随机事件与概率的定义 事件的本质是集合&#xff0c;有关集…

workerman的基本用法(示例详解)

workerman是什么&#xff1f; Workerman是一个异步事件驱动的PHP框架&#xff0c;具有高性能&#xff0c;可轻松构建快速&#xff0c;可扩展的网络应用程序。支持HTTP&#xff0c;Websocket&#xff0c;SSL和其他自定义协议。支持libevent&#xff0c;HHVM&#xff0c;ReactPH…

朋友一口气拿下字节27K的offer,实名羡慕了....

最近有朋友去字节面试&#xff0c;面试前后进行了20天左右&#xff0c;包含4轮电话面试、1轮笔试、1轮主管视频面试、1轮hr视频面试。 据他所说&#xff0c;80%的人都会栽在第一轮面试&#xff0c;要不是他面试前做足准备&#xff0c;估计都坚持不完后面几轮面试。 其实&…

DRM全解析 —— framebuffer详解(1)

本文参考以下博文&#xff1a; Linux内核4.14版本——drm框架分析(1)——drm简介 特此致谢&#xff01; 1. 简介 framebuffer是一块内存区域&#xff0c;可以理解为一块画布&#xff0c;驱动和应用层都能访问它。绘制前需要将它格式化&#xff0c;设定绘制的色彩模式&#x…

leetcode:217. 存在重复元素(python3解法)

难度&#xff1a;简单 给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 &#xff0c;返回 true &#xff1b;如果数组中每个元素互不相同&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3,1] 输出&#xff1a;true 示例 2&#xff1a; 输…

DruidDataSource导致OOM问题处理

DruidDataSource导致OOM问题处理 起因分析日志分析Dump文件问题分析处理 起因 一个平凡的工作日&#xff0c;我像往常一样完成产品提出的需求的业务代码&#xff0c;突然收到了监控平台发出的告警信息。本以为又是一些业务上的 bug 导致的报错&#xff0c;一看报错发现日志写着…

Deepwalk,Node2vec算法原理生动理解(图文)

Deepwalk算法原理详解 DeepWalk算法之所以能够有效地学习节点的低维表示&#xff0c;是因为它利用了本质上与自然语言处理相同的思路&#xff1a;图是一种高维数据&#xff0c;很难直接处理&#xff0c;但是可以将其映射到低维空间中&#xff0c;这样可以更好地进行处理 Deep…

ElasticSearch 使用 searchAfter() 进行遍历查询 查到的数据总数小于 totalHits

ElasticSearch 使用 searchAfter() 进行遍历查询&#xff0c;查到的数据总数小于 totalHits&#xff0c;并且每次查询的页 size 越大&#xff0c;遍历总数和 totalHits 的差距越小。 原因 这是由于如下的机制&#xff1a; 每个文档具有一个唯一值的字段应该用作排序规范的仲裁…

2023 IDC中国数字金融论坛丨中电金信向行业分享“源启+应用重构”新范式

9月8日&#xff0c;IDC主办的“2023 IDC中国数字金融论坛”在北京召开。中电金信受邀参会&#xff0c;并带来了深度数字化转型趋势之下关于应用重构的分享与洞见。 论坛重点关注金融科技创新发展趋势与数字化转型之路&#xff0c;中电金信副总经理、研究院院长况文川带来了“创…

多无人机编队集群飞行

matlab2016b可直接运行 多无人机集群编队飞行&#xff08;8架无人机&#xff09;资源-CSDN文库

S7-1200PLC与力控通过S7协议进行通信的具体步骤示例

S7-1200PLC与力控通过S7协议进行通信的具体步骤示例 准备条件: TIA PORTAL V16 力控7.2 SP3 PLC:1214 DC/DC/DC PLC一侧的配置: PLC IP设置为192.168.2.10 PLC属性中的连接机制,勾选允许来自远程对象的PUT/GET 新建一个名为FirstDB的数据块,数据块编号为1 在FirstDB中添加…

大屏设计器项目部署详细步骤

一.项目效果图 二.部署步骤 1.nginx配置前端配置 #gzip on;server {listen 48009;server_name analyse;location / {root /home/designer/dist;index index.html;try_files $uri

vue中v-model的原理是什么?v-model作用在组件上的原理是什么?sync修饰符的原理是什么?

vue中v-model的原理是什么&#xff1f; 特点&#xff1a;双向绑定 数据>视图 视图>数据 场景&#xff1a; 收集表单数据组件上 原理&#xff1a; v-model只是个语法题&#xff0c;本质是&#xff1a;v-model v-bind (:value) v-on (input) <template><…

蓝牙资讯|三星推迟发布智能戒指Galaxy Ring,智能穿戴小型化是大趋势

根据外媒 The Elec 报道&#xff0c;Galaxy Ring这款戒指主要面向健康和 XR 头显市场&#xff0c;该智能戒指可能被延期至 2024 年第三季度后发布。 外媒声称三星 Galaxy Ring 的上市周期&#xff0c;主要取决医疗认证的相关审批时间&#xff0c;三星计划将在 2024 年第三季度…

2023年软件测试工具总结 —— 性能测试工具

软件性能测试的目标是识别应用程序中的所有性能瓶颈。一个软件系统的性能不仅取决于系统本身的设计和编码&#xff0c;而且取决于系统所依赖的运行环境。系统的运行环境会依赖于一些关键因素&#xff0c;例如&#xff1a;系统架构、硬件配置、网络带宽、配套的软件如数据库和中…

java多线程卖电影票的三种实现方式

java多线程卖电影票的三种实现方式 一、需求描述二、实现方式1、继承Thread类的方式2、实现Runnable接口的方式3、使用Lock锁的方式 一、需求描述 某电影院目前正在上映国产大片&#xff0c;共有1000张票&#xff0c;而它有2个窗口卖票&#xff0c;请设计一个程序模拟该电影院…

12.3 实现模拟鼠标录制回放

本节将向读者介绍如何使用键盘鼠标操控模拟技术&#xff0c;键盘鼠标操控模拟技术是一种非常实用的技术&#xff0c;可以自动化执行一些重复性的任务&#xff0c;提高工作效率&#xff0c;在Windows系统下&#xff0c;通过使用各种键盘鼠标控制函数实现动态捕捉和模拟特定功能的…