从01开始 从01开始
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)

peterjxl

人生如逆旅,我亦是行人
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)
  • 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完整启动过程
      • SpringBoot
  • Java并发

  • Java源码

  • JVM

  • 韩顺平

  • Java
  • 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,读者可以通过切换分支来查看本文的示例代码

在GitHub上编辑此页 (opens new window)
上次更新: 2023/8/23 10:10:57
Model、Map参数解析原理
自定义Converter原理

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

Theme by Vdoing | Copyright © 2022-2023 粤ICP备2022067627号-1 粤公网安备 44011302003646号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式