仅当 运行 通过 Eclipse 时:POST on Spring Boot Controller 失败并显示 HTTP 错误 415
Only if run through Eclipse: POST on Spring Boot Controller fails with HTTP Error 415
问题:
我们有一个可用的 Spring 引导后端服务器(Java 11,Spring 引导 2.2。4.RELEASE)带有 React 前端, 运行 在 Docker 容器中完美 ,如果 运行 通过 java -jar app.jar
并且如果 运行通过 IntelliJ。
如果它是 运行 通过 Eclipse(运行ning on Windows Server 2016), 在尝试发送 POST 与 JSON body, 遇到以下错误:
2020-03-09 15:09:52.515 WARN 218960 --- [nio-8080-exec-1] .c.j.MappingJackson2HttpMessageConverter : Failed to evaluate Jackson deserialization for type [[simple type, class xxx.xxx.xxx.xxx.xxx.xxx.xxx]]: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type `xxx.xxx.xxx.xxx.xxx.xxx.xxx`: Argument #0 has no property name, is not Injectable: can not use as Creator [constructor for xxx.xxx.xxx.xxx.xxx.xxx.xxx, annotations: {interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}]
2020-03-09 15:09:52.517 WARN 218960 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported]
无论通过前端代码设置的 headers,都会发生此错误。所以通过设置 Content-Type: application/json,同样的错误发生(见下面的 headers)。
有没有人body遇到过只在 Eclipse 中发生的类似事情?
我已经检查了编码(在 Eclipse 中将所有内容设置为 UTF-8)并确保所有环境都使用相同的 Java 版本(我正在使用 AdoptOpenJDK 11.0.6.hs-adpt 安装通过 SDKMan) 在 IntelliJ 和 Eclipse 中。
编译后的 classes 文件看起来完全一样。 class应用程序 运行 时的路径包含相同的 jackson 库(总体上看起来非常相似)。
我相信这与 Eclipse 运行 应用程序的运行方式有关。有谁知道为什么会观察到这种奇怪的行为?为什么只使用 Eclipse?而我们有一些开发者在使用Eclipse,我们该如何解决呢?
有关请求的更多信息:
通过浏览器看到的请求headers:
POST /server/configure HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 510
Accept: application/json, text/plain, */*
Sec-Fetch-Dest: empty
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36
Content-Type: application/json
Origin: http://localhost:3000
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Referer: http://localhost:3000/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,de-DE;q=0.8,de;q=0.7
响应:
{"timestamp":"2020-03-09T14:00:27.285+0000","status":415,"error":"Unsupported Media Type","message":"Content type 'application/json;charset=UTF-8' not supported","path":"/server/configure"}
响应headers:
HTTP/1.1 415
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: http://localhost:3000
Content-Type: application/json
Transfer-Encoding: chunked
Date: Mon, 09 Mar 2020 13:53:05 GMT
Keep-Alive: timeout=60
Connection: keep-alive
有关该应用程序的更多信息:
控制器如下(简化):
package xxx.xxx.xxx.xxx.xxx.xxx;
...
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
...
@Controller
@RequestMapping("/server")
@CrossOrigin(origins = "http://localhost:3000")
public class Controller {
private someService SomeService;
@Autowired
public Controller(SomeService someService) {
this.someService= someService;
}
@PostMapping(value = "/configure", consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String configureConnection(@RequestBody ConnectionConfiguration connectionConfiguration) throws MalformedURLException {
String serverId = someService.configureConnection(connectionConfiguration);
return serverId;
}
}
ConnectionConfigurationclass如下(此处显示较少的属性):
package xxx.xxx.xxx.xxx.xxx.xxx;
import com.fasterxml.jackson.annotation.JsonCreator;
public class ConnectionConfiguration {
private String someProperty;
@JsonCreator
public ConnectionConfiguration(String someProperty) {
this.someProperty= someProperty;
}
public String getSomeProperty() {
return someProperty;
}
}
关于项目结构,我们有一个根文件夹,其中包含一个 backend 和一个 frontend 文件夹。每个(根、后端和前端)都包含一个 build.gradle 文件,通过根文件夹中的 build.gradle 链接在一起。通过这样做,我们可以制作构建前端的生产构建,将静态 HTML 和 Java 脚本文件复制到后端的 resources/static 文件夹,然后构建 Spring 引导罐。然后,在生产模式下,前端通过 Spring Boot 的 Tomcat.
交付
对于开发,我们通常通过 npm run start
和 Spring 从 Application.java class 包含主要方法的启动应用程序启动前端的 Webpack 开发服务器通过 IDE:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
在拥有相同的 class 路径后,我们检查了构建的 .class 文件是否真的相同 - 它们不是(见下文)。我们意识到,在 Eclipse 的 Preferences/Java/Compiler 中设置 "Store information about method parameters (usable via reflection)" 选项后,当 运行 后端通过 Eclipse:
时,everythinkg 也能正常工作
知道了这一点,反序列化不起作用的原因就明白了。 Jackson 依靠我们构造函数中用 @JsonCreator
注释的参数名称来这样做,它通过反射获取名称。
另请参阅:
https://github.com/FasterXML/jackson-modules-java8/tree/master/parameter-names
Why when a constructor is annotated with @JsonCreator, its arguments must be annotated with @JsonProperty?
另一种解决方案,可能更安全,因为您不必编辑 IDE 的设置:
如果我们用 @JsonProperty("name")
注释参数,它也可以在没有 -parameters
编译选项的情况下工作:
package xxx.xxx.xxx.xxx.xxx.xxx;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class ConnectionConfiguration {
private String someProperty;
@JsonCreator
public ConnectionConfiguration(@JsonProperty("someProperty") String someProperty) {
this.someProperty= someProperty;
}
public String getSomeProperty() {
return someProperty;
}
}
字节码部分(没有 @JsonProperty
注释):
用构建-parameters
:
...
// access flags 0x1
// signature (Ljava/lang/String;)V
// declaration: java.lang.String)
public <init>(Ljava/lang/String;)V
// parameter someProperty
@Lcom/fasterxml/jackson/annotation/JsonCreator;()
L0
LINENUMBER 17 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
...
建立没有 -parameters
:
...
// access flags 0x1
// signature (Ljava/lang/String;)V
// declaration: java.lang.String)
public <init>(Ljava/lang/String;)V
@Lcom/fasterxml/jackson/annotation/JsonCreator;()
L0
LINENUMBER 16 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
...
问题:
我们有一个可用的 Spring 引导后端服务器(Java 11,Spring 引导 2.2。4.RELEASE)带有 React 前端, 运行 在 Docker 容器中完美 ,如果 运行 通过 java -jar app.jar
并且如果 运行通过 IntelliJ。
如果它是 运行 通过 Eclipse(运行ning on Windows Server 2016), 在尝试发送 POST 与 JSON body, 遇到以下错误:
2020-03-09 15:09:52.515 WARN 218960 --- [nio-8080-exec-1] .c.j.MappingJackson2HttpMessageConverter : Failed to evaluate Jackson deserialization for type [[simple type, class xxx.xxx.xxx.xxx.xxx.xxx.xxx]]: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type `xxx.xxx.xxx.xxx.xxx.xxx.xxx`: Argument #0 has no property name, is not Injectable: can not use as Creator [constructor for xxx.xxx.xxx.xxx.xxx.xxx.xxx, annotations: {interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}]
2020-03-09 15:09:52.517 WARN 218960 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported]
无论通过前端代码设置的 headers,都会发生此错误。所以通过设置 Content-Type: application/json,同样的错误发生(见下面的 headers)。
有没有人body遇到过只在 Eclipse 中发生的类似事情?
我已经检查了编码(在 Eclipse 中将所有内容设置为 UTF-8)并确保所有环境都使用相同的 Java 版本(我正在使用 AdoptOpenJDK 11.0.6.hs-adpt 安装通过 SDKMan) 在 IntelliJ 和 Eclipse 中。
编译后的 classes 文件看起来完全一样。 class应用程序 运行 时的路径包含相同的 jackson 库(总体上看起来非常相似)。
我相信这与 Eclipse 运行 应用程序的运行方式有关。有谁知道为什么会观察到这种奇怪的行为?为什么只使用 Eclipse?而我们有一些开发者在使用Eclipse,我们该如何解决呢?
有关请求的更多信息:
通过浏览器看到的请求headers:
POST /server/configure HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 510
Accept: application/json, text/plain, */*
Sec-Fetch-Dest: empty
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36
Content-Type: application/json
Origin: http://localhost:3000
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Referer: http://localhost:3000/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,de-DE;q=0.8,de;q=0.7
响应:
{"timestamp":"2020-03-09T14:00:27.285+0000","status":415,"error":"Unsupported Media Type","message":"Content type 'application/json;charset=UTF-8' not supported","path":"/server/configure"}
响应headers:
HTTP/1.1 415
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: http://localhost:3000
Content-Type: application/json
Transfer-Encoding: chunked
Date: Mon, 09 Mar 2020 13:53:05 GMT
Keep-Alive: timeout=60
Connection: keep-alive
有关该应用程序的更多信息:
控制器如下(简化):
package xxx.xxx.xxx.xxx.xxx.xxx;
...
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
...
@Controller
@RequestMapping("/server")
@CrossOrigin(origins = "http://localhost:3000")
public class Controller {
private someService SomeService;
@Autowired
public Controller(SomeService someService) {
this.someService= someService;
}
@PostMapping(value = "/configure", consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String configureConnection(@RequestBody ConnectionConfiguration connectionConfiguration) throws MalformedURLException {
String serverId = someService.configureConnection(connectionConfiguration);
return serverId;
}
}
ConnectionConfigurationclass如下(此处显示较少的属性):
package xxx.xxx.xxx.xxx.xxx.xxx;
import com.fasterxml.jackson.annotation.JsonCreator;
public class ConnectionConfiguration {
private String someProperty;
@JsonCreator
public ConnectionConfiguration(String someProperty) {
this.someProperty= someProperty;
}
public String getSomeProperty() {
return someProperty;
}
}
关于项目结构,我们有一个根文件夹,其中包含一个 backend 和一个 frontend 文件夹。每个(根、后端和前端)都包含一个 build.gradle 文件,通过根文件夹中的 build.gradle 链接在一起。通过这样做,我们可以制作构建前端的生产构建,将静态 HTML 和 Java 脚本文件复制到后端的 resources/static 文件夹,然后构建 Spring 引导罐。然后,在生产模式下,前端通过 Spring Boot 的 Tomcat.
交付对于开发,我们通常通过 npm run start
和 Spring 从 Application.java class 包含主要方法的启动应用程序启动前端的 Webpack 开发服务器通过 IDE:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
在拥有相同的 class 路径后,我们检查了构建的 .class 文件是否真的相同 - 它们不是(见下文)。我们意识到,在 Eclipse 的 Preferences/Java/Compiler 中设置 "Store information about method parameters (usable via reflection)" 选项后,当 运行 后端通过 Eclipse:
时,everythinkg 也能正常工作知道了这一点,反序列化不起作用的原因就明白了。 Jackson 依靠我们构造函数中用 @JsonCreator
注释的参数名称来这样做,它通过反射获取名称。
另请参阅:
https://github.com/FasterXML/jackson-modules-java8/tree/master/parameter-names
Why when a constructor is annotated with @JsonCreator, its arguments must be annotated with @JsonProperty?
另一种解决方案,可能更安全,因为您不必编辑 IDE 的设置:
如果我们用 @JsonProperty("name")
注释参数,它也可以在没有 -parameters
编译选项的情况下工作:
package xxx.xxx.xxx.xxx.xxx.xxx;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class ConnectionConfiguration {
private String someProperty;
@JsonCreator
public ConnectionConfiguration(@JsonProperty("someProperty") String someProperty) {
this.someProperty= someProperty;
}
public String getSomeProperty() {
return someProperty;
}
}
字节码部分(没有 @JsonProperty
注释):
用构建-parameters
:
...
// access flags 0x1
// signature (Ljava/lang/String;)V
// declaration: java.lang.String)
public <init>(Ljava/lang/String;)V
// parameter someProperty
@Lcom/fasterxml/jackson/annotation/JsonCreator;()
L0
LINENUMBER 17 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
...
建立没有 -parameters
:
...
// access flags 0x1
// signature (Ljava/lang/String;)V
// declaration: java.lang.String)
public <init>(Ljava/lang/String;)V
@Lcom/fasterxml/jackson/annotation/JsonCreator;()
L0
LINENUMBER 16 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
...