应用程序中的 NoClassDefFoundError,类路径是正确的(乍一看)。为什么?

NoClassDefFoundError in app, classpath is correct (at first sight). Why?

I 运行 my app.jar as java -jar app.jar 并查看下一个错误:

Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/thrift/transport/TTransportException
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
at java.lang.Class.privateGetMethodRecursive(Class.java:3048)
at java.lang.Class.getMethod0(Class.java:3018)
at java.lang.Class.getMethod(Class.java:1784)
at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:544)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:526)
Caused by: java.lang.ClassNotFoundException: org.apache.thrift.transport.TTransportException
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 7 more

app.jar结构:

    .
    ├── lib
    │   ├── ... (some *.jar files)
    │   ├── libthrift-0.9.3.jar
    │   └── ... (some *.jar files)
    ├── META-INF
    │   ├── MANIFEST.MF
    │   └── maven
    │       └── groupId-name
    │           └── artifactId-name
    │               ├── pom.properties
    │               └── pom.xml
    └── ... *.class files of app

META-INF/MANIFEST.MF 中声明一个 classpath 为:

Class-Path: lib/libthrift-0.9.3.jar lib/...(other *.jar's from lib/ folder)

libthrift-0.9.3.jar结构:

    .
    ├── META-INF
    │   ├── LICENSE.txt
    │   ├── MANIFEST.MF
    │   └── NOTICE.txt
    └── org
        └── apache
            └── ... some packages with files
                ├── transport
                │   ├── ... some files
                │   ├── TTransportException.class
                │   └── ...
                └── ...

如您所见,class org.apache.transport.TTransportException 存在并且必须在 运行 时间内可以访问。但是不要。为什么会这样?

首先:如果您没有使用任何特殊的 tools/frameworks(例如 spring-boot),默认情况下在 java 中,您不能在 jar 中包含 jar。

第二:清单文件中的条目(如 Class-Path: lib/libthrift-0.9.3.jar 等)引用的不是 jar 中的 jar,而是文件中的 jars罐子附近的系统。也就是说 运行 你的应用 java -jar app.jar 的文件结构应该是:

./
 /libs --> all 3-d party jars here
 app.jar

如果你想把所有东西都放在一个罐子里,一种变体是使用所谓的 'uber-jar' - 在这种情况下,所有 3-d 派对 类 都从他们的罐子中提取并打包连同你自己的 类 在一个罐子里。

例如对于maven构建可以使用Shade Plugin

在打包 app.jar 时,只需将 external/3rd 派对库(如 libthrift-0.9.3.jar 放在名为 "lib" 的 folder/directory 中app.jar。让你Manifest条目保持不变。执行时,使用 java -cp 。 -jar app.jar。否则,就像 inigo 所说的那样,只需使用像 eclipse 这样的工具并将所有库打包到 jar 中。另一种选择是简单地从 thrift 等外部 jar 中提取所有 class 文件并将它们打包到您的 app.jar 中。在那种情况下,您可以 运行 随心所欲。