2023年12月2日发(作者:)

使用PathVariable注解获取路径中的参数出现HTTP状态400-错误的请求返回信息如下:

HTTP状态 400 - 错误的请求


类型 状态报告

描述 由于被认为是客户端对错误(例如:畸形的请求语法、无效的请求信息帧或者虚拟的请求路由),服务器无法或不会处理当前请求。


Apache Tomcat/9.0.41

情况一、获取Long类型的参数

在不使用@InitBinder注册用户的自定义的tyEditor类型情况Java代码如下:@RestController@RequestMapping(path = "v1/hello/")public class HelloController { @GetMapping(path = "showLong/{userId}") public long showLong(@PathVariable long userId) { return userId; }}请求参数如下:### 测试long类型GET localhost:9090/v1/hello/showLong/12345LAccept: application/json源码跟踪关键流程。nder#convertIfNecessary(, , Parameter)---->nverterSupport#convertIfNecessary(, , Parameter)---->---->---->---->sionUtils#invokeConverter---->terFactoryAdapter#convert---->ToNumber#convert---->Utils#parseNumber(, )nverterSupport#convertIfNecessary(, , scriptor)nverterDelegate#convertIfNecessary(, , , , cConversionService#convert(, scriptor, Utils#parseNumber(, )的源码如下:public static T parseNumber(String text, Class targetClass) { l(text, "Text must not be null"); l(targetClass, "Target class must not be null"); String trimmed = lWhitespace(text); if ( == targetClass) { return (T) (isHexNumber(trimmed) ? (trimmed) : f(trimmed)); } else if ( == targetClass) { return (T) (isHexNumber(trimmed) ? (trimmed) : f(trimmed)); } else if ( == targetClass) { return (T) (isHexNumber(trimmed) ? (trimmed) : f(trimmed)); } else if ( == targetClass) { return (T) (isHexNumber(trimmed) ? (trimmed) : f(trimmed)); } else if ( == targetClass) { return (T) (isHexNumber(trimmed) ? decodeBigInteger(trimmed) : new BigInteger(trimmed)); } else if ( == targetClass) { return (T) f(trimmed); } else if ( == targetClass) { return (T) f(trimmed); } else if ( == targetClass || == targetClass) { return (T) new BigDecimal(trimmed); } else { throw new IllegalArgumentException( "Cannot convert String [" + text + "] to target class [" + e() + "]"); } }

使用@InitBinder注册用户的自定义的tyEditor类型情况Java代码如下:(这里我使用itor,也可自己实现)@RestController@RequestMapping(path = "v1/hello/")public class HelloController { @InitBinder public void initBinder(WebDataBinder binder) { erCustomEditor(, new LongEditor()); erCustomEditor(, new LongEditor()); } @GetMapping(path = "showLong/{userId}") public long showLong(@PathVariable long userId) { return userId; }}请求参数如下:### 测试long类型GET localhost:9090/v1/hello/showLong/12345LAccept: application/json源码跟踪的关键流程如下:nder#convertIfNecessary(, , Parameter)---->nverterSupport#convertIfNecessary(, , Parameter)---->---->---->nverterDelegate#doConvertValue---->nverterDelegate#nverterSupport#convertIfNecessary(, , scriptor)nverterDelegate#convertIfNecessary(, , , , frame nverterDelegate#doConvertTextValue的源码如下:private Object doConvertTextValue(@Nullable Object oldValue, String newTextValue, PropertyEditor editor) { try { ue(oldValue); } catch (Exception var5) { if (gEnabled()) { ("PropertyEditor [" + ss().getName() + "] does not support setValue call", var5); } } ext(newTextValue); return ue(); }itor#setAsText的源码如下:public void setAsText(String var1) throws IllegalArgumentException { ue(var1 == null ? null : (var1));}仔细查看最终的参数转换都是使用(String value)方法或者f(String value)。而我们传递的参数是12345L,这两个方法执行都会抛出FormatException: For input string: "12345L"至此Long类型的参数出现上述问题的根因找出,解决方法也就明了了。方式一:传递Long类型参数时不要时L标注参数类型,直接传递相应的数值即可。如下示例:### 测试long类型GET localhost:9090/v1/hello/showLong/12345Accept: application/json方式二:直接写一个实现tyEditor接口或者继承Editor实现一个自定义的Editor,将字符串后面的L或者l给删除后在使用()和f()方法。情况二:获取类型的参数1、使用@PathVariable注解取路径中的参数值Java代码如下:@RestController@RequestMapping(path = "v1/hello/")public class HelloController { @GetMapping(path = "showDate/{initDate}") public Date showDate(@PathVariable Date initDate) { return initDate; }}请求如下:### 测试Date类型GET localhost:9090/v1/hello/showDate/2021-01-01 14:30:34Accept: application/json代码跟踪路径nder#convertIfNecessary(, , Parameter)---->nverterSupport#convertIfNecessary(, , Parameter)---->---->---->---->sionUtils#invokeConverter---->差异步骤,每个GenericConverter中的convert方法ToObjectConverter#nverterSupport#convertIfNecessary(, , scriptor)nverterDelegate#convertIfNecessary(, , , , cConversionService#convert(, scriptor, fram默认配置的情况下看流程都是都一个模版,一个差异为如下方法返回的对象不同。cConversionService#getConverter这个方法方法返回不同的cConverter类型。例如当前这种传递参数是类型,而接受类型是类型,其返回的结果是ToObjectConverter在ObjectToObjectConverter对象中的convert方法中通过反射获取了一个Date(String str)的一个构造器,而没有相应的构造器,从而导致出现lArgumentException。所以这种情况我目前只知道使用官网提供的使用@InitBinder注解注册一个DateEditor,当然也可以是自己实现的。解决方案代码如下:@RestController@RequestMapping(path = "v1/hello/")public class HelloController { private static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @InitBinder public void initBinder(WebDataBinder binder) { erCustomEditor(, new CustomDateEditor(df, false)); } @GetMapping(path = "showDate/{initDate}") public Date showDate(@PathVariable Date initDate) { return initDate; }}使用@InitBinder注解后其执行流程就合上述的Long类型一致了,这里就不再介绍一次了。需要注意的是传递的参数格式必须和DateFormat的pattern的格式一致,不然也是会报错。2、使用@RequestBody注解时POJO对象中有类型的属性Java代码如下:(前提是引入了jackson的相关核心依赖)package ;import tInfo;import tion.*;@RestController@RequestMapping(path = "v1/hello/")public class HelloController { @PostMapping(path = "showRequestInfo") public RequestInfo showRequestInfo(@RequestBody RequestInfo requestInfo) { return requestInfo; }}RequestInfo定义如下:package ;import izable;import imal;import ;public class RequestInfo implements Serializable { private String username; private Long userId; private int userAge; private double userHeight; // 身高 private Date birthday; private BigDecimal balance; // 余额

// 省略get和set方法}请求定义如下:### 测试Pojo类型POST localhost:9090/v1/hello/showRequestInfoContent-Type: application/json{ "username" : "java", "userId" : "123455", "userAge" : "23", "userHeight" : "170.5", "birthday" : "2020-12-19 12:12:12", "balance" : "20.55"} 代码跟踪关键方法栈如下:ctJackson2HttpMessageConverter#read--->Mapper#_readMapAndClose--->--->serializer#deserializeFromObject--->Property#serializer#deserialize(rser, alizationCont在转换传递参数的birthday属性报错,相应的调用方法如下:sedDeserializer#_parseDate报错信息如下:Cannot deserialize value of type `` from String "2020-12-19 12:12:12": not a valid representation (error: Failed to parse Date value '2020-12-1跟踪发现Mapper中默认创建的的DateFormat是eFormat,该对象的pattern为:yyyy-MM-dd'T'HH:mm:看到这里,我们的一个解决方案就是按照eFormat中的pattern格式传递时间参数,例如需改请求为:### 测试Pojo类型POST localhost:9090/v1/hello/showRequestInfoContent-Type: application/json{ "username" : "java", "userId" : "123455", "userAge" : "23", "userHeight" : "170.5", "birthday" : "2021-01-01T22:43:56.145+08", "balance" : "20.55"}响应结果如下:POST localhost:9090/v1/hello/showRequestInfoHTTP/1.1 200

Content-Type: application/xml;charset=UTF-8Transfer-Encoding: chunkedDate: Fri, 01 Jan 2021 14:44:33 GMTKeep-Alive: timeout=20Connection: keep-alive java 123455 23 170.5 16 20.55但是这显然不是我们想要的结果,并且也不推荐这样。解决方案二:使用@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")注解修改RequestInfo对象信息,修改后代码如下:package ;import rmat;import izable;import imal;import ;public class RequestInfo implements Serializable { private String username; private Long userId; private int userAge; private double userHeight; // 身高 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date birthday; private BigDecimal balance; // 余额 // 省略get/set方法}请求信息如下:(注意:必须保证日期参数的格式和定义的pattern保持一致)### 测试Pojo类型POST localhost:9090/v1/hello/showRequestInfoContent-Type: application/json{ "username" : "java", "userId" : "123455", "userAge" : "23", "userHeight" : "170.5", "birthday" : "2021-01-01 22:43:56", "balance" : "20.55"}响应结果如下: java 123455 23 170.5 2021-01-01 22:43:56 20.55使用@JsonFormat注解虽然可以确保时间格式的一致,但是需要在相应的属性上进行配置。一个系统中使用类型作为属性的地方不可能只有几个。当需求确实需要特定的时间格式时可以使用这个注解,通常情况下我们还是希望系统中所有的属性都使用统一的格式进行转换。使用方案三可以做到这点。方案三:重写configureMessageConverters()方法,替换SpringMVC默认创建的。方法全路径信息如下:Configurer#configureMessageConverters(List> converters)Java代码如下:package ;import entScan;import uration;import Type;import ssageConverter;import n2ObjectMapperBuilder;import gJackson2HttpMessageConverter;import gJackson2XmlHttpMessageConverter;import WebMvc;import Configurer;import ter;import DateFormat;import ;@Configuration@ComponentScan(basePackages = {""}, excludeFilters = {@(type = TION, classes = {})})@EnableWebMvcpublic class ServletConfig implements WebMvcConfigurer { @Override public void configureMessageConverters(List> converters) { MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(); Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder() .indentOutput(true) .dateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); (new MappingJackson2HttpMessageConverter(())); (new MappingJackson2XmlHttpMessageConverter(XmlMapper(true).build())); (mappingJackson2HttpMessageConverter); }}配置后容器中MessageConverter的列表信息为:0 = {MappingJackson2HttpMessageConverter@5702}

1 = {MappingJackson2XmlHttpMessageConverter@5706}

2 = {MappingJackson2HttpMessageConverter@5707}

重新修改RequestInfo对象信息,修改后代码如下:package ;import izable;import imal;import ;public class RequestInfo implements Serializable { private String username; private Long userId; private int userAge; private double userHeight; // 身高 private Date birthday; private BigDecimal balance; // 余额

// 省略get和set方法}请求信息如下:### 测试Pojo类型POST localhost:9090/v1/hello/showRequestInfoContent-Type: application/json{ "username" : "java", "userId" : "123455", "userAge" : "23", "userHeight" : "170.5", "birthday" : "2021-01-01 22:43:56", "balance" : "20.55"}响应信息如下:POST localhost:9090/v1/hello/showRequestInfoHTTP/1.1 200

Content-Type: application/jsonTransfer-Encoding: chunkedDate: Fri, 01 Jan 2021 15:16:23 GMTKeep-Alive: timeout=20Connection: keep-alive{ "username": "java", "userId": 123455, "userAge": 23, "userHeight": 170.5, "birthday": "2021-01-01 22:43:56", "balance": 20.55}使用的jackson依赖信息如下: rmat jackson-dataformat-cbor ${n} rmat jackson-dataformat-xml ${n} 以上信息是我个人在学习过程中遇到的一些问题,以及自己查阅资料和调试部分代码后的一些个人观点,有错误的地方欢迎指正。