web-dev-qa-db-ja.com

Java 9?

この例外は、アプリケーションをJava 9。

Java.lang.reflect.InaccessibleObjectException: Unable to make protected final Java.lang.Class Java.lang.ClassLoader.defineClass(Java.lang.String,byte[],int,int,Java.security.ProtectionDomain) throws Java.lang.ClassFormatError accessible: module Java.base does not "opens Java.lang" to unnamed module @1941a8ff
    at Java.base/jdk.internal.reflect.Reflection.throwInaccessibleObjectException(Reflection.Java:427)
    at Java.base/Java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.Java:201)
    at Java.base/Java.lang.reflect.Method.checkCanSetAccessible(Method.Java:192)
    at Java.base/Java.lang.reflect.Method.setAccessible(Method.Java:186)
    at javassist.util.proxy.SecurityActions.setAccessible(SecurityActions.Java:102)
    at javassist.util.proxy.FactoryHelper.toClass2(FactoryHelper.Java:180)
    at javassist.util.proxy.FactoryHelper.toClass(FactoryHelper.Java:163)
    at javassist.util.proxy.ProxyFactory.createClass3(ProxyFactory.Java:501)
    at javassist.util.proxy.ProxyFactory.createClass2(ProxyFactory.Java:486)
    at javassist.util.proxy.ProxyFactory.createClass1(ProxyFactory.Java:422)
    at javassist.util.proxy.ProxyFactory.createClass(ProxyFactory.Java:394)

メッセージは言う:

保護された最終的なJava.lang.ClassをJava.lang.ClassLoader.defineClass(Java.lang.String、byte []、int、int、Java.security.ProtectionDomain)にできないと、Java.lang.ClassFormatErrorがスローされます:モジュールJava.base名前のないモジュール@ 1941a8ffに対して「Java.langを開きません」

例外を回避し、プログラムを正常に実行するにはどうすればよいですか?

49
Nicolai

例外は、Java 9、特に強力なカプセル化の実装)で導入された Java Platform Module System によって引き起こされます。許可されるのは access =特定の条件下で、最も顕著なものは次のとおりです。

  • タイプはパブリックでなければなりません
  • 所有パッケージをエクスポートする必要があります

同じ制限がリフレクションにも当てはまります。リフレクションでは、例外を引き起こすコードが使用しようとしました。より正確には、例外は setAccessible の呼び出しによって引き起こされます。これは上記のスタックトレースで確認できます。javassist.util.proxy.SecurityActionsの対応する行は次のようになります。

static void setAccessible(final AccessibleObject ao,
                          final boolean accessible) {
    if (System.getSecurityManager() == null)
        ao.setAccessible(accessible); // <~ Dragons
    else {
        AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                ao.setAccessible(accessible);  // <~ moar Dragons
                return null;
            }
        });
    }
}

プログラムが正常に実行されるようにするには、setAccessibleが呼び出された要素へのアクセスを許可するようにモジュールシステムを納得させる必要があります。そのために必要なすべての情報は例外メッセージに含まれていますが、これを実現するための 多くのメカニズム があります。どちらが最良かは、それを引き起こした正確なシナリオによって異なります。

{member}をアクセス可能にできません:モジュール{A}は{B}に対して「{package}」を開きません

最も顕著なシナリオは次の2つです。

  1. ライブラリまたはフレームワークは、リフレクションを使用してJDKモジュールを呼び出します。このシナリオでは:

    • {A}は、Javaモジュール(接頭辞Java.またはjdk.)です)
    • {member}および{package}は、Java APIの一部です
    • {B}は、ライブラリ、フレームワーク、またはアプリケーションモジュールです。しばしばunnamed module @...
  2. Spring、Hibernate、JAXBなどのリフレクションベースのライブラリ/フレームワークは、アプリケーションコードを反映して、Bean、エンティティなどにアクセスします。このシナリオでは:

    • {A}はアプリケーションモジュールです
    • {member}および{package}はアプリケーションコードの一部です
    • {B}はフレームワークモジュールまたはunnamed module @...のいずれかです

一部のライブラリ(たとえば、JAXB)は両方のアカウントで失敗する可能性があるため、現在のシナリオをよく見てください。問題の1つはケース1です。

1. JDKへのリフレクティブコール

JDKモジュールはアプリケーション開発者にとって不変であるため、プロパティを変更できません。これにより、考えられる解決策は1つだけになります: コマンドラインフラグ 。それらを使用して、特定のパッケージをリフレクション用に開くことができます。

したがって、上記のような場合(短縮)...

Java.lang.ClassLoader.defineClassをアクセス可能にすることができません:モジュールJava.baseは、名前のないモジュール@ 1941a8ffに対して「Java.langを開きません」

...正しい修正方法は、次のようにJVMを起動することです。

# --add-opens has the following syntax: {A}/{package}={B}
Java --add-opens Java.base/Java.lang=ALL-UNNAMED

リフレクションコードが名前付きモジュールにある場合、ALL-UNNAMEDはその名前で置き換えることができます。

実際にリフレクションコードを実行するJVMにこのフラグを適用する方法を見つけるのが難しい場合があることに注意してください。問題のコードがプロジェクトのビルドプロセスの一部であり、ビルドツールが生成したJVMで実行される場合、これは特に困難です。

追加するフラグが多すぎる場合は、代わりに encapsulation kill switch--permit-illegal-accessを使用することを検討してください。クラスパス上のすべてのコードがすべての名前付きモジュールに反映できるようにします。このフラグは、Java 9

2.アプリケーションコードのリフレクション

このシナリオでは、リフレクションを使用して侵入するモジュールを編集できる可能性があります。 (そうでない場合は、事実上ケース1です。)つまり、コマンドラインフラグは不要であり、代わりにモジュール{A}の記述子を使用して内部を開くことができます。さまざまな選択肢があります。

  • exports {package}でパッケージをエクスポートします。これにより、コンパイル時および実行時にすべてのコードで使用できるようになります。
  • exports {package} to {B}を使用して、アクセスモジュールにパッケージをエクスポートします。これにより、コンパイル時および実行時に使用可能になりますが、{B}のみになります。
  • opens {package}でパッケージを開く
  • opens {package} to {B}を使用してアクセスモジュールにパッケージを開きます。これにより、実行時に(リフレクションの有無にかかわらず)使用可能になりますが、{B}のみが使用できます。
  • open module {A} { ... }でモジュール全体を開く

これらのアプローチのより詳細な議論と比較については this post をご覧ください。

75
Nicolai

これは解決が非常に難しい問題です。他の人が述べたように、--add-opensオプションは回避策にすぎません。根底にある問題を解決する緊急性は、1回だけ拡大しますJava 9が公開されます。

Java 9でHibernateベースのアプリケーションをテストしているときに、この正確なJavassistエラーを受け取った後、このページで気づきました。そして、Java 7複数のプラットフォームで8および9を使用する場合、最良の解決策を見つけるのに苦労しました(Java 7および8 JVMは、認識できない "--add-opens"引数を表示するとすぐに中断しますコマンドライン;これはバッチファイル、スクリプト、またはショートカットの静的な変更では解決できません。)

主流のライブラリ(SpringやHibernateなど)の作成者から公式のガイダンスを受け取るのはいいことですが、Java 9の現在のリリースがリリースされるまで100日かかります見つけにくい。

多くの実験とテストの後、私はHibernateの解決策を見つけてホッとしました。

  1. Hibernate 5.0.0以降を使用します(以前のバージョンは機能しません)。
  2. リクエストビルド時バイトコード拡張(Gradle、Maven、またはAntプラグインを使用)。

これにより、Hibernateが実行時にJavassistベースのクラス変更を実行する必要がなくなり、元の投稿に示されているスタックトレースが排除されます。

[〜#〜] however [〜#〜]、後でアプリケーションを徹底的にテストする必要があります。ビルド時にHibernateによって適用されるバイトコードの変更は、実行時に適用されるものとは異なるように見え、アプリケーションの動作がわずかに異なります。ビルド時のバイトコード拡張機能を有効にすると、長年成功してきたアプリの単体テストが突然失敗しました。 (新しいLazyInitializationExceptionsおよびその他の問題を追跡しなければなりませんでした。)動作は、Hibernateのバージョンごとに異なるようです。注意して進めてください。

3
David T

--add-opensを使用することは、回避策と見なす必要があります。正しいことは、Spring、Hibernate、およびその他のライブラリが違法なアクセスを行って問題を修正することです。

2
Alan Bateman

Hibernate 5で警告が表示されました。

Illegal reflective access by javassist.util.proxy.SecurityActions

最新のjavassistライブラリを依存関係gradleに追加しました。

compile group: 'org.javassist', name: 'javassist', version: '3.22.0-GA'

これで問題が解決しました。

1
Karol Golec