从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

    • SpringBoot 教程-尚硅谷

      • SpringBoot 课程介绍
      • Spring 和 SpringBoot
      • HelloWorld
      • 了解自动配置原理
      • 底层注解-@Configuration 详解
      • 底层注解-@Import 导入组件
      • 底层注解-@Conditional 条件装配
      • 原生配置文件引入-@ImportResource
      • 底层注解-配置绑定 @ConfigurationProperties
      • 自动配置原理
      • 自动配置流程
      • Lombok 简化开发
      • DevTools
      • Spring-Initailizr
      • 配置文件-Yaml 用法
      • Web 开发简介
      • web 开发-静态资源规则于定制化
      • 静态资源配置原理
      • Rest 映射及源码解析
      • 请求映射原理
      • 常用参数注解使用
      • MatrixVariable:矩阵变量
      • 各种类型参数解析原理
      • Servlet-API 参数解析原理
      • Model、Map 参数解析原理
      • 自定义对象参数绑定原理
        • 新增 JavaBean
        • 新增表单
        • 新增 controller
        • debug
        • converter
        • 设置值
        • 最后
      • 自定义 Converter 原理
      • 数据响应原理
      • 内容协商原理
      • 基于请求参数的内容原理
      • 自定义 MessageConverter 原理
      • Thymeleaf 初体验
      • web 实验-后台管理系统
      • web 实验-抽取公共页面
      • web 实验-遍历数据
      • 源码分析-视图解析器与视图
      • 拦截器-登录检查与静态资源放行
      • 拦截器的执行时机和原理
      • 单文件和多文件上传的使用
      • 文件上传原理
      • 错误处理机制
      • 错误处理-底层组件源码分析
      • 异常处理流程
      • 几种异常处理原理
      • Web 原生对象注入
      • 嵌入式 Servlet 容器
      • 定制化原理
      • 数据库场景的自动配置分析和整合测试
      • 自定义方式整合 Druid
      • 通过 starter 整合 Druid
      • 整合 Mybatis
      • 使用注解整合 Mybatis
      • 整合 MybatisPlus 操作数据库
      • MybatisPlus-列表分页展示
      • 整合 Redis
      • 单元测试-Junit5
      • 单元测试-断言机制
      • 单元测试-前置条件
      • 单元测试-嵌套测试
      • 单元测试-参数化测试
      • 指标监控-基本概念
      • 指标监控-配置 EndPoint
      • 指标监控-可视化
      • 原理解析-Profile 功能
      • 配置文件深入
      • 自定义 Starter
      • SpringApplication 初始化过程
      • SpringBoot 完整启动过程
  • Java
  • SpringBoot
  • SpringBoot 教程-尚硅谷
2023-08-22
目录

自定义对象参数绑定原理

# 260.自定义对象参数绑定原理

我们可以给方法传入一个自定义的类,然后 SpringBoot 会自动帮我们绑定;接下来就讲讲其原理

‍

# 新增 JavaBean

我们新增两个类,用来演示。

package com.peterjxl.boot.bean;
import lombok.Data;

@Data
public class Pet2 {
    private String name;
    private int age;
}
1
2
3
4
5
6
7
8
package com.peterjxl.boot.bean;
import lombok.Data;
import java.util.Date;

@Data
public class Person2 {
    private String userName;
    private Integer age;
    private Date birth;
    private Pet2 pet;
}
1
2
3
4
5
6
7
8
9
10
11

# 新增表单

新建 resources/haha/customClass.html,文件内容:

<!doctype html>
<html lang="zh">
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
    </head>
    <body>
        测试复杂类型(封装POJO)<br/>
        <form action="/saveUser" method="post">
            姓名:<input name="userName" value="zhangsan"><br/>
            年龄:<input name="age" value="18"><br/>
            生日:<input name="birth" value="2022/05/20"><br/>
            宠物姓名:<input name="pet.name" value="阿猫"><br/>
            宠物年龄:<input name="pet.age" value="5"><br/>
            <input type="submit" value="保存"><br/>
        </form>
    </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

‍

# 新增 controller

在 ParameterTestController 类中新增方法:

@PostMapping("/saveUser")
public Person2 saveUser(Person2 person2) {
    return person2;
}
1
2
3
4

‍ 重启项目,访问 localhost: 8888/customClass.html (opens new window),点击提交,可以看到能返回 person2 对象

‍

# debug

我们在 DispatcherServlet 类中的 doDispatch 方法上打个断点,然后以 debug 的方式启动,观察封装的过程。 ‍ 来到 doDispatch 方法后,我们执行到这行代码,然后步入进去:

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
1

‍ 然后来到 AbstractHandlerMethodAdapter 类的 handleInternal 方法,再步入:

public final ModelAndView handle(
    HttpServletRequest request, 
    HttpServletResponse response, Object handler) throws Exception {
	return handleInternal(request, response, (HandlerMethod) handler);
}
1
2
3
4
5

然后来到 RequestMappingHandlerAdapter 类的 handleInternal 方法,执行到该行代码(787 行左右)并步入:

mav = invokeHandlerMethod(request, response, handlerMethod);
1

‍ 然后来到 RequestMappingHandlerAdapter 类的 invokeHandlerMethod 方法,这个方法就是初始化的,例如参数解析器,mavContainer 等数据,然后我们执行到第 878 行,并步入:

invocableMethod.invokeAndHandle(webRequest, mavContainer);
1

然后来到 invokeAndHandle 方法:

public void invokeAndHandle( ServletWebRequest webRequest,   ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    setResponseStatus(webRequest);
    // .....
1
2
3
4
5

第一行就是调用我们的处理器方法,然后会拿到方法的返回值,因此封装参数就是在 invokeForRequest 里完成的 ‍ invokeForRequest 源码如下:

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
	if (logger.isTraceEnabled()) {
		logger.trace("Arguments: " + Arrays.toString(args));
	}
	return doInvoke(args);
}
1
2
3
4
5
6
7
8
9

‍ 我们步入到 getMethodArgumentValues 方法:

首先会获取到所有参数,其中包含了返回值类型和参数类型 ‍ 接下来就是判断是否支持解析当前的参数类型,我们步入进去:

然后会发现其调用的是 getArgumentResolver 方法:

@Override
public boolean supportsParameter(MethodParameter parameter) {
	return getArgumentResolver(parameter) != null;
}
1
2
3
4

我们再次步入进去,可以看到里面是一个 for 循环,也就是遍历所有参数解析器,观察是否支持当前参数。

@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    if (result == null) {
	for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
		if (resolver.supportsParameter(parameter)) {
			result = resolver;
			this.argumentResolverCache.put(parameter, result);
			break;
		}
	}
    }
    return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

我们打断点到最后一行,可以看到返回的参数解析器是 ServletModelAttributeMethodProcessor:

‍

也就是我们自定义类型的参数,是由该解析器解析的。那么其是如何判断是否支持自定义类型的呢?我们重新打个断点到第 133 行,然后步入到 supportsParameter 方法中:

@Override
public boolean supportsParameter(MethodParameter parameter) {
	return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
				(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
1
2
3
4
5

‍ 可以看到是一个判断,首先判断是否用了 ModelAttribute 注解,这里我们没有用

然后判断是否必输的,这里也不是必输;

然后判断是不是非简单属性(BeanUtils.isSimpleProperty),是则返回 true

所谓的简单类型,就是这样判断的:逐个判断是否 Java 的基本类型。

public static boolean isSimpleValueType(Class<?> type) {
    return (Void.class != type && void.class != type &&
		(ClassUtils.isPrimitiveOrWrapper(type) ||
		Enum.class.isAssignableFrom(type) ||
		CharSequence.class.isAssignableFrom(type) ||
		Number.class.isAssignableFrom(type) ||
		Date.class.isAssignableFrom(type) ||
		Temporal.class.isAssignableFrom(type) ||
		URI.class == type ||
		URL.class == type ||
		Locale.class == type ||
		Class.class == type));
}
1
2
3
4
5
6
7
8
9
10
11
12
13

‍ 知道怎么判断是否支持方法的参数类型后,接下来就是赋值了,我们来步入:

resolveArgument 方法源码如下:

@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    if (resolver == null) {
	throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
    }
    return resolver.resolveArgument(parameter, mavContainer,webRequest, binderFactory);
}
1
2
3
4
5
6
7
8
9

‍ 我们步入到最后一行,该方法会帮我们创建一个空的 Person2 实例(成员变量都是 null)

接下来比较关键的就是绑定数据了。我们继续执行下一步,会看到创建了一个绑定器(第 4 行),传入的参数有 request 对象,attribute(也就是 Person2 的空对象):

if (bindingResult == null) {
    // Bean property binding and validation;
    // skipped in case of binding failure on construction.
    WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
    if (binder.getTarget() != null) {
	if (!mavContainer.isBindingDisabled(name)) {
		bindRequestParameters(binder, webRequest);
	}
	validateIfApplicable(binder, parameter);
	if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
		throw new BindException(binder.getBindingResult());
	}
    }
    // Value type adaptation, also covering java.util.Optional
    if (!parameter.getParameterType().isInstance(attribute)) {
	attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
    }
    bindingResult = binder.getBindingResult();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

‍

# converter

在 binder 中,还有很多的转换器(converter):

因为 HTTP 是超文本传输协议,传的都是文本,我们需要将这些文本转为 Java 中的数据类型。如果是文件上传请求,还有将 byte 转为 File 类型的转换器。我们后续也可以自己添加 Converter。

接下来我们看到第 7 行的代码:

bindRequestParameters(binder, webRequest);
1

这段代码就是绑定 request 中的数据,到 person 对象的,我们步入 bind 方法:

@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
	ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
	Assert.state(servletRequest != null, "No ServletRequest");
	ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
	servletBinder.bind(servletRequest);
}
1
2
3
4
5
6
7

‍ bind 方法源码:

public void bind(ServletRequest request) {
    MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
    MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
    if (multipartRequest != null) {
	bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
    }
    addBindValues(mpvs, request);
    doBind(mpvs);
}
1
2
3
4
5
6
7
8
9

第二行:获取 request 域中的所有参数,以键值对的方式保存到 mpvs 变量中:

‍

然后的 if 判断是判断是否文件上传请求,这里不是,因此忽略;然后 addBindValues 就是一些初始化的功能,我们不表。我们步入最后一行 dobind 方法中:

@Override
protected void doBind(MutablePropertyValues mpvs) {
	checkFieldDefaults(mpvs);
	checkFieldMarkers(mpvs);
	super.doBind(mpvs);
}
1
2
3
4
5
6

该方法会调用 2 个检查的方法,最后调用父类的 dobind 方法:

protected void doBind(MutablePropertyValues mpvs) {
	checkAllowedFields(mpvs);
	checkRequiredFields(mpvs);
	applyPropertyValues(mpvs);
}
1
2
3
4
5

‍ 我们步入到 applyPropertyValues 方法:

protected void applyPropertyValues(MutablePropertyValues mpvs) {
    try {
    	// Bind request parameters onto target object.
    	getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
    }
    catch (PropertyBatchUpdateException ex) {
    	// Use bind error processor to create FieldErrors.
    	for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
        	getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
	}
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

‍ 然后我们步入 setPropertyValues 方法:

‍

可以看到有一个 for 循环,其中有个 setPropertyValue 方法,我们步入进去:

‍

首先会通过反射,获取到 BeanWrapper 对象;然后调用 setPropertyValue 方法,我们步入进去:

protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
    if (tokens.keys != null) {
	processKeyedProperty(tokens, pv);
    }
    else {
	processLocalProperty(tokens, pv);
    }
}
1
2
3
4
5
6
7
8

‍ 然后我们再步入 processLocalProperty 方法:在该方法中,会调用 convertForProperty 方法,因为我们 HTTP 是超文本传输协议,需要转换器,转为 Java 中的 Integer 类型

‍

我们步入进去:

@Nullable
protected Object convertForProperty(String propertyName, @Nullable Object oldValue, @Nullable Object newValue, TypeDescriptor td) throws TypeMismatchException {

    return convertIfNecessary(propertyName, oldValue, newValue, td.getType(), td);
}
1
2
3
4
5

‍ 再步入 convertIfNecessary 方法:

@Nullable
private Object convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<?> requiredType, @Nullable TypeDescriptor td) throws TypeMismatchException {

    Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");
    try {
        return this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, requiredType, td);
    }
    //....
1
2
3
4
5
6
7
8

然后第 6 行就是返回转换后的变量,propertyName 是变量名,这里是 age;oldvalue 是旧的值,newValue 是转换后的值。我们步入进去 ‍ 接下来,看到 typeConverterDelegate.convertIfNecessary 方法:首先会判断能否进行转换

‍

canConvert 源码如下:可以看到第 7 行 getConverter 方法,是通过传入 sourceType(原始的数据类型,这里是 String)和 targetType(要转换的数据类型,这里是 Integer)两个参数,来获取一个转换器;如果转换器不为空,则说明支持转换,因此我们步入到 getConverter 方法

@Override
public boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
	Assert.notNull(targetType, "Target type to convert to cannot be null");
	if (sourceType == null) {
		return true;
	}
	GenericConverter converter = getConverter(sourceType, targetType);
	return (converter != null);
}
1
2
3
4
5
6
7
8
9

‍ getConverter 方法如下:

@Nullable
protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
	ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);
	GenericConverter converter = this.converterCache.get(key);
	if (converter != null) {
		return (converter != NO_MATCH ? converter : null);
	}

	converter = this.converters.find(sourceType, targetType);
	if (converter == null) {
		converter = getDefaultConverter(sourceType, targetType);
	}

	if (converter != null) {
	this.converterCache.put(key, converter);
		return converter;
	}

	this.converterCache.put(key, NO_MATCH);
	return null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

首先会从缓存中找,找到了就返回;当然,我们项目刚启动,缓存是没有数据的,因此调用 converters.find 方法来找,converters 就是我们之前说的所有转换器(124 个)。

步入 find 方法,此时会来到 GenericConversionService 类里 find 方法:

@Nullable
public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) {
	// Search the full type hierarchy
	List<Class<?>> sourceCandidates = getClassHierarchy(sourceType.getType());
	List<Class<?>> targetCandidates = getClassHierarchy(targetType.getType());
	for (Class<?> sourceCandidate : sourceCandidates) {
	    for (Class<?> targetCandidate : targetCandidates) {
	           ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate);
		    GenericConverter converter = getRegisteredConverter(sourceType, targetType, convertiblePair);
		    if (converter != null) {
			return converter;
		    }
	    }
	}
	return null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

可以看到是一个遍历循环 converter,判断是否支持当前的类型转换。我们执行多几次,可以看到能找到一个转换器:也就是 String 类型转 Number 类型的

‍

拿到转换器,下一步就是转换值了。我们一点点的步出,直到调用 convert 方法:

convert 方法源码:

@Override
@Nullable
public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
	Assert.notNull(targetType, "Target type to convert to cannot be null");

	if (sourceType == null) {
		Assert.isTrue(source == null, "Source must be [null] if source type == [null]");
		return handleResult(null, targetType, convertNullSource(null, targetType));
	}

	if (source != null && !sourceType.getObjectType().isInstance(source)) {
		throw new IllegalArgumentException("Source to convert from must be an instance of [" +sourceType + "]; instead it was a [" + source.getClass().getName() + "]");
	}

	GenericConverter converter = getConverter(sourceType, targetType);

	if (converter != null) {
		Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
		return handleResult(sourceType, targetType, result);
	}

	return handleConverterNotFound(source, sourceType, targetType);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

之前的代码都是一些校验 null 的,注意第 18 行,就是调用工具类的方法去转换,我们步入进去:

@Nullable
public static Object invokeConverter(GenericConverter converter, @Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
	try {
		return converter.convert(source, sourceType, targetType);
	}
	catch (ConversionFailedException ex) {
		throw ex;
	}
	catch (Throwable ex) {
		throw new ConversionFailedException(sourceType, targetType, source, ex);
	}
}
1
2
3
4
5
6
7
8
9
10
11
12

‍ 可以看到调用了 converter.convert 方法,继续步入:

@Override
@Nullable
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
	if (source == null) {
		return convertNullSource(sourceType, targetType);
	}
	return this.converterFactory.getConverter(targetType.getObjectType()).convert(source);
}
1
2
3
4
5
6
7
8

‍ 继续步入 this.converterFactory.getConverter 方法:

最后,NumberUtils.parseNumber 方法,其底层就是逐个判断要转换的类型,然后调用基本类型的 decode 方法来转换为数字类型:

public static <T extends Number> T parseNumber(String text, Class<T> targetClass) {
Assert.notNull(text, "Text must not be null");
Assert.notNull(targetClass, "Target class must not be null");
String trimmed = StringUtils.trimAllWhitespace(text);

    if (Byte.class == targetClass) {
	return (T) (isHexNumber(trimmed) ? Byte.decode(trimmed) : Byte.valueOf(trimmed));
    }
    else if (Short.class == targetClass) {
        return (T) (isHexNumber(trimmed) ? Short.decode(trimmed) : Short.valueOf(trimmed));
    }
    else if (Integer.class == targetClass) {
        return (T) (isHexNumber(trimmed) ? Integer.decode(trimmed) : Integer.valueOf(trimmed));
    }
    else if (Long.class == targetClass) {
	return (T) (isHexNumber(trimmed) ? Long.decode(trimmed) : Long.valueOf(trimmed));
    }
    else if (BigInteger.class == targetClass) {
    	return (T) (isHexNumber(trimmed) ? decodeBigInteger(trimmed) : new BigInteger(trimmed));
    }
    else if (Float.class == targetClass) {
	return (T) Float.valueOf(trimmed);
    }
    else if (Double.class == targetClass) {
	return (T) Double.valueOf(trimmed);
    }
    else if (BigDecimal.class == targetClass || Number.class == targetClass) {
	return (T) new BigDecimal(trimmed);
    }
    else {
	throw new IllegalArgumentException("Cannot convert String [" + text + "] to target class [" + targetClass.getName() + "]");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

‍

# 设置值

我们一点点的步出,直到最开始,调用 convert 方法的地方:此时可以看到值已经被转换好了。然后下一步就是设置值了(setValue):

‍

这里仅仅演示了一个属性的设置,其他属性同理,这里就不再演示了; ‍

# 最后

本文篇幅较长,希望读者能慢慢地一步步 debug,加深阅读和调试源码的能力;并对如下对象有基本的认知:

  • DataBinder:web 数据绑定器,将请求参数的值绑定到指定的 JavaBean 里面。其在绑定过程中,会调用 GenericConversionService,进行类型转换。

  • GenericConversionService:在设置每一个值的时候,找它里面的所有 converter 那个可以将这个数据类型(request 带来参数的字符串)转换到指定的类型。

  • Converter:只是一个接口,有很多的实现类,用来转换数据类型。源码:

    @FunctionalInterface
    public interface Converter<S, T> {
    	@Nullable
    	T convert(S source);
    }
    
    1
    2
    3
    4
    5

‍ 我们在《Web 开发简介》这一篇博客中,也说明了 SpringBoot 帮我们自动配置了 converter,DataBinder,这是 ConfigurableWebBindingInitializer 中配置的:

@Override
public void initBinder(WebDataBinder binder) {
	binder.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
	if (this.directFieldAccess) {
		binder.initDirectFieldAccess();
	}
	if (this.messageCodesResolver != null) {
		binder.setMessageCodesResolver(this.messageCodesResolver);
	}
	if (this.bindingErrorProcessor != null) {
		binder.setBindingErrorProcessor(this.bindingErrorProcessor);
	}
	if (this.validator != null && binder.getTarget() != null &&
			this.validator.supports(binder.getTarget().getClass())) {
		binder.setValidator(this.validator);
	}
	if (this.conversionService != null) {
		binder.setConversionService(this.conversionService);
	}
	if (this.propertyEditorRegistrars != null) {
		for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
			propertyEditorRegistrar.registerCustomEditors(binder);
		}
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

例如在 18 行初始化了 conversionService,这里面就包含了很多的 converter。

已将本文源码上传到 Gitee (opens new window) 或 GitHub (opens new window) 的分支 demo14,读者可以通过切换分支来查看本文的示例代码

上次更新: 2025/6/3 09:31:54
Model、Map 参数解析原理
自定义 Converter 原理

← Model、Map 参数解析原理 自定义 Converter 原理→

最近更新
01
语雀文档一键下载至本地教程
07-04
02
要成功,就不要低估环境对你的影响
07-03
03
血泪教训:电子设备要定期开机
07-02
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式