Maven Java 版本配置被 Eclipse/Idea 忽略
Maven Java Version Configuration ignored by Eclipse/Idea
我有:
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
然而我声明没有问题:
public enum DirectoryWatchService {
INSTANCE;
private java.util.Optional<String> test;
private java.nio.file.Files files;
}
Eclipse 不会打扰。 IntelliJ 也没有。甚至 Maven 也不会打扰。
我什至可以做一个 mvn clean package。在没有任何警告的情况下构建该死的东西。
你遇到了source
/target
选项的交叉编译错误。在您的 class 路径(JDK 7 或 8)中使用主要版本,但希望针对次要版本(在您的情况下为 6)进行编译。
编译没问题,但是你会在 运行time 时出错(即 NoClassDefFoundError
or NoSuchMethodError
, probably also the more generic LinkageError
)。
使用 source
/target
,Java 编译器可以用作交叉编译器来生成 class 文件 运行nable on JDKs implementing Java SE 规范的早期版本。
普遍认为使用两个编译器选项就足够了。但是,source
选项指定我们正在编译的版本,而 target
选项指定要支持的最低 Java 版本。
编译器用于生成字节码,source
和target
用于在交叉编译时生成兼容的字节码。然而,Java API 不被编译器处理(它们作为 JDK 安装的一部分提供,著名的 rt.jar
文件)。编译器不知道 API,它只是针对当前的 rt.jar
进行编译。因此,当使用 JDK 1.7 编译 target=1.6
时,编译器仍将指向 JDK 7 rt.jar
.
那么,怎样才能真正正确的交叉编译呢?
Since JDK 7, javac prints a warning 在 source
/target
未与 bootclasspath
选项组合的情况下。在这些情况下,bootclasspath
选项是指向所需目标 Java 版本的 rt.jar
的关键选项(因此,您需要在您的计算机中安装目标 JDK机器)。因此,javac
将有效地针对好的 Java API.
进行编译
但这可能还不够!
并非所有 Java API 都来自 rt.jar
。其他 classes 由 lib\ext
文件夹提供。为此使用了另一个 javac
选项,extdirs
。来自官方 Oracle 文档
If you are cross-compiling (compiling classes against bootstrap and extension classes of a different Java platform implementation), this option specifies the directories that contain the extension classes.
因此,即使使用 source
/target
和 bootclasspath
选项,我们仍然可能在交叉编译期间遗漏一些东西,正如 official example 中所解释的那样使用 javac 文档。
The Java Platform JDK's javac would also by default compile against its own bootstrap classes, so we need to tell javac to compile against JDK 1.5 bootstrap classes instead. We do this with -bootclasspath and -extdirs. Failing to do this might allow compilation against a Java Platform API that would not be present on a 1.5 VM and would fail at runtime.
但是..可能还是不够!
来自Oracle官方documentation
Even when the bootclasspath and -source/-target are all set appropriately for cross-compilation, compiler-internal contracts, such as how anonymous inner classes are compiled, may differ between, say, javac in JDK 1.4.2 and javac in JDK 6 running with the -target 1.4 option.
建议的解决方案(来自 Oracle official 文档)
The most reliably way to produce class files that will work on a particular JDK and later is to compile the source files using the oldest JDK of interest. Barring that, the bootclasspath must be set for robust cross-compilation to an older JDK.
所以,这真的不可能吗?
Spring 4 currently supports Java 6, 7 and 8。即使使用 Java 7 和 Java 8 个特征和 API。那么它如何与 Java 7 和 8 兼容?!
Spring 利用了 source
/target
和 bootclasspath
的灵活性。 Spring 4 始终使用 source
/target
编译为 Java 6,以便字节码在 JRE 6 下仍然可以 运行。因此没有 Java 7/使用了8种语言特性:语法保持Java 6级。
但是它也用了Java7和Java8API!因此,不使用 bootclasspath
选项。可选,Stream 和许多其他 Java 8 API 被使用。然后它根据 Java 7 或 Java 8 API 仅在 运行 时间检测到 JRE 7/8 时注入 bean:聪明的方法!
但是Spring如何保证API兼容性呢?
使用 Maven Animal Sniffer 插件。
此插件检查您的应用程序是否 API 与指定的 Java 版本兼容。之所以称为动物嗅探器,是因为 Sun 传统上以 different animals 命名不同版本的 Java(Java 4 = Merlin(鸟),Java 5 = Tiger,Java 6 = 野马(马),Java 7 = 海豚,Java 8 = 没有动物)。
您可以将以下内容添加到您的 POM 文件中:
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-maven-plugin</artifactId>
<version>1.14</version>
<configuration>
<signature>
<groupId>org.codehaus.mojo.signature</groupId>
<artifactId>java16</artifactId>
<version>1.0</version>
</signature>
</configuration>
<executions>
<execution>
<id>ensure-java-1.6-class-library</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
并且一旦您使用 JDK 7 API 而希望仅使用 JDK 6 API.
构建将失败
官方也推荐这种用法source
/target
page of the maven-compiler-plugin
Note: Merely setting the target
option does not guarantee that your code actually runs on a JRE with the specified version. The pitfall is unintended usage of APIs that only exist in later JREs which would make your code fail at runtime with a linkage error. To avoid this issue, you can either configure the compiler's boot classpath to match the target JRE or use the Animal Sniffer Maven Plugin to verify your code doesn't use unintended APIs.
Java 9 次更新
在Java9这个机制has been radically changed下面的做法:
javac --release N ...
在语义上等同于
javac -source N -target N -bootclasspath rtN.jar ...
Information about APIs of earlier releases available to javac
- Stored in a compressed fashion
- Only provide Java SE N and JDK N-exported APIs that are platform neutral
- Same set of release values N as for
-source
/ -target
- Incompatible combinations of options rejected
--release N
方法的主要优点:
- No user need to manage artifacts storing old API information
- Should remove need to use tools like the Maven plugin Animal Sniffer
May use newer compilation idioms than the javac
in older releases
- Bug fixes
- Speed improvements
更新 Java 9 和 Maven
由于版本 3.6.0
,maven-compiler-plugin
通过其 release
选项提供对 Java 9 的支持:
The -release
argument for the Java compiler, supported since Java9
一个例子:
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<release>8</release>
</configuration>
</plugin>
我有:
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
然而我声明没有问题:
public enum DirectoryWatchService {
INSTANCE;
private java.util.Optional<String> test;
private java.nio.file.Files files;
}
Eclipse 不会打扰。 IntelliJ 也没有。甚至 Maven 也不会打扰。 我什至可以做一个 mvn clean package。在没有任何警告的情况下构建该死的东西。
你遇到了source
/target
选项的交叉编译错误。在您的 class 路径(JDK 7 或 8)中使用主要版本,但希望针对次要版本(在您的情况下为 6)进行编译。
编译没问题,但是你会在 运行time 时出错(即 NoClassDefFoundError
or NoSuchMethodError
, probably also the more generic LinkageError
)。
使用 source
/target
,Java 编译器可以用作交叉编译器来生成 class 文件 运行nable on JDKs implementing Java SE 规范的早期版本。
普遍认为使用两个编译器选项就足够了。但是,source
选项指定我们正在编译的版本,而 target
选项指定要支持的最低 Java 版本。
编译器用于生成字节码,source
和target
用于在交叉编译时生成兼容的字节码。然而,Java API 不被编译器处理(它们作为 JDK 安装的一部分提供,著名的 rt.jar
文件)。编译器不知道 API,它只是针对当前的 rt.jar
进行编译。因此,当使用 JDK 1.7 编译 target=1.6
时,编译器仍将指向 JDK 7 rt.jar
.
那么,怎样才能真正正确的交叉编译呢?
Since JDK 7, javac prints a warning 在 source
/target
未与 bootclasspath
选项组合的情况下。在这些情况下,bootclasspath
选项是指向所需目标 Java 版本的 rt.jar
的关键选项(因此,您需要在您的计算机中安装目标 JDK机器)。因此,javac
将有效地针对好的 Java API.
但这可能还不够!
并非所有 Java API 都来自 rt.jar
。其他 classes 由 lib\ext
文件夹提供。为此使用了另一个 javac
选项,extdirs
。来自官方 Oracle 文档
If you are cross-compiling (compiling classes against bootstrap and extension classes of a different Java platform implementation), this option specifies the directories that contain the extension classes.
因此,即使使用 source
/target
和 bootclasspath
选项,我们仍然可能在交叉编译期间遗漏一些东西,正如 official example 中所解释的那样使用 javac 文档。
The Java Platform JDK's javac would also by default compile against its own bootstrap classes, so we need to tell javac to compile against JDK 1.5 bootstrap classes instead. We do this with -bootclasspath and -extdirs. Failing to do this might allow compilation against a Java Platform API that would not be present on a 1.5 VM and would fail at runtime.
但是..可能还是不够!
来自Oracle官方documentation
Even when the bootclasspath and -source/-target are all set appropriately for cross-compilation, compiler-internal contracts, such as how anonymous inner classes are compiled, may differ between, say, javac in JDK 1.4.2 and javac in JDK 6 running with the -target 1.4 option.
建议的解决方案(来自 Oracle official 文档)
The most reliably way to produce class files that will work on a particular JDK and later is to compile the source files using the oldest JDK of interest. Barring that, the bootclasspath must be set for robust cross-compilation to an older JDK.
所以,这真的不可能吗?
Spring 4 currently supports Java 6, 7 and 8。即使使用 Java 7 和 Java 8 个特征和 API。那么它如何与 Java 7 和 8 兼容?!
Spring 利用了 source
/target
和 bootclasspath
的灵活性。 Spring 4 始终使用 source
/target
编译为 Java 6,以便字节码在 JRE 6 下仍然可以 运行。因此没有 Java 7/使用了8种语言特性:语法保持Java 6级。
但是它也用了Java7和Java8API!因此,不使用 bootclasspath
选项。可选,Stream 和许多其他 Java 8 API 被使用。然后它根据 Java 7 或 Java 8 API 仅在 运行 时间检测到 JRE 7/8 时注入 bean:聪明的方法!
但是Spring如何保证API兼容性呢?
使用 Maven Animal Sniffer 插件。
此插件检查您的应用程序是否 API 与指定的 Java 版本兼容。之所以称为动物嗅探器,是因为 Sun 传统上以 different animals 命名不同版本的 Java(Java 4 = Merlin(鸟),Java 5 = Tiger,Java 6 = 野马(马),Java 7 = 海豚,Java 8 = 没有动物)。
您可以将以下内容添加到您的 POM 文件中:
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-maven-plugin</artifactId>
<version>1.14</version>
<configuration>
<signature>
<groupId>org.codehaus.mojo.signature</groupId>
<artifactId>java16</artifactId>
<version>1.0</version>
</signature>
</configuration>
<executions>
<execution>
<id>ensure-java-1.6-class-library</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
并且一旦您使用 JDK 7 API 而希望仅使用 JDK 6 API.
构建将失败官方也推荐这种用法source
/target
page of the maven-compiler-plugin
Note: Merely setting the
target
option does not guarantee that your code actually runs on a JRE with the specified version. The pitfall is unintended usage of APIs that only exist in later JREs which would make your code fail at runtime with a linkage error. To avoid this issue, you can either configure the compiler's boot classpath to match the target JRE or use the Animal Sniffer Maven Plugin to verify your code doesn't use unintended APIs.
Java 9 次更新
在Java9这个机制has been radically changed下面的做法:
javac --release N ...
在语义上等同于
javac -source N -target N -bootclasspath rtN.jar ...
Information about APIs of earlier releases available to
javac
- Stored in a compressed fashion
- Only provide Java SE N and JDK N-exported APIs that are platform neutral
- Same set of release values N as for
-source
/-target
- Incompatible combinations of options rejected
--release N
方法的主要优点:
- No user need to manage artifacts storing old API information
- Should remove need to use tools like the Maven plugin Animal Sniffer
May use newer compilation idioms than the
javac
in older releases
- Bug fixes
- Speed improvements
更新 Java 9 和 Maven
由于版本 3.6.0
,maven-compiler-plugin
通过其 release
选项提供对 Java 9 的支持:
The
-release
argument for the Java compiler, supported since Java9
一个例子:
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<release>8</release>
</configuration>
</plugin>