JNLP class 加载:截断 Class 文件(不是 Maven 缓存问题!)

JNLP class loading: Truncated Class File (not a maven cache issue!)

我有一个包含多个 jar 的项目,其中一部分是我们的,另一部分是第 3 方库。应用程序必须通过 Java WebStart (JNLP) 运行。同时我遇到了两次,如果第 3 方库是 "too new",则加载失败并出现以下异常:

java.lang.ClassFormatError: Truncated class file
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
at java.net.URLClassLoader.access0(URLClassLoader.java:73)
at java.net.URLClassLoader.run(URLClassLoader.java:368)
at java.net.URLClassLoader.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at com.sun.jnlp.JNLPClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at com.sun.jnlp.JNLPClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at <...>.ApplicationImpl.<init>(ApplicationImpl.java:63)

类似的问题经常被回答为"clear maven cache",但这没有关系。因为如果我 运行 它作为常规 java 应用程序(不是 WebStart),一切正常。但是,是的,我还是试过了。

JDK 目前试过的版本是jdk1.8.0_151, jdk1.8.0_161.

我的测试代码如下:

try {
  LOG.debug("@@@ jackson-core");
  Class.forName("com.fasterxml.jackson.core.Versioned");
  LOG.debug("@@@ jackson-annotations");
  Class.forName("com.fasterxml.jackson.annotation.JsonAutoDetect");
  LOG.debug("@@@ jackson-databind");
  Class.forName("com.fasterxml.jackson.databind.ObjectMapper");
} catch (ClassNotFoundException ex) {
  LOG.debug(ex);
}

到目前为止,我已经测试了这个版本的库:

该项目是用 target_jdk=8 编译的。罐子是用自签名证书签名的,但我用生产(官方)证书进行了测试,问题仍然存在。我试过 -XX:+TraceClassLoadinga-verbose:class 但没用。编辑:将其移至单独的问题:

也是最有趣的部分。如果我将这些库重新打包到我自己的 jar 中(jar-with-dependencies),即使通过 JNLP 也可以很好地加载它们。

可能是什么问题?

我明白了。这件事太棒了。事实证明,当构建为常规桌面 Java 应用程序时,jar 确实不同于构建为 JNLP 的那些。一个自定义的 maven 插件已经到位,可以重建 jar。当它将 类 从 JarInputStream 复制到 JarOutputStream 时,它使用 ZipEntry::size 来获取要传输的字节数。但事实证明,这种方法 不能保证报告正确的值 (认真的,作者?)。它适用于较旧的库,因为报告的值对它们来说是正确的,但并不总是适用于较新的库。