在JDK6,升级为JDK7或者JDK8的过程中会遇到一些奇怪的问题,简单的介绍一下经典的ClassNotFound:sun.nio.*、com.sun.image.codec.jpeg.*等。
背景简介
很多项目使用在JDK6升级到JDK7或者JDK8的过程中,会遇到一些问题。 本文主要介绍一下,在升级过程中,JDK的部分类在Android Studio、Eclipse(以下概称IDE,不包括神器NetBeans)的编译过程中没有提示。 但是使用Maven、Ant、Gradle(这三个工具在下文中概称为打包工具)打包的过程中,会出现错误‘ClassNotFound’、‘程序包sun.net.sdp不存在’。 例如:
- com.sun.image.codec.jpeg.JPEGCodec
- com.sun.image.codec.jpeg.JPEGEncodeParam
- jdk.internal.org.objectweb.asm.commons.AdviceAdapter
- sun.net.sdp.SdpSupport
示例
JDK6可以编译通过、JDK7和JDK8在IDE中可以编译通过,但是使用打包工具,则编译不通过。
代码语言:javascript复制import com.sun.image.codec.jpeg.JPEGCodec;
public class TestCtSymJdk6Pass {
private JPEGCodec jpegCodec;
}
编译尝试
jdk1.6/bin/javac TestCtSymJdk6Pass.java.java
通过,但是会有两个警告。
代码语言:javascript复制TestCtSymJdk6Pass.java:1: 警告:com.sun.image.codec.jpeg.JPEGCodec 是 Sun 的专用 API,可能会在未来版本中删除
import com.sun.image.codec.jpeg.JPEGCodec;
^
TestCtSymJdk6Pass.java:3: 警告:com.sun.image.codec.jpeg.JPEGCodec 是 Sun 的专用 API,可能会在未来版本中删除
private JPEGCodec jpegCodec;
^
2 警告
jdk1.7/bin/javac TestCtSymJdk6Pass.java jdk1.8/bin/javac TestCtSymJdk6Pass.java
不通过,两个错误
代码语言:javascript复制TestCtSymJdk6Pass.java:1: 错误: 程序包com.sun.image.codec.jpeg不存在
import com.sun.image.codec.jpeg.JPEGCodec;
^
TestCtSymJdk6Pass.java:3: 错误: 找不到符号
private JPEGCodec jpegCodec;
^
符号: 类 JPEGCodec
位置: 类 TestCtSymJdk6
2 个错误
JDK6-8都编译不通过
代码语言:javascript复制import com.sun.image.codec.jpeg.JPEGCodec;
import sun.net.sdp.SdpSupport;
public class TestCtSymJdk6NotPass {
private JPEGCodec jpegCodec;
private SdpSupport sdpSupport;
}
JDK6报错
代码语言:javascript复制TestCtSymJdk6NotPass.java:1: 警告:com.sun.image.codec.jpeg.JPEGCodec 是 Sun 的专用 API,可能会在未来版本中删除
import com.sun.image.codec.jpeg.JPEGCodec;
^
TestCtSymJdk6NotPass.java:2: 软件包 sun.net.sdp 不存在
import sun.net.sdp.SdpSupport;
^
TestCtSymJdk6NotPass.java:4: 警告:com.sun.image.codec.jpeg.JPEGCodec 是 Sun 的专用 API,可能会在未来版本中删除
private JPEGCodec jpegCodec;
^
TestCtSymJdk6NotPass.java:5: 找不到符号
符号: 类 SdpSupport
位置: 类 TestCtSymJdk6NotPass
private SdpSupport sdpSupport;
^
2 错误
2 警告
添加编译参数:忽略链接文件
jdk1.6/bin/javac -XDignore.symbol.file TestCtSymJdk6NotPass.java jdk1.7/bin/javac -XDignore.symbol.file TestCtSymJdk6Pass.java jdk1.8/bin/javac -XDignore.symbol.file TestCtSymJdk6Pass.java
以上三条命令都可以正常执行。
原因
在JDK6以及以后的版本,JDK在目录下新增了一个链接文件${JDK_HOME}/lib/ct.sym文件。 在使用javac命令进行编译代码时,默认使用该文件进行编译时class类的检查和链接,而不是使用rt.jar。
该文件保存了JDK建议使用的类描述信息。com.sun.*包和sun.*包,以及新的jdk.*都不是Open的API,是JDK内部的私有类,这些类的接口可能在之后的版本变动,也不保证平台移植性。
事实上,JDK提供的Public API,仅有三个包:java.*、javax.*、org.*。它们是官方支持的公共接口(Official、Supported、Public )。
ct.sym文件是一个zip压缩包,它里面包含了部分rt.jar中的类。
ct.sym中的类文件都是简单的空函数,不包含函数体,所以非常小。
ct.sym中如果没有该类,则会出现ClassNotFound的错误。 比如JDK6中的sun.net.sdp.SdpSupport类。在ct.sym中就没有sun.net.sdp包。
比如JDK7中的com.sun.image.codec.jpeg.JPEGCodec类。
解决方案
方案-1 【建议】
使用JDK开放的接口实现这部分功能。
方案-2 【临时方案】
在编译的时候加入参数-XDignore.symbol.file.
Maven
代码语言:javascript复制<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<compilerArgs>
<arg>-XDignore.symbol.file</arg>
</compilerArgs>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
Ant
代码语言:javascript复制<target name="compile">
<javac
srcdir="src"
destdir="bin"
encoding="UTF-8"
source="1.8"
target="1.8">
<compilerarg value="-XDignore.symbol.file"/>
<classpath refid="classpath" />
</javac>
</target>
Gradle
代码语言:javascript复制compileJava.options.compilerArgs = [ '-XDignore.symbol.file=true' ]
方案-3 【不建议】
非常粗暴!直接删除ct.sym文件。
PS
PS
神器-NetBeans在IDE中就会提示该错误。