ClassNotFoundException при запуске jar-файла, скомпилированного с использованием внешних библиотек (Kotlin с JVM)

Вопрос или проблема

Вот мои шаги для воспроизведения проблемы:

com/example/Library.kt

package com.example

class Library {
    fun getValue(): Int {
        return 123
    }
}

MyCode.kt

import com.example.Library

fun main() {
    println("Hello World")
    println(Library().getValue())
}

Я могу скомпилировать оба файла с помощью команды ниже, без каких-либо ошибок:

kotlinc com/example/Library.kt -d Library.jar
kotlinc -cp Library.jar MyCode.kt -d MyCode.jar

Структура файлов после компиляции:

$ tree
.
├── Library.jar
├── MyCode.jar
├── MyCode.kt
└── com
    └── example
        └── Library.kt

Однако я не могу выполнить MyCode.jar

$ java -cp Library.jar -jar MyCode.jar
Hello world
Exception in thread "main" java.lang.NoClassDefFoundError: com/example/Library
        at MyCodeKt.main(MyCode.kt:5)
        at MyCodeKt.main(MyCode.kt)
Caused by: java.lang.ClassNotFoundException: com.example.Library
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
        at java.base/jdk/internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
        ... 2 more

Также не работает использование kotlin вместо java:

$ kotlin -cp Library.jar MyCode.jar
Hello world
Exception in thread "main" java.lang.NoClassDefFoundError: com/example/Library
        at MyCodeKt.main(MyCode.kt:5)
        at MyCodeKt.main(MyCode.kt)
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
        at java.base/java.lang.reflect.Method.invoke(Method.java:580)
        at org.jetbrains.kotlin.runner.AbstractRunner.run(runners.kt:70)
        at org.jetbrains.kotlin.runner.Main.run(Main.kt:194)
        at org.jetbrains.kotlin.runner.Main.main(Main.kt:204)
Caused by: java.lang.ClassNotFoundException: com.example.Library
        at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:445)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:593)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
        ... 7 more

Может кто-нибудь сказать мне, в чем проблема?
(Maven / Gradle не вариант, я пытаюсь создать быстрый инструмент для компиляции кодов пользователей)

Причина ошибки указана в команде kotlin -help:

  app.jar                    Выполняет указанный JAR файл так, как это делает 'java -jar'

Поэтому нам нужно посмотреть, что делает java -jar. Для этого смотрите документацию Java:

-jar jarfile
Выполняет программу, инкапсулированную в JAR файле. Аргумент jarfile – это имя JAR файла с манифестом, который содержит строку в формате Main-Class:classname, определяющую класс с общедоступным статическим методом void main(String[] args), который служит точкой входа для вашего приложения. Когда вы используете -jar, указанный JAR файл является источником всех пользовательских классов, и другие настройки пути к классам игнорируются. Если вы используете JAR файлы, то смотрите jar.

Соответствующее предложение выделено жирным шрифтом. Таким образом, kotlin MyCode.jar игнорирует аргумент -cp. Решение состоит в том, чтобы либо запустить его так, как вы указали в своем ответе, либо поместить файл Library.class внутри вашего MyCode.jar.

Благодарю @liaifat85 за то, что дал мне большой намек, который решал мою проблему.

Вместо использования опции -jar вы можете попробовать опцию -cp для обоих JAR файлов и явно указать основной класс.

java -cp Library.jar:MyCode.jar MyCodeKt

Ответ или решение

При выполнении программного кода на Kotlin, скомпилированного с использованием внешних библиотек и упакованного в файлы JAR, вы можете столкнуться с исключением ClassNotFoundException. Эта проблема часто возникает, когда неправильно настроен класс-путь или не учитывается специфика выполнения JAR-файлов в Java.

Описание проблемы

Как видно из вашего примера, вы создали два файла: Library.kt и MyCode.kt. Файл Library.kt содержит класс Library, который вы используете в вашем основном коде MyCode.kt. Вы успешно скомпилировали оба файла в JAR-файлы с помощью следующих команд:

kotlinc com/example/Library.kt -d Library.jar
kotlinc -cp Library.jar MyCode.kt -d MyCode.jar

Однако при попытке выполнения MyCode.jar с использованием следующей команды возникает исключение ClassNotFoundException:

java -cp Library.jar -jar MyCode.jar

Причина ошибки

Ошибка связана с тем, как работает ключ -jar в команде Java. Когда вы используете -jar, процесс выполнения игнорирует все дополнительные параметры -cp (classpath) и использует только JAR-файл, указанный после -jar. Это означает, что JAR-файл MyCode.jar воспринимается как отдельная единица, и Java ищет все классы именно в этом файле, без учета других JAR-файлов, которые вы указали в параметре -cp.

Как указано в официальной документации Java, использование -jar требует, чтобы в манифесте JAR-файла содержалась строка Main-Class, указывающая на класс с методом public static void main(String[] args). В данном случае, поскольку Library.class не находится в MyCode.jar, JVM не может загрузить класс com.example.Library, что и приводит к ClassNotFoundException.

Решение проблемы

Для решения проблемы у вас есть несколько вариантов:

  1. Только использование Classpath:
    Запустите программу без ключа -jar, но явно укажите класс-путь для обоих JAR-файлов и основной класс:

    java -cp Library.jar:MyCode.jar MyCodeKt

    Эта команда позволяет JVM видеть оба JAR-файла, и она успешно найдет класс com.example.Library, вызывая main метод вашего приложения.

  2. Создание манифеста:
    Если вы все же хотите использовать ключ -jar, вам нужно создать манифест для MyCode.jar, в который включить указание класса с main методом и обеспечить наличие в этом JAR файле всех необходимых классов. Для этого необходимо:

    • Объединить классы из обеих JAR-файлов в один JAR-файл, используя команду jar:

      jar -cvfm MyCode.jar MANIFEST.MF -C output-directory/ .
    • В вашем MANIFEST.MF укажите:

      Main-Class: MyCodeKt

    Однако, для небольших и краткосрочных проектов, как ваш, первый вариант с использованием ключа -cp наиболее удобен и прост.

Заключение

Проблема ClassNotFoundException при работе с JAR-файлами в Kotlin устраняется пониманием принципа работы с -jar и -cp. Убедитесь, что все необходимые классы доступны для JVM, и используйте правильные параметры запуска. Если вы будете следовать предложенным шагам, выполнение ваших Kotlin-программ должно проходить без ошибок.

Помните, что корректная организация вашего кода и понимание работы классов и пакетов являются важными аспектами программирования на Kotlin.

Оцените материал
Добавить комментарий

Капча загружается...