Java9移行の問題と解決策
1. 概要
以前は、Javaプラットフォームはモノリシックアーキテクチャであり、すべてのパッケージを1つのユニットとしてバンドルしていました。
Java 9では、これは Javaプラットフォームモジュールシステム(JPMS)、または略してModulesの導入により合理化されました。 関連するパッケージはモジュールの下にグループ化され、モジュールがパッケージに取って代わり、再利用の基本単位になりました。
このクイックチュートリアルでは、既存のアプリケーションをJava9に移行するときに直面する可能性のあるモジュールに関連するいくつかの問題について説明します。
2. 簡単な例
4つのメソッドを含む単純なJava8アプリケーションを見てみましょう。これらのメソッドは、Java 8で有効ですが、将来のバージョンでは困難です。 これらの方法を使用して、Java9への移行の影響を理解します。
最初のメソッドは、アプリケーション内で参照されている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を使用して文字列をBase64にエンコードします。
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");
}
このアプリケーションをJava8で実行すると、次のようになります。
> 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. Java9での実行
それでは、このアプリケーションをJava9で実行してみましょう。
>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つの方法は失敗したことがわかります。 アプリケーションの依存関係を分析して、失敗の原因を調査しましょう。 Java9に付属の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.xml.bindパッケージはjava.xml.bindモジュールに属していることがわかります。これは有効なモジュールのようです。 それでは、このモジュールの公式ドキュメントを見てみましょう。
公式ドキュメントによると、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>
...
実行が成功したことがわかります。 このソリューションは迅速かつ簡単ですが、最善のソリューションではありません。
長期的な解決策として、Mavenを使用して依存関係をサードパーティのライブラリとして追加する必要があります。
<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パッケージがJDKinternalAPIであることがわかります。
内部APIは、その名前が示すように、JDKの内部で使用されるプライベートコードです。
この例では、内部APIがJDKから削除されているようです。–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モジュールに追加されました。 このモジュールは、Java9のクラスパスでデフォルトで使用できます。
- com.sun.crypto.provider.SunJCEなどの内部APIは、特定のJava実装でのみ提供されます。それらを使用するコードが同じ実装で実行されている限り、エラーはスローされません。
この例のすべての場合では、内部APIを使用していますが、これは推奨される方法ではありません。 したがって、長期的な解決策は、プラットフォームによって提供される適切なパブリックAPIに置き換えることです。
6. 結論
この記事では、 Java 9で導入されたモジュールシステムが、非推奨または内部APIを使用する一部の古いアプリケーションで移行の問題を引き起こす可能性があることを確認しました。
また、これらのエラーに短期的および長期的な修正を適用する方法も確認しました。
いつものように、この記事の例はGitHubでから入手できます。