128

この例外は、Java 9 でアプリケーションを実行しているときに、さまざまなシナリオで発生します。特定のライブラリとフレームワーク (Spring、Hibernate、JAXB) は、特に発生しやすい傾向があります。Javassist の例を次に示します。

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 を開きません」

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

4

17 に答える 17

172

この例外は、Java 9 で導入されたJava Platform Module System、特にその強力なカプセル化の実装が原因で発生します。特定の条件下でのみアクセスを許可します。最も顕著なものは次のとおりです。

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

例外の原因となったコードが使用しようとしたリフレクションにも同じ制限が当てはまります。より正確には、例外は への呼び出しによって引き起こされます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. 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 で実行される場合、これは特に困難になる可能性があります。

追加するフラグが多すぎる場合は、代わりにカプセル化キル スイッチの使用を検討してください。 --permit-illegal-accessクラスパス上のすべてのコードが、名前付きモジュール全体を反映できるようになります。このフラグ は Java 9 でのみ機能することに注意してください。

2. アプリケーション コードの考察

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

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

これらのアプローチの詳細な説明と比較については、この投稿を参照してください。

于 2016-12-21T14:34:40.087 に答える
7

これは解決が非常に難しい問題です。他の人が指摘したように、 --add-opens オプションは回避策にすぎません。根底にある問題を解決する緊急性は、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 ベースのクラス変更を実行する必要がなくなり、元の投稿で示したスタック トレースがなくなります。

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

于 2017-04-07T03:42:24.813 に答える
4

openJDK 1.8 と STS 4 を使用しているときに、2021 年に同じ問題が発生しました。

Window => Preferences => Java => Installed JREs.

add オプションを使用して新しい JRE (後述) を追加し、openJdk フォルダーを参照して [OK] を選択します。新しい JDK をデフォルトにします。適用をクリックして閉じます。

/usr/lib/jvm/java-1.8.0-openjdk-amd64/jre

それは魅力のように働きました:)

于 2021-07-12T15:27:14.230 に答える
1

SornarQube の実行中に同様の問題に直面していたので、次のようにしてソナー レポートをアップロードする回避策を得ました。

既存のパッケージ: Java 16.0.2、MacOS (BigSur)

  • インストールされた sonar_scanner
  • 次のコマンドを実行しました: export SONAR_SCANNER_OPTS="--illegal-access=permit"

同じ問題に直面している場合、これが誰かに役立つことを願っています。:-)

于 2021-09-13T04:48:58.707 に答える