【源码分析】Spring MVC类型转换器匹配

  • Post author:
  • Post category:其他




Spring MVC类型转换器匹配

之前一直以为类型转换器是通过遍历的方式挨个匹配,直到匹配到合适的类型。但是一想到有这么多类型,如果挨个匹配不仅效率慢,而且还容易出错,翻翻源码,看到了类型转换的转换规则如下。


既然是在参数位置进行的转换,必然得从DispatcherServlet去入手:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B6iaYDbV-1686301067348)(/home/wuyahan/Documents/探索Deepin.assets/image-20230609135353286-16862900367101.png)]


所以在1040行打断点:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NynNIzNV-1686301067349)(/home/wuyahan/Documents/探索Deepin.assets/image-20230609135448878-16862900904092.png)]


继续深入:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HYbklYsp-1686301067349)(/home/wuyahan/Documents/探索Deepin.assets/image-20230609135820438-16862903021333.png)]


在这里获取到了ModelAndView结果,所以在这里执行完了我们在Controller层,对应@RequestMapping的方法,继续深入:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g3m90B28-1686301067349)(/home/wuyahan/Documents/探索Deepin.assets/image-20230609140018425-16862904199224.png)]


在里面会有一堆的set,不用管往下找到反射的部分:

这里注意840行,这里维护了一个缓存对象,包含了我们要用的转换器:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S8uDtAUV-1686301067350)(/home/wuyahan/Documents/探索Deepin.assets/image-20230609145901575-168629394257513.png)]


本地方法,, 在这里进行了 处理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UGPVvM74-1686301067350)(/home/wuyahan/Documents/探索Deepin.assets/image-20230609164050135-168630005287214.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KuBv6a49-1686301067350)(/home/wuyahan/Documents/探索Deepin.assets/image-20230609140537429-16862907387375.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eaWaYvYp-1686301067351)(/home/wuyahan/Documents/探索Deepin.assets/image-20230609140556053-16862907570406.png)]


这里获取到了返回值,继续深入:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GNgtVteB-1686301067351)(/home/wuyahan/Documents/探索Deepin.assets/image-20230609140716488-16862908377407.png)]

到这里才算真正找到了处理类型转换的地方

在这里插入图片描述

下面开始粘贴代码补充:

os: 构建springmvc源码失败,,真难受。


在这里获取到了参数的类型、参数名称等信息,下面必然就是对参数的处理,继续往下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PgnphXda-1686301067352)(/home/wuyahan/Documents/探索Deepin.assets/image-20230609141040763-16862910419619.png)]


这里把参数对象的值进行转换并存储了起来,点进去继续深入:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V5I0hLvM-1686301067352)(/home/wuyahan/Documents/探索Deepin.assets/image-20230609141338230-168629121921210.png)]


既然返回值是我们要的结果,那

return

那行便是我们需要继续深入的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3CuH5UzX-1686301067353)(/home/wuyahan/Documents/探索Deepin.assets/image-20230609141502746-168629130396411.png)]


这里就直接上代码,因为代码部分比较多所以这里只保留核心部分

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                                    NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
	// 获取参数名、是否是必须的、默认值等
    NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
    // 当前参数类型信息、参数名等信息
    MethodParameter nestedParameter = parameter.nestedIfOptional();
	// 获取到参数的名称
    Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
   
    ......
    
	// 获取我们给参数传的值  未进行转换之前的值
    Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
    // 判断参数值是否为null
    if (arg == null) {
        ......
    }
    // 判断参数值是否为空
    else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
        ......
    }
	// binderFactory中包含conversionService ,其中有124个类型转换器
    if (binderFactory != null) {

        ......
        
        try {
            // 这里进行真正的数据转换,将我们传入的数据通过类型转换器转换为和参数对应的类型
            arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
        }

        .......
    }

    .......
}


继续深入,最终来到

TypeConverterDelegate.java --> convertIfNecessary ()


这里便是最终转换的地方,这个方法很庞大,我这里只保留我代码经过的地方

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yBER4Z1V-1686301067353)(/home/wuyahan/Documents/探索Deepin.assets/image-20230609142940569-168629218158412.png)]

// Custom editor for this type?
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);

ConversionFailedException conversionAttemptEx = null;

// No custom editor but custom ConversionService specified?
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 {
            // newValue 我们传入的值 
            // sourceTypeDesc 原类型
            // typeDescriptor 要转换成的类型
            return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
        }
		......
    }
}

......


这里已经转换完毕了,我们可以继续点进去深入看一下:

public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
    
    ...... 一些数据的校验
    // 这里找到对应的转换器
    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);
}


GenericConverter converter = getConverter(sourceType, targetType);

protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
    // 将原类型和要转换的类型封装为 ConverterCacheKey
    ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);
    // 这里是维护了一个map ,里面存着 ConverterCacheKey-> GenericConverter 的数据
    // this.converterCache 
    // private final Map<ConverterCacheKey, GenericConverter> converterCache = new ConcurrentReferenceHashMap<>(64);
    GenericConverter converter = this.converterCache.get(key);
    if (converter != null) {
        return (converter != NO_MATCH ? converter : null);
    }

   ......
}


Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);

public Boolean convert(String source) {
		String value = source.trim();
		if (value.isEmpty()) {
			return null;
		}
		value = value.toLowerCase();
		if (trueValues.contains(value)) {
			return Boolean.TRUE;
		}
		else if (falseValues.contains(value)) {
			return Boolean.FALSE;
		}
		else {
			throw new IllegalArgumentException("Invalid boolean value '" + source + "'");
		}
	}



版权声明:本文为qq_46283617原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。