Java 9の移行の問題と解決策

1. 概要

Javaプラットフォームは、モノリシックアーキテクチャを使用しており、すべてのパッケージを単一のユニットとしてバンドルしていました。
Java 9では、これはlink:/java-9-modularity[Java Platform Module System(JPMS)]、または_Modules_の導入により合理化されました。 関連するパッケージはモジュールの下にグループ化され、*モジュールがパッケージを置き換えて再利用の基本単位*になりました。
このクイックチュートリアルでは、*既存のアプリケーションをJava 9 *に移行するときに直面する可能性のあるモジュールに関連するいくつかの問題について説明します。

2. 簡単な例

4つのメソッドを含む単純なJava 8アプリケーションを見てみましょう。これらのメソッドはJava 8で有効ですが、将来のバージョンでは困難です。 これらのメソッドを使用して、Java 9への移行の影響を理解します。
最初のメソッドは、アプリケーション内で参照されているJCEプロバイダーの名前を*フェッチします:
private static void getCrytpographyProviderName() {
    LOGGER.info("1. JCE Provider Name: {}\n", new SunJCE().getName());
}
2番目のメソッドは、スタックトレース内のクラスの名前をリストします*:
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);
}
3番目のメソッドは、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);
}
最後のメソッドは、JDK内部ライブラリの_sun.misc.BASE64Encoder_を使用して、文字列をBase 64にエンコードします*:
private static void getBase64EncodedString(String inputString) {
    String encodedString = new BASE64Encoder().encode(inputString.getBytes());
    LOGGER.info("4. Base Encoded String: {}", encodedString);
}
mainメソッドからすべてのメソッドを呼び出しましょう。
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:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<book id="100">
    <title>Java Modules Architecture</title>
</book>

[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
最初の2つのメソッドは正常に実行され、最後の2つのメソッドは失敗したことがわかります。 *アプリケーションの依存関係を分析して、失敗の原因を調査しましょう*。 Java 9に付属の_jdeps_ツールを使用します。
>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)
コマンドからの出力は次のとおりです。
  • 最初の列のアプリケーション内のすべてのパッケージのリスト

  • 2番目のアプリケーション内のすべての依存関係のリスト
    カラム

  • Java 9プラットフォームでの依存関係の場所-*これは
    モジュール名、または内部JDK API、*またはサードパーティライブラリの場合はなし

4. 廃止されたモジュール

最初のエラー_java.lang.NoClassDefFoundError:javax / xml / bind / JAXBContext._を解決してみましょう。
依存関係リストによると、_java.xml.bind_パッケージは__java.xml.bind module belongs__に属していることがわかります。これは有効なモジュールのようです。 それでは、https://docs.oracle.com/javase/9​​/docs/api/java.xml.bind-summary.html [このモジュールの公式ドキュメント]を見てみましょう。
公式ドキュメントでは、_java.xml.bind_モジュールは将来のリリースでの削除が推奨されないと述べています。 したがって、このモジュールはデフォルトではクラスパスにロードされません。
ただし、* Javaは、_add-modules_オプションを使用して、オンデマンドでモジュールをロードするメソッドを提供します。 それでは、先に進んで試してみましょう。
>java --add-modules java.xml.bind -jar target\pre-jpms.jar
...
INFO 3. Xml for Book object:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<book id="100">
    <title>Java Modules Architecture</title>
</book>
...
実行が成功したことがわかります。 このソリューションは迅速かつ簡単ですが、最良のソリューションではありません。
長期的なソリューションとして、https://search.maven.org/search?q = g:javax.xml.bind%20AND%20a:jaxb-apiを追加する必要があります
<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>

5. JDK内部API

2番目のエラー_java.lang.NoClassDefFoundError:sun / misc / BASE64Encoder._を見てみましょう。
依存関係リストから、_sun.misc_パッケージが_JDK internal_ APIであることがわかります。
内部APIは、その名前が示すように、JDKで内部的に使用されるプライベートコードです。
この例では、**内部APIはJDKから削除されているように見えます** __.___âj“ jdk-internals_オプションを使用して、この代替APIが何であるかを確認しましょう。
>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では、_sun.misc.Base64Encoder._の代わりに_java.util.Base64_を使用することを推奨していることがわかります。したがって、アプリケーションをJava 9で実行するにはコードの変更が必須です。 _ _
Javaプラットフォームが代替を提案しているアプリケーションで使用している他の2つの内部APIがあることに注意してください。ただし、これらのエラーは発生しませんでした。
  • _sun.reflect.Reflection_などの一部の内部API *が考慮されました
    プラットフォームにとって重要なため、JDK固有の_jdk.unsupported_モジュールに追加されました。 このモジュールは、Java 9のクラスパスでデフォルトで使用可能です。

  • * _com.sun.crypto.provider.SunJCE_などの内部APIはのみ提供されます
    特定のJava実装で。*それらを使用するコードが同じ実装で実行されている限り、エラーはスローされません。

    すべてのケースで*この例では、内部APIを使用していますが、これは推奨されるプラクティスではありません*。 したがって、長期的な解決策は、プラットフォームで提供される適切なパブリックAPIに置き換えることです。

6. 結論

この記事では、* Java 9で導入されたモジュールシステムが、非推奨APIまたは内部API *を使用する一部の古いアプリケーションで移行問題を引き起こす可能性があることを確認しました。
また、これらのエラーに短期および長期の修正を適用する方法も確認しました。
いつものように、この記事の例はhttps://github.com/eugenp/tutorials/tree/master/core-java-modules/pre-jpms[GitHubで]から入手できます。