Kotlin 控制器中请求正文的 Jackson 序列化不与 restTemplate 一起工作,与邮递员一起工作

时间:2021-03-07 17:47:55

标签: spring-boot kotlin jackson

我无法弄清楚为什么我在服务器上收到此错误: 我构建了一个接受请求 Dto 的简单控制器:

@ResponseBody
@PostMapping("/log")
fun logReuqest(@RequestBody request: Request) {
    logger.info("Request: $request")
}

Reuest 和嵌套的 DTO 如下所示:

data class Request(val customer: Customer, val dealer: Dealer)
data class Customer(val id: String, val name: String)
data class Dealer( val id: String, val name: String)

我发送的json是:

 {
  "customer" : {
    "id" : "123",
    "name" : "customer"
  },
  "dealer" : {
    "id" : "123",
    "name" : "dealer"
  }

}

问题:我从邮递员那里发送了一个带有“application/json” contentType 标头的请求,它运行良好。但是每当我部署这个应用程序并且它收到来自另一个服务的传入请求时,我就会收到这个 Jackson 错误:

nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot 
construct instance of `com.service.dto.Request` (although at least one Creator exists): no
String-argument constructor/factory method to deserialize from String value

代码有什么问题,为什么它可以从邮递员那里工作,并且每当我部署它时,当使用 restTemplate 从另一个服务获取传入请求时,它不起作用?

堆栈跟踪:

 
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `com.service.dto.Request` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{
  "customer" : {
    "id" : "123",
    "name" : "customer"
  },
  "dealer" : {
    "id" : "123",
    "name" : "dealer"
  }'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.service.dto.Request` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{
  "customer" : {
    "id" : "123",
    "name" : "customer"
  },
  "dealer" : {
    "id" : "123",
    "name" : "dealer"
  }')
 at [Source: (PushbackInputStream); line: 1, column: 1]
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:387)
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:342)
    at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:186)
    at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:158)
    at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:131)
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
    at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:170)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1060)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:962)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:652)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
    at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:764)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:346)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:887)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1684)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.base/java.lang.Thread.run(Unknown Source)
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.service.dto.Request` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{
  "customer" : {
    "id" : "123",
    "name" : "customer"
  },
  "dealer" : {
    "id" : "123",
    "name" : "dealer"
  }')
 at [Source: (PushbackInputStream); line: 1, column: 1]
    at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
    at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1588)
    at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1213)
    at com.fasterxml.jackson.databind.deser.std.StdDeserializer._deserializeFromString(StdDeserializer.java:311)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1495)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:207)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:197)
    at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4593)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3601)
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:376)
    ... 56 common frames omitted 

更新:

发送请求的发件人应用程序有一个配置好的带有 messageConverter 的 restTemplate。如何从接收器应用程序解决它?

@Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
    return createObjectMapperBuilder();
}

private MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setObjectMapper(jackson2ObjectMapperBuilder().build());
    return converter;
}

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
    RestTemplate restTemplate = builder.build();
    restTemplate.setMessageConverters(Collections.singletonList(jackson2HttpMessageConverter()));
    return restTemplate;
}

1 个答案:

答案 0 :(得分:1)

您的问题缺少实际的 restTemplate 调用,所以没有那个我有点猜测......

我假设您的示例 restTemplate 将 JSON 请求正文作为字符串发送。

这就是 causedBy 告诉您的:

<块引用>

引起:com.fasterxml.jackson.databind.exc.MismatchedInputException:无法构造com.service.dto.Request的实例(尽管至少存在一个Creator):没有从字符串值反序列化的字符串参数构造函数/工厂方法( '{

看看这两个示例 REST 端点并自己在本地尝试

@PostMapping("/log")
fun logRequest(@RequestBody request: Request): Request {
    println("/log request: $request")
    return request
}

@PostMapping("/log/string")
fun logRequestString(@RequestBody request: String): String {
    println("/log/string request: $request")
    return request
}

示例 RestTemplate 调用

val restTemplate = RestTemplate().apply {
    messageConverters = listOf(jackson2HttpMessageConverter())
}

// Sending payload as a JSON Object
val requestBody = Request(Customer("id", "name"), Dealer("id", "name"))
val response = restTemplate.postForEntity(url, HttpEntity(requestBody), Object::class.java)
// Server logs: 
// -> /log request: Request(customer=Customer(id=id, name=name), dealer=Dealer(id=id, name=name))

// Sending payload as a string
val headers = HttpHeaders().apply {
    contentType = MediaType.APPLICATION_JSON
}
val response2 = restTemplate.postForEntity(
    "$url/string",
    HttpEntity(jacksonObjectMapper().writeValueAsString(requestBody), headers),
    Object::class.java
)
// Server logs:
// -> /log/string request: "{\"customer\":{\"id\":\"id\",\"name\":\"name\"},\"dealer\":{\"id\":\"id\",\"name\":\"name\"}}"

如果您删除此端点,您将看到同样的错误 no String-argument constructor

@PostMapping("/log/string")
fun logRequestString(@RequestBody request: String): String {
    println("/log/string request: $request")
    return request
}