您遇到了交叉编译的误解source
/target
选项。在类路径中使用主要版本(JDK 7 或 8),但希望针对次要版本(在您的情况下为 6)进行编译。
编译没问题,但是你会出现错误runtime (i.e. NoClassDefFoundError https://docs.oracle.com/javase/7/docs/api/java/lang/NoClassDefFoundError.html or NoSuchMethodError https://docs.oracle.com/javase/7/docs/api/java/lang/NoSuchMethodError.html,可能也是更通用的LinkageError https://docs.oracle.com/javase/7/docs/api/java/lang/LinkageError.html).
Using source/target http://docs.oracle.com/javase/6/docs/technotes/tools/solaris/javac.html#options,Java 编译器可以用作交叉编译器来生成可在实现早期版本的 Java SE 规范的 JDK 上运行的类文件。
人们普遍认为使用两个编译器选项就足够了。但是,那source
选项指定我们正在编译的版本target
选项指定要支持的最低 Java 版本。
编译器致力于字节码生成和source
and target
用于在交叉编译期间生成兼容的字节码。然而,Java API 不是由编译器处理的(它们是作为 JDK 安装的一部分提供的,著名的rt.jar
文件)。编译器对 API 没有任何了解,它只是根据当前的 API 进行编译rt.jar
。因此,当编译时target=1.6
使用JDK 1.7,编译器仍会指向JDK 7rt.jar
.
那么,我们怎样才能真正进行正确的交叉编译呢?
从 JDK 7 开始,javac 会打印警告 https://blogs.oracle.com/darcy/entry/bootclasspath_older_source的情况下source
/target
不与bootclasspath
选项。这bootclasspath
选项是在这些情况下指向的关键选项rt.jar
所需的目标 Java 版本(因此,您需要在计算机中安装目标 JDK)。像这样,javac
将有效地针对良好的 Java API 进行编译。
但这可能还不够!
并非所有 Java API 都来自rt.jar
。其他课程由lib\ext
文件夹。进一步javac
选项用于此目的,extdirs
。来自 Oracle 官方文档
如果您正在进行交叉编译(针对不同 Java 平台实现的引导类和扩展类进行编译),则此选项指定包含扩展类的目录。
因此,即使使用source
/target
and bootclasspath
选项,我们在交叉编译过程中仍然可能会错过一些东西,正如官方示例 http://docs.oracle.com/javase/6/docs/technotes/tools/solaris/javac.html#crosscomp-example随 javac 文档一起提供。
Java 平台 JDK 的 javac 默认情况下也会针对其自己的引导类进行编译,因此我们需要告诉 javac 针对 JDK 1.5 引导类进行编译。我们使用 -bootclasspath 和 -extdirs 来完成此操作。如果不这样做,可能会允许针对 Java 平台 API 进行编译,而该 API 不会出现在 1.5 VM 上,并且会在运行时失败。
但是..可能还不够!
来自甲骨文官方文档 https://blogs.oracle.com/darcy/entry/how_to_cross_compile_for
即使 bootclasspath 和 -source/-target 都为交叉编译进行了适当设置,编译器内部约定(例如匿名内部类的编译方式)可能会有所不同,例如 JDK 1.4.2 中的 javac 和 JDK 6 中的 javac使用 -target 1.4 选项运行。
建议的解决方案(来自甲骨文官方 https://blogs.oracle.com/darcy/entry/how_to_cross_compile_for文档)
生成可在特定 JDK 及更高版本上运行的类文件的最可靠方法是使用最旧的 JDK 来编译源文件。除此之外,必须设置 bootclasspath 才能对旧版 JDK 进行稳健的交叉编译。
那么,这真的不可能吗?
Spring 4 目前支持 Java 6、7 和 8 https://spring.io/blog/2015/04/03/how-spring-achieves-compatibility-with-java-6-7-and-8。甚至使用 Java 7 和 Java 8 功能和 API。那它怎么能兼容Java 7和8呢?!
Spring利用source
/target
and bootclasspath
灵活性。 Spring 4 总是编译source
/target
到 Java 6,以便字节码仍然可以在 JRE 6 下运行。因此,不使用 Java 7/8 语言功能:语法保持 Java 6 水平。
但它也使用 Java 7 和 Java 8 API!因此,bootclasspath
不使用选项。使用了可选的 Stream 和许多其他 Java 8 API。然后,仅当运行时检测到 JRE 7/8 时,它才会根据 Java 7 或 Java 8 API 注入 Bean:聪明的方法!
那么 Spring 如何保证 API 兼容性呢?
使用Maven 动物嗅探器 http://www.mojohaus.org/animal-sniffer/animal-sniffer-maven-plugin/ plugin.
该插件检查您的应用程序是否与指定的 Java 版本 API 兼容。被称为动物嗅探器,因为 Sun 传统上以不同版本的 Java 命名不同的动物 http://www.oracle.com/technetwork/java/javase/codenames-136090.html(Java 4 = 梅林(鸟),Java 5 = 老虎,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的页面maven-compiler-plugin https://maven.apache.org/plugins/maven-compiler-plugin/examples/set-compiler-source-and-target.html
Note: 只需设置target
选项不保证您的代码实际上在指定版本的 JRE 上运行。陷阱是意外使用仅存在于后续 JRE 中的 API,这会使您的代码在运行时失败并出现链接错误。为了避免此问题,您可以配置编译器的启动类路径以匹配目标 JRE,或者使用 Animal Sniffer Maven 插件来验证您的代码没有使用意外的 API。
Java 9 更新
在Java 9中这个机制已被彻底改变 https://blogs.oracle.com/darcy/resource/JavaOne/J1_2016-jdk9-lang-tools-libs.pdf采用以下方法:
javac --release N ...
在语义上等同于
javac -source N -target N -bootclasspath rtN.jar ...
-
有关可用的早期版本 API 的信息javac
- 以压缩方式存储
- 只提供Java SEN和JDKN-导出的API是平台中立的
- 同一组释放值N as for
-source
/ -target
- 不兼容的选项组合被拒绝
主要优点--release N
方法:
Java 9 和 Maven 更新
自版本以来3.6.0
, the maven-compiler-plugin
通过其提供对 Java 9 的支持release https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#release option:
The -release
Java 编译器的参数,从 Java9 开始支持
一个例子:
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<release>8</release>
</configuration>
</plugin>