JSON 带有 javac "-parameters" 的 ObjectMapper 在 运行 通过 maven 而不是通过 IntelliJ IDEA 时表现
JSON ObjectMapper with javac "-parameters" behaves when run via maven, not via InteliJ IDEA
正如您可能从标题中了解到的那样,这是一个有点复杂的问题。
首先,我的目标:
我正在尝试实现 java 类 与 JSON 之间的转换,而无需添加任何 json-specific 注释。
我的 java 类 包括不可变的, 必须 从传递给构造函数的参数初始化它们的成员,所以我必须有 multi-parameter 个可以工作的构造函数 没有 @JsonCreator 和 @JsonParameter。
我正在使用 jackson ObjectMapper。如果有另一个我可以使用的 ObjectMapper 没有本文描述的问题,我会很乐意使用它,但它必须与 jackson ObjectMapper 同等信誉。 (所以,我不愿意从他的GitHub那里下载Jim的ObjectMapper。)
我对如何实际实现这一点的理解,以防我在某处出错:
Java 用于使方法(和构造函数)参数 types 可通过反射发现,而不是参数 names。这就是为什么 @JsonCreator 和 @JsonParameter 注释过去是必需的:告诉 json ObjectMapper 哪个构造函数参数对应于哪个 属性。使用 Java 8,如果您提供新的 -parameters
参数,编译器会将方法(和构造函数)参数名称发送到字节码中,并将通过反射使它们可用,并且最新版本的 jackson ObjectMapper 支持这样,所以现在应该可以在没有任何 json-specific 注释的情况下进行 json object 映射。
我有这个pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test.json</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Json Test</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<sourceDirectory>main</sourceDirectory>
<testSourceDirectory>test</testSourceDirectory>
<plugins>
<plugin>
<!--<groupId>org.apache.maven.plugins</groupId>-->
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<!--<compilerArgument>-parameters</compilerArgument>-->
<!--<fork>true</fork>-->
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
我用它来编译 运行 下面的小 self-contained 程序:
package jsontest;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import java.io.IOException;
import java.lang.reflect.*;
public final class MyMain
{
public static void main( String[] args ) throws IOException, NoSuchMethodException
{
Method m = MyMain.class.getMethod("main", String[].class);
Parameter mp = m.getParameters()[0];
if( !mp.isNamePresent() || !mp.getName().equals("args") )
throw new RuntimeException();
Constructor<MyMain> c = MyMain.class.getConstructor(String.class,String.class);
Parameter m2p0 = c.getParameters()[0];
if( !m2p0.isNamePresent() || !m2p0.getName().equals("s1") )
throw new RuntimeException();
Parameter m2p1 = c.getParameters()[1];
if( !m2p1.isNamePresent() || !m2p1.getName().equals("s2") )
throw new RuntimeException();
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule( new ParameterNamesModule() ); // "-parameters" option must be passed to the java compiler for this to work.
mapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true );
mapper.configure( SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true );
mapper.setSerializationInclusion( JsonInclude.Include.ALWAYS );
mapper.setVisibility( PropertyAccessor.ALL, JsonAutoDetect.Visibility.PUBLIC_ONLY );
mapper.enableDefaultTyping( ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY );
MyMain t = new MyMain( "1", "2" );
String json = mapper.writeValueAsString( t );
/*
* Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class saganaki.Test]: can not
* instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
*/
t = mapper.readValue( json, MyMain.class );
if( !t.s1.equals( "1" ) || !t.s2.equals( "2" ) )
throw new RuntimeException();
System.out.println( "Success!" );
}
public final String s1;
public final String s2;
public MyMain( String s1, String s2 )
{
this.s1 = s1;
this.s2 = s2;
}
}
事情是这样的:
如果我使用 mvn clean compile
编译程序然后 运行 或从 Idea 中调试它,它工作正常并显示 "Success!"。
如果我在 Intellij Idea 中执行 "Rebuild Project" 然后我 run/debug,它会失败并显示 JsonMappingException
"No suitable constructor found for simple type jsontest.MyMain".
(对我来说)奇怪的是,在 ObjectMapper 实例化之前的代码检查以确保构造函数参数名称存在且有效,从而有效地确保“-parameters”参数具有已成功传递给编译器,并且这些检查总是通过!
如果我在 Idea 和 "Before launch" 部分中编辑 "debug configuration",我会删除 "Make" 并将其替换为 "Run maven goal" compile
然后我可以从 Idea 中成功 运行 我的程序,但是 我不想做必须这样做。 (而且,它甚至不能很好地工作,我想我一定是做错了什么:我经常 运行 并且它失败并出现与上面相同的异常,而下一次我 运行 它成功了。)
所以,这是我的问题:
为什么我的程序在用 maven 编译时和用 Idea 编译时表现不同?
- 更具体地说:ObjectMapper 的问题是什么,因为我的断言证明“-parameters”参数 被 传递给了编译器,并且参数 do有名字吗?
我该怎么做才能让 Idea 以与 maven 相同的方式编译我的程序(至少就手头的问题而言)无需 替换 Idea 的 "Make"?
为什么我在Idea的调试配置中将默认的"Make"替换为"Run maven goal"compile
时不能始终如一地工作? (我做错了什么?)
编辑
抱歉,assert
离子不一定能证明任何事情,因为它们不一定与 -enableassertions
一起启用。我将它们替换为 if() throw RuntimeException()
以避免混淆。
据我在 IntelliJ Community edition sources 中看到的,IntelliJ 没有对您指定的 compilerArgs
做任何事情。
在MavenProject.java中,有两个地方正在读取compilerArgs
:
Element compilerArguments = compilerConfiguration.getChild("compilerArgs");
if (compilerArguments != null) {
for (Element element : compilerArguments.getChildren()) {
String arg = element.getValue();
if ("-proc:none".equals(arg)) {
return ProcMode.NONE;
}
if ("-proc:only".equals(arg)) {
return ProcMode.ONLY;
}
}
}
和
Element compilerArgs = compilerConfig.getChild("compilerArgs");
if (compilerArgs != null) {
for (Element e : compilerArgs.getChildren()) {
if (!StringUtil.equals(e.getName(), "arg")) continue;
String arg = e.getTextTrim();
addAnnotationProcessorOption(arg, res);
}
}
第一个代码块只查看 -proc:
参数,因此可以忽略此块。第二个是将 arg
元素(您指定的)的值传递给 addAnnotationProcessorOption
方法。
private static void addAnnotationProcessorOption(String compilerArg, Map<String, String> optionsMap) {
if (compilerArg == null || compilerArg.trim().isEmpty()) return;
if (compilerArg.startsWith("-A")) {
int idx = compilerArg.indexOf('=', 3);
if (idx >= 0) {
optionsMap.put(compilerArg.substring(2, idx), compilerArg.substring(idx + 1));
} else {
optionsMap.put(compilerArg.substring(2), "");
}
}
}
此方法仅处理以 -A
开头的参数,这些参数用于将选项传递给注释处理器。忽略其他参数。
目前,从 IntelliJ 中获取 运行 源代码的唯一方法是在编译器设置(不可移植)的 "Additional command line parameters" 字段中自行启用标志,或者通过使用 maven 编译作为 运行 配置中的预制作步骤。如果您希望在 IntelliJ 中自动执行此操作,您可能必须向 Jetbrains 提出问题。
正如您可能从标题中了解到的那样,这是一个有点复杂的问题。
首先,我的目标:
我正在尝试实现 java 类 与 JSON 之间的转换,而无需添加任何 json-specific 注释。
我的 java 类 包括不可变的, 必须 从传递给构造函数的参数初始化它们的成员,所以我必须有 multi-parameter 个可以工作的构造函数 没有 @JsonCreator 和 @JsonParameter。
我正在使用 jackson ObjectMapper。如果有另一个我可以使用的 ObjectMapper 没有本文描述的问题,我会很乐意使用它,但它必须与 jackson ObjectMapper 同等信誉。 (所以,我不愿意从他的GitHub那里下载Jim的ObjectMapper。)
我对如何实际实现这一点的理解,以防我在某处出错:
Java 用于使方法(和构造函数)参数 types 可通过反射发现,而不是参数 names。这就是为什么 @JsonCreator 和 @JsonParameter 注释过去是必需的:告诉 json ObjectMapper 哪个构造函数参数对应于哪个 属性。使用 Java 8,如果您提供新的 -parameters
参数,编译器会将方法(和构造函数)参数名称发送到字节码中,并将通过反射使它们可用,并且最新版本的 jackson ObjectMapper 支持这样,所以现在应该可以在没有任何 json-specific 注释的情况下进行 json object 映射。
我有这个pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test.json</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Json Test</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<sourceDirectory>main</sourceDirectory>
<testSourceDirectory>test</testSourceDirectory>
<plugins>
<plugin>
<!--<groupId>org.apache.maven.plugins</groupId>-->
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<!--<compilerArgument>-parameters</compilerArgument>-->
<!--<fork>true</fork>-->
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
我用它来编译 运行 下面的小 self-contained 程序:
package jsontest;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import java.io.IOException;
import java.lang.reflect.*;
public final class MyMain
{
public static void main( String[] args ) throws IOException, NoSuchMethodException
{
Method m = MyMain.class.getMethod("main", String[].class);
Parameter mp = m.getParameters()[0];
if( !mp.isNamePresent() || !mp.getName().equals("args") )
throw new RuntimeException();
Constructor<MyMain> c = MyMain.class.getConstructor(String.class,String.class);
Parameter m2p0 = c.getParameters()[0];
if( !m2p0.isNamePresent() || !m2p0.getName().equals("s1") )
throw new RuntimeException();
Parameter m2p1 = c.getParameters()[1];
if( !m2p1.isNamePresent() || !m2p1.getName().equals("s2") )
throw new RuntimeException();
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule( new ParameterNamesModule() ); // "-parameters" option must be passed to the java compiler for this to work.
mapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true );
mapper.configure( SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true );
mapper.setSerializationInclusion( JsonInclude.Include.ALWAYS );
mapper.setVisibility( PropertyAccessor.ALL, JsonAutoDetect.Visibility.PUBLIC_ONLY );
mapper.enableDefaultTyping( ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY );
MyMain t = new MyMain( "1", "2" );
String json = mapper.writeValueAsString( t );
/*
* Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class saganaki.Test]: can not
* instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
*/
t = mapper.readValue( json, MyMain.class );
if( !t.s1.equals( "1" ) || !t.s2.equals( "2" ) )
throw new RuntimeException();
System.out.println( "Success!" );
}
public final String s1;
public final String s2;
public MyMain( String s1, String s2 )
{
this.s1 = s1;
this.s2 = s2;
}
}
事情是这样的:
如果我使用
mvn clean compile
编译程序然后 运行 或从 Idea 中调试它,它工作正常并显示 "Success!"。如果我在 Intellij Idea 中执行 "Rebuild Project" 然后我 run/debug,它会失败并显示
JsonMappingException
"No suitable constructor found for simple type jsontest.MyMain".(对我来说)奇怪的是,在 ObjectMapper 实例化之前的代码检查以确保构造函数参数名称存在且有效,从而有效地确保“-parameters”参数具有已成功传递给编译器,并且这些检查总是通过!
如果我在 Idea 和 "Before launch" 部分中编辑 "debug configuration",我会删除 "Make" 并将其替换为 "Run maven goal"
compile
然后我可以从 Idea 中成功 运行 我的程序,但是 我不想做必须这样做。 (而且,它甚至不能很好地工作,我想我一定是做错了什么:我经常 运行 并且它失败并出现与上面相同的异常,而下一次我 运行 它成功了。)
所以,这是我的问题:
为什么我的程序在用 maven 编译时和用 Idea 编译时表现不同?
- 更具体地说:ObjectMapper 的问题是什么,因为我的断言证明“-parameters”参数 被 传递给了编译器,并且参数 do有名字吗?
我该怎么做才能让 Idea 以与 maven 相同的方式编译我的程序(至少就手头的问题而言)无需 替换 Idea 的 "Make"?
为什么我在Idea的调试配置中将默认的"Make"替换为"Run maven goal"
compile
时不能始终如一地工作? (我做错了什么?)
编辑
抱歉,assert
离子不一定能证明任何事情,因为它们不一定与 -enableassertions
一起启用。我将它们替换为 if() throw RuntimeException()
以避免混淆。
据我在 IntelliJ Community edition sources 中看到的,IntelliJ 没有对您指定的 compilerArgs
做任何事情。
在MavenProject.java中,有两个地方正在读取compilerArgs
:
Element compilerArguments = compilerConfiguration.getChild("compilerArgs");
if (compilerArguments != null) {
for (Element element : compilerArguments.getChildren()) {
String arg = element.getValue();
if ("-proc:none".equals(arg)) {
return ProcMode.NONE;
}
if ("-proc:only".equals(arg)) {
return ProcMode.ONLY;
}
}
}
和
Element compilerArgs = compilerConfig.getChild("compilerArgs");
if (compilerArgs != null) {
for (Element e : compilerArgs.getChildren()) {
if (!StringUtil.equals(e.getName(), "arg")) continue;
String arg = e.getTextTrim();
addAnnotationProcessorOption(arg, res);
}
}
第一个代码块只查看 -proc:
参数,因此可以忽略此块。第二个是将 arg
元素(您指定的)的值传递给 addAnnotationProcessorOption
方法。
private static void addAnnotationProcessorOption(String compilerArg, Map<String, String> optionsMap) {
if (compilerArg == null || compilerArg.trim().isEmpty()) return;
if (compilerArg.startsWith("-A")) {
int idx = compilerArg.indexOf('=', 3);
if (idx >= 0) {
optionsMap.put(compilerArg.substring(2, idx), compilerArg.substring(idx + 1));
} else {
optionsMap.put(compilerArg.substring(2), "");
}
}
}
此方法仅处理以 -A
开头的参数,这些参数用于将选项传递给注释处理器。忽略其他参数。
目前,从 IntelliJ 中获取 运行 源代码的唯一方法是在编译器设置(不可移植)的 "Additional command line parameters" 字段中自行启用标志,或者通过使用 maven 编译作为 运行 配置中的预制作步骤。如果您希望在 IntelliJ 中自动执行此操作,您可能必须向 Jetbrains 提出问题。