Рубрики
Без рубрики

Java 9 Вопросы миграции и резолюции

Узнайте, как решить общие проблемы, связанные с системой модуля Java при переходе существующего приложения на Java 9.

Автор оригинала: Mohan Sundararaju.

1. Обзор

Платформа Java раньше была монолитной архитектурой, объединяющей все пакеты как единое целое.

В Java 9 это было упорядочено с введением Система модулей платформы Java (JPMS) , или Модули для краткости. Связанные пакеты были сгруппированы под модули, и модули заменили пакеты, чтобы стать основной единицей повторного .

В этом быстром учебнике мы пройдемся по некоторым вопросам, связанным с модулями, с которыми мы можем столкнуться при миграция существующего приложения на Java 9 .

2. Простой пример

Давайте рассмотрим простое приложение Java 8, которое содержит четыре метода, которые действительны под Java 8, но сложны в будущих версиях. Мы будем использовать эти методы, чтобы понять влияние миграции на Java 9.

Первый метод получает имя поставщика JCE ссылки в приложении:

private static void getCrytpographyProviderName() {
    LOGGER.info("1. JCE Provider Name: {}\n", new SunJCE().getName());
}

Второй метод перечисляет имена классов в стеке трассировки :

private static void getCallStackClassNames() {
    StringBuffer sbStack = new StringBuffer();
    int i = 0;
    Class caller = Reflection.getCallerClass(i++);
    do {
        sbStack.append(i + ".").append(caller.getName())
            .append("\n");
        caller = Reflection.getCallerClass(i++);
    } while (caller != null);
    LOGGER.info("2. Call Stack:\n{}", sbStack);
}

Третий метод преобразует объект Java в XML :

private static void getXmlFromObject(Book book) throws JAXBException {
    Marshaller marshallerObj = JAXBContext.newInstance(Book.class).createMarshaller();
    marshallerObj.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

    StringWriter sw = new StringWriter();
    marshallerObj.marshal(book, sw);
    LOGGER.info("3. Xml for Book object:\n{}", sw);
}

И окончательный метод кодирует строку на базу 64 с помощью sun.misc.BASE64Энкодер , из внутренних библиотек JDK :

private static void getBase64EncodedString(String inputString) {
    String encodedString = new BASE64Encoder().encode(inputString.getBytes());
    LOGGER.info("4. Base Encoded String: {}", encodedString);
}

Давайте вызовем все методы из основного метода:

public static void main(String[] args) throws Exception {
    getCrytpographyProviderName();
    getCallStackClassNames();
    getXmlFromObject(new Book(100, "Java Modules Architecture"));
    getBase64EncodedString("Java");
}

Когда мы запускаем это приложение в Java 8, мы получаем следующее:

> java -jar target\pre-jpms.jar
[INFO] 1. JCE Provider Name: SunJCE

[INFO] 2. Call Stack:
1.sun.reflect.Reflection
2.com.baeldung.prejpms.App
3.com.baeldung.prejpms.App

[INFO] 3. Xml for Book object:


    Java Modules Architecture


[INFO] 4. Base Encoded String: SmF2YQ==

Как правило, Java-версии гарантируют обратную совместимость, но JPMS изменения некоторых из этого.

3. Казнь на Java 9

Теперь давайте за запустите это приложение в Java 9:

>java -jar target\pre-jpms.jar
[INFO] 1. JCE Provider Name: SunJCE

[INFO] 2. Call Stack:
1.sun.reflect.Reflection
2.com.baeldung.prejpms.App
3.com.baeldung.prejpms.App

[ERROR] java.lang.NoClassDefFoundError: javax/xml/bind/JAXBContext
[ERROR] java.lang.NoClassDefFoundError: sun/misc/BASE64Encoder

Мы видим, что первые два метода работают нормально, в то время как последние два не удалось. Давайте выясним причину сбоя, проанализировав зависимости наших приложений . Мы будем использовать jdeps инструмент, поставляемый с Java 9:

>jdeps target\pre-jpms.jar
   com.baeldung.prejpms            -> com.sun.crypto.provider               JDK internal API (java.base)
   com.baeldung.prejpms            -> java.io                               java.base
   com.baeldung.prejpms            -> java.lang                             java.base
   com.baeldung.prejpms            -> javax.xml.bind                        java.xml.bind
   com.baeldung.prejpms            -> javax.xml.bind.annotation             java.xml.bind
   com.baeldung.prejpms            -> org.slf4j                             not found
   com.baeldung.prejpms            -> sun.misc                              JDK internal API (JDK removed internal API)
   com.baeldung.prejpms            -> sun.reflect                           JDK internal API (jdk.unsupported)

Выход из команды дает:

  • список всех пакетов внутри нашего приложения в первой колонке
  • список всех зависимостей в нашем приложении во второй колонке
  • расположение зависимостей на платформе Java 9 — это может быть имя модуля или внутренний API JDK, или нет для сторонних библиотек

4. Обеженные модули

Давайте попробуем решить первую ошибку java.lang.NoClassDefFoundError: javax/xml/bind/JAXBContext.

В соответствии со списком зависимостей, мы знаем, что Java.xml.bind пакет принадлежит Java.xml.bind модуль который, как представляется, является действительным модулем. Итак, давайте посмотрим на официальная документация для этого модуля .

В официальной документации говорится, что Java.xml.bind модуль уничает для удаления в будущем выпуске. Следовательно, этот модуль не загружается по умолчанию в classpath.

Тем не менее, Java предоставляет метод загрузки модулей по требованию с помощью – дополнительные модули выбор. Итак, давайте идти вперед и попробовать:

>java --add-modules java.xml.bind -jar target\pre-jpms.jar
...
INFO 3. Xml for Book object:


    Java Modules Architecture

...

Мы видим, что исполнение было успешным. Это решение является быстрым и простым, но не лучшим решением.

Как долгосрочное решение, мы должны добавить зависимость в качестве стороннёй библиотеки с использованием Maven:


    javax.xml.bind
    jaxb-api
    2.3.1

5. Внутренние API JDK

Давайте теперь рассмотрим вторую ошибку java.lang.NoClassDefFoundError: sun/misc/BASE64Encoder.

Из списка зависимостей мы видим, что sun.misc пакет является Внутренняя JDK API.

Внутренние API, как следует из названия, являются частным кодом, используемым внутренне в JDK.

В нашем примере внутренний API, по-видимому, был удален из системы JDK . Давайте проверим, что альтернативный API для этого с помощью -jdk-внутренние выбор:

>jdeps --jdk-internals target\pre-jpms.jar
...
JDK Internal API                         Suggested Replacement
----------------                         ---------------------
com.sun.crypto.provider.SunJCE           Use java.security.Security.getProvider(provider-name) @since 1.3
sun.misc.BASE64Encoder                   Use java.util.Base64 @since 1.8
sun.reflect.Reflection                   Use java.lang.StackWalker @since 9

Мы видим, что Java 9 рекомендует использовать java.util.Base64 вместо sun.misc.Base64Encoder. Следовательно, изменение кода является обязательным для запуска нашего приложения на Java 9.

Обратите внимание, что в нашем приложении есть два других внутренних API, для которых java-платформа предложила замену, но мы не получили никаких ошибок для них:

  • Некоторые внутренние API, такие как sun.reflect.Отражение были сочтены критически важными для платформы и поэтому были добавлены к JDK-специфической jdk.unsupported модуль. Этот модуль доступен по умолчанию на classpath в Java 9.
  • Внутренние API, такие как com.sun.crypto.provider.SunJCE предоставляются только на определенных реализациях Java. До тех пор, пока код, использующий их, работает на той же реализации, он не будет бросать какие-либо ошибки.

Во всех случаях в этом примере мы используем внутренние API, что не является рекомендуемой практикой . Таким образом, долгосрочное решение заключается в том, чтобы заменить их подходящими общественными API, предоставляемыми платформой.

6. Заключение

В этой статье мы увидели, как модульная система введена в Java 9 может вызвать проблемы с миграцией для некоторых старых приложений с помощью увяди или внутренних .

Мы также видели, как применять краткосрочные и долгосрочные исправления для этих ошибок.

Как всегда, примеры из этой статьи доступны более на GitHub .