@PathVariable 的参数解析场景

本文对 @PathVariable 的参数解析场景进行了详细的源码分析,希望通过该具体场景,帮助理解以下内容:

  • SpringMVC 请求处理的参数解析的大致流程
  • 请求映射 RequestMappingHandlerMapping 的工作原理
  • 处理器适配器 RequestMappingHandlerAdapter 的工作原理
  • 数据绑定器 WebDataBinder 中的转换服务 ConversionService 的工作原理

本文参考:SpringMVC处理请求源码分析 —— part2 场景分析 > 1. @PathVariable

浏览器请求是:GET /car/3/owner/lisi

Controller 处理器定义是:

1
2
3
4
5
6
@ResponseBody
@GetMapping("/car/{id}/owner/{username}")
public Map<String, Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name) {
// ...
}

请求映射阶段

请求映射阶段的主要工作是:获取请求对应的处理器,并找到支持该处理器的处理器适配器

请求首先需要经过 HandlerMapping 来找到对应的处理器。

  1. 首先尝试 RequestMappingHandlerMapping 来进行处理,也就是查询 MappingRegistry 是否有注册能够匹配该请求的 RequestMappingInfo,如果有,以 RequestMappingInfo 为 key 即可找到对应的 HandlerMethod 返回:

    • 先根据请求路径 /car/3/owner/lisi 查询是否存在对应的 RequestMappingInfo

      MappingRegistry 中的 urlLookup 存储的都是确定的 url 请求路径所对应的 RequestMappingInfo 集合。

      但对于 @GetMapping("/car/{id}/owner/{username}") 注解,路径变量 {id}{username} 的值是可变的,该 @GetMapping 注解不存在一个确定的 url 请求路径,因此在 urlLookup 中将不会存储 @GetMapping("/car/{id}/owner/{username}") 对应的 RequestMappingInfo 信息。

      所以,根据请求路径 /car/3/owner/lisiurlLookup 中将查询不到匹配的 RequestMappingInfo

    • 刚才查不到任何匹配结果,那么第二步会将当前 MappingRegistry 中所有已注册的 RequestMappingInfo 与请求 request 进行匹配。

      RequestMappingInfo 中包含多个匹配条件,比如:请求方式条件 methodsCondition,请求路径模板条件 patternsCondition

      请求 request 必须满足 RequestMappingInfo 的所有条件,才表示找到匹配,但凡一个不满足,均匹配失败。

      对于 @GetMapping("/car/{id}/owner/{username}") 对应的 RequestMappingInfo,当前请求 request GET /car/3/owner/lisi 可以成功匹配!具体匹配情况是:

      • 请求方式是 GET,满足 methodsCondition 条件
      • 请求路径 /car/3/owner/lisi 能够匹配模板 /car/{id}/owner/{username},所以也满足 patternsCondition 条件
      • 当然,其他所有条件也都满足。
    • 请求 requestMappingRegistry 中找到第一个匹配成功的 RequestMappingInfo 后,还会继续进行匹配,如果最后匹配到多个,一般会报错,因为一个请求,只能被一个 RequestMappingInfo 匹配,从而保证只会被一个 HandlerMethod 处理。

      找到匹配的 RequestMappingInfo 后,会再到 MappingRegistry 中的 mappingLookup Map 集合中取出对应的 HandlerMethod,然后将 RequestMappingInfoHandlerMethod 一并封装为 Match 返回。

  2. 找到唯一的最佳匹配后,在将最佳匹配中封装的 HandlerMethod 返回之前,还需要做一些工作:使用 PathMatcher 以及 UrlPathHelper 对请求路径以及其他请求信息做一些处理,将处理结果设置到 request 作用域中。

    比如对于当前包含路径变量的请求 /car/3/owner/lisi,会使用 PathMatcher 按照模板 /car/{id}/owner/{username} 先解析出一个 Map 集合,其中包含两个键值对:("id", "3") ("username", "lisi"),然后将该 Map 集合设置到 request 作用域中,key 为 HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE

  3. 拿到返回的 HandlerMethod 后,将其再封装为 HandlerExecutionChain 返回。

    HandlerExecutionChain 不为空,说明 RequestMappingHandlerMapping 能够处理该请求,后续不再检查其他的 HandlerMapping 实现能否处理请求。

    HandlerExecutionChain 中除了拿到的 HandlerMethod 以外,还包括与当前请求路径匹配的所有拦截器,目前我们先不用关心拦截器相关的事情。

    这里需要清楚一点:对于不同的 HandlerMapping 实现,其最终返回的处理器的类型是不同的,比如 RequestMappingHandlerMapping 返回的是 HandlerMethod,其他实现类一般返回的都是 Object

  4. 根据返回的 HandlerExecutionChain 处理器执行链中的处理器,找到支持该处理器的处理器适配器 HandlerAdapter

    首先尝试 RequestMappingHandlerAdapter 是否支持,RequestMappingHandlerAdapter 的支持逻辑是:只要提供的处理器是 HandlerMethod 类型就宣布支持。

    所以 RequestMappingHandlerAdapter 能支持当前的处理器,直接将其返回,后续不再检查其他的 HandlerAdapter 实现能否支持处理器。

以上的第 1, 2 步的代码详情看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// AbstractHandlerMethodMapping.java
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}

if (!matches.isEmpty()) {
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
throw new IllegalStateException();
}
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
}

请求处理执行阶段

请求处理执行阶段的主要工作有三个:

  • 先通过参数解析器,从请求中解析得到处理器方法的所有实参
  • 然后传入实参,反射执行处理器方法,得到结果
  • 通过返回值处理器,将结果处理后返回

这里我们仅仅关注第一个工作:通过参数解析器,从请求中解析得到处理器方法的所有实参。

RequestMappingHandlerAdapter 处理器适配器简介

RequestMappingHandlerAdapter 对于动态请求场景,也即处理器是 HandlerMethod 类型的情况提供了非常多的支持。

  • 内部属性 argumentResolvers 就包括了很多参数解析器 HandlerMethodArgumentResolver,来对 HandlerMethod 处理器方法上的所有参数进行解析:根据不同的参数 MethodParameter 情况,选择不同的参数解析器从请求中解析出相应的值作为后续调用处理器方法的实参。

    对于 @PathVariable 标注的处理器方法参数,会使用 PathVariableMethodArgumentResolver 进行解析

    对于使用 @RequestBody 标注的处理器方法参数,会选择 RequestResponseBodyMethodProcessor 进行解析,RequestResponseBodyMethodProcessor 内部包括了很多的报文转换器 HttpMessageConverter,彼时将根据请求信息,处理器方法参数类型等信息,选择合适的报文转换器,来将请求报文转换为相应的处理器方法参数。

  • 内部属性 returnValueHandlers 就包括了很多返回值处理器 HandlerMethodReturnValueHandler,来对 HandlerMethod 处理器方法的返回值进行处理:根据不同的返回情况,比如返回类型不同,方法上是否标注了 @ResponseBody 注解等,来选择不同的返回值处理器进行处理。

    对于使用 @ResponseBody 标注的处理器方法,同样是会选择 RequestResponseBodyMethodProcessor 进行返回值处理,其内部诸多的报文转换器彼时会根据请求信息,处理器方法返回值类型等信息,选择合适的报文转换器,来将处理器方法返回值,转换为相应的响应报文。

  • 内部属性 webBindingInitializer 是绑定初始化器,该属性内部又包括了一个转换服务 conversionService,该转换服务中包括了很多转换器 Converters

    该属性在后续会用于创建数据绑定工厂 WebDataBinderFactory,数据绑定工厂将用于生产数据绑定器 WebDataBinder,数据绑定器主要负责参数解析中的最后一步:将从请求中得到的原始数据,绑定到后续将传递给 HandlerMethod 处理器方法的实参上。

    因此,对于每个处理器方法参数,相应的参数解析器在完成初步的解析后,就会使用 WebDataBinderFactory 创建一个数据绑定器 WebDataBinder,通过该数据绑定器来完成具体的绑定工作。

    对于简单类型的处理器方法参数,数据绑定器的主要工作一般只是完成转换。比如 @PathVariable("id") Integer id,初步解析时得到的路径变量值是 java.lang.String 类型的,为了完成绑定,数据绑定器会使用内部的转换服务进行类型转换,最终得到 java.lang.Integer 类型的实参。数据绑定器内部的转换服务,其实就是从 webBindingInitializer 绑定初始化器中的转换服务 conversionService

    对于非简单类型的处理器方法参数,数据绑定器则同时需要进行数据绑定和转换。比如没有带任何注解的 User user 类型的形参,初步解析时会通过反射调用无参构造器创建一个属性值均为默认值的 User 对象,可称之为壳对象,然后数据绑定器内部会解析请求获得所有的请求参数,然后底层借助 BeanWrapperImpl 的强大功能来最终反射调用 User 的 setter 方法来完成属性值的注入,同时在类型不兼容时,会先使用内部的转换服务进行类型转换然后再注入。

    WebDataBinderFactory 创建数据绑定器 WebDataBinder 时,需要提供三个重要参数:原始的请求,需要绑定到的目标对象(简单类型无需提供),目标对象的命名。

综上,可以将处理器适配器视为一个处理特定请求场景的工具箱,其中的属性就是所封装的辅助请求处理的大量工具。

请求处理执行阶段的初始化工作

  1. 将当前请求 requestresponse 打包为 ServletWebRequest

  2. 创建 ServletInvocableHandlerMethod 来包装 HandlerMethod,并为其装配 RequestMappingHandlerAdapter 中定制的大量工具,包括:

    • 参数解析器 argumentResolvers
    • 返回值处理器 returnValueHandlers
    • 数据绑定工厂 WebDataBinderFactory
  3. 创建 ModelAndViewContainer 对象,可以将该对象视为一次请求的上下文对象。

    如果处理器方法上有 Model ModelMap 以及不带注解的 Map 类型参数,则后续会将该对象内部的 defaultModel 属性作为对应的实参。

    因此对于页面开发的情况,向 Model 参数保存键值对,其实就是保存到 ModelAndViewContainer 对象内部的 defaultModel 属性中;同时返回的视图名称,最终也会保存到 ModelAndViewContainer 对象内部的 view 属性中。

至此,ServletInvocableHandlerMethodHandlerMethod 以及用于支持 HandlerMethod 调用执行的一大堆工具全都封装到内部了。接下来,是时候正式开始调用处理 HandlerMethod 了。

以上初始化过程的代码详情看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// RequestMappingHandlerMapping.java
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

ServletWebRequest webRequest = new ServletWebRequest(request, response);
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);

ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);

ModelAndViewContainer mavContainer = new ModelAndViewContainer();

// 正式开始调用处理 HandlerMethod
invocableMethod.invokeAndHandle(webRequest, mavContainer);

return getModelAndView(mavContainer, modelFactory, webRequest);
}

处理器方法参数解析

  1. 首先拿到的 HandlerMethod 的所有形参列表中的所有参数,将每个形参的信息,比如所标注的注解,具体的类型,都封装为一个 MethodParameter 对象返回,全部形参则返回一个 MethodParameter[] parameters 数组。

  2. 创建一个 Object[] args 数组,长度为 parameters.length,该数组中的每个元素初始均为 null,但最终将保存解析得到的实参结果。

  3. 遍历 parameters 数组,对取出的每个 MethodParameter 选择合适的参数解析器进行解析,解析的结果保存到 args 数组中。

以上第 1, 2, 3 步的代码详情看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// InvocableHandlerMethod.java
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {

MethodParameter[] parameters = getMethodParameters();

Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
return args;
}

对于 @PathVariable("id") Integer id 对应的 MethodParameter,会选择 PathVariableMethodArgumentResolver 来进行参数解析。

PathVariableMethodArgumentResolver 的解析过程主要分为两步:根据 @PathVariable("id") 中的 id 从请求路径中获取相应的字符串值,然后利用数据绑定器内部的转换服务,将字符串路径变量值从 java.lang.String 转换为处理器方法形参对应的 java.lang.Integer。具体代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// AbstractNamedValueMethodArgumentResolver.java
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 拿到 @PathVariable("id") 中的 "id"
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = resolveStringValue(namedValueInfo.name);
// 从请求路径中获取 id 对应的字符串路径变量值,对于 /car/3/owner/lisi 来说是 "3"
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);

// 创建数据绑定器,将 "3" 转换为目标类型
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);

return arg;
}

我们先来解析 resolveName() 的执行,该方法会通过动态绑定,调用子类 PathVariableMethodArgumentResolver 中重写的 resolveName() 方法。

在请求映射阶段,我们就已经通过 PathMatcher 对请求路径进行了处理,以 HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE 为 key 向请求域中放入了一个 Map 集合,该 Map 集合中存放了 ("id", "3")("username", "lisi")

因此 PathVariableMethodArgumentResolverresolveName() 方法就是先从请求域中获取 HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE 为 key 的 Map 集合,然后执行 map.get("id") 拿到字符串路径变量值 "3",将其返回。具体代码如下所示:

1
2
3
4
5
6
7
// PathVariableMethodArgumentResolver.java
@Override
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}

SpringMVC 数据绑定器的转换工作 binder.convertIfNecessary() 被封装了非常多层,但所做的工作,用一句话就可以概括清楚:根据原始类型信息和目标类型信息从 conversionService 转换服务中选取合适的转换器 Converter 来进行转换工作,如下面的代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// TypeConverterDelegate.java
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {

ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
// canConvert 内部会先确定能匹配给定的原始类型信息和目标类型信息的转换器,并将其放入缓存中
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
try {
// convert 时就直接从缓存中取出转换器,进行转换
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
catch (ConversionFailedException ex) {
// fallback to default conversion logic below
conversionAttemptEx = ex;
}
}
}

Object convertedValue = newValue;
return (T) convertedValue;
}

ConversionService 对象就是最开始 RequestMappingHandlerAdapter 内部 webBindingInitializer 中的转换服务,它有两个重要的属性:

1
2
3
4
// 保存了一系列 Spring 提供好的转换器
private final Converters converters = new Converters();
// 转换器缓存
private final Map<ConverterCacheKey, GenericConverter> converterCache = new ConcurrentReferenceHashMap<>(64);

目前我们的原始类型信息是 java.lang.String,目标类型信息是 @PathVariable java.lang.Integer,它们都是 TypeDescriptor 对象,不仅记录了类型,还记录了注解信息。

数据绑定器首先将 java.lang.String@PathVariable java.lang.Integer 封装为一个 ConverterCacheKey 作为 key,尝试先从转换器缓存 converterCache 中获取转换器。

第一次执行时,转换器缓存为空,因此从缓存中获取不到对应的转换器,此时去到 converters 属性中查找合适的转换器。

converters 属性内部封装了一个 Map<ConvertiblePair, ConvertersForPair> converters 转换器 Map 集合,该 Map 集合中保存了一系列 Spring 提供好的转换器。

ConvertiblePair 内部封装的是原始类型和目标类型,区别于之前提到的 TypeDescriptorConvertiblePair 内部的原始类型和目标类型是 Class 对象,也即仅保存类型,不关心注解信息。通过它可以在 converters 中快速定位到一个 ConvertersForPair 对象。

ConvertersForPair 内部则保存了一个的转换器列表 LinkedList<GenericConverter> converters,列表中的所有转换器都满足键 ConvertiblePair 的原始类型和目标类型要求,但在类型以外的信息上各自又存在差异。

需要注意的是:当在缓存中找不到时,SpringMVC 并不是直接将原始类型和目标类型,直接封装为 ConvertiblePairconverters 中查找。

SpringMVC 会先分析原始类型和目标类型的各自的继承树,得到所有候选原始类型,以及所有候选目标类型。

SpringMVC 会从两个候选列表中,按顺序各自选出一个组成一对封装为 ConvertiblePair,然后才到 converters 属性中查找合适的转换器。

对于原始类型 java.lang.String,目标类型 java.lang.Integer,得到的候选类型如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 原始类型 java.lang.String 的候选原始类型
0 = {Class@343} "class java.lang.String"
1 = {Class@346} "interface java.io.Serializable"
2 = {Class@345} "interface java.lang.Comparable"
3 = {Class@344} "interface java.lang.CharSequence"
4 = {Class@347} "class java.lang.Object"

// 目标类型 java.lang.Integer 的候选目标类型
0 = {Class@272} "class java.lang.Integer"
1 = {Class@277} "class java.lang.Number"
2 = {Class@345} "interface java.lang.Comparable"
3 = {Class@346} "interface java.io.Serializable"
4 = {Class@347} "class java.lang.Object"

候选类型使用 ArrayList 保存,且其中元素具有优先级。

SpringMVC 先将 java.lang.String java.lang.Integer 组成一对封装为 ConvertiblePair,到 converters 中查找,发现能找到对应的 ConvertersForPair,其内部的转换器列表只包含一个适配 java.lang.String -> @NumberFormat java.lang.Integer 的转换器,该转换器要求目标参数必须是 java.lang.Integer 且必须标注了 @NumberFormat 注解,我们的目标参数不满足后者,因此本轮查找失败。

进入下一轮查找,保持候选原始类型不变,尝试 java.lang.String java.lang.Number 组成一对封装为 ConvertiblePair,到 converters 中查找,发现能找到对应的 ConvertersForPair,其内部的转换器列表只包含一个适配 java.lang.String -> java.lang.Number: StringToNumberConverterFactory 的转换器,该转换器对目标参数没有其他要求,因此本次查找成功,该转换器将会放到缓存 converterCache 中,并最后返回。

以上查找转换器的过程的具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// GenericConversionService.java
@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) {
this.converterCache.put(key, converter);
return converter;
}

this.converterCache.put(key, NO_MATCH);
return null;
}

查找到 java.lang.String -> java.lang.Number: StringToNumberConverterFactory 转换器以后,该转换器底层具体是怎么转换的呢?

目前我们获取到的转换器,其实还只是一个转换器工厂适配器,其内部保存了一个 converterFactory 转换器工厂属性,类型为 StringToNumberConverterFactory

在进行转换时,为了确保最终得到的是 java.lang.Integer 类型的数据,该转换器工厂适配器会使用 converterFactory 转换器工厂根据原始目标类型 java.lang.Integer 生产一个最终的转换器。

最终的转换器调用 Spring 的工具类 NumberUtils.parseNumber(source, this.targetType) 进行转换,this.targetType 就是最终的转换器内部记录的原始目标类型 java.lang.Integer,工具类底层其实就是调用 Integer.valueOf(source) 进行转换。

以上转换器转换的具体代码如下:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// ConversionUtils.java
public static Object invokeConverter(GenericConverter converter, @Nullable Object source,
TypeDescriptor sourceType, TypeDescriptor targetType) {

return converter.convert(source, sourceType, targetType);
}
// GenericConversionService.java
@Override
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.converterFactory.getConverter(targetType.getObjectType()).convert(source);
}

// StringToNumberConverterFactory.java
final class StringToNumberConverterFactory implements ConverterFactory<String, Number> {

@Override
public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToNumber<>(targetType);
}

private static final class StringToNumber<T extends Number> implements Converter<String, T> {

private final Class<T> targetType;

public StringToNumber(Class<T> targetType) {
this.targetType = targetType;
}

@Override
public T convert(String source) {
return NumberUtils.parseNumber(source, this.targetType);
}
}

}

// NumberUtils.java
public static <T extends Number> T parseNumber(String text, Class<T> targetClass) {
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) { // Integer.valueOf(trimmed) 完成转换
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() + "]");
}
}