11

私はこれが通常かなり愚かであることを知っていますが、質問を読む前に私を撃たないでください。私はこれを行う必要がある正当な理由があることを約束します:)

リフレクションを使用してJavaの通常のプライベートフィールドを変更することは可能ですが、Javaがfinalフィールドに対して同じことを行おうとすると、セキュリティ例外がスローされます。

これは厳密に強制されていると思いますが、誰かがこれを行うためのハックを見つけた場合に備えて、とにかく尋ねると思いました。

SomeClassクラス「 」を持つ外部ライブラリがあるとしましょう。

public class SomeClass 
{
  private static final SomeClass INSTANCE = new SomeClass()

  public static SomeClass getInstance(){ 
      return INSTANCE; 
  }

  public Object doSomething(){
    // Do some stuff here 
  }
} 

私は基本的に、自分のバージョンのを実行できるように、SomeClassをモンキーパッチしますdoSomething()。(私の知る限り)Javaで実際にそれを行う方法はないので、ここでの唯一の解決策は、の値を変更INSTANCEして、変更されたメソッドでクラスのバージョンを返すようにすることです。

基本的には、セキュリティチェックで呼び出しをラップしてから、元のメソッドを呼び出したいだけです。

外部ライブラリは常にgetInstance()このクラスのインスタンスを取得するために使用します(つまり、シングルトンです)。

編集:明確にするために、getInstance()私のコードではなく外部ライブラリによって呼び出されるので、サブクラス化するだけでは問題は解決しません。

それができない場合、私が考えることができる他の唯一の解決策は、クラス全体をコピーして貼り付け、メソッドを変更することです。ライブラリの変更に合わせてフォークを最新の状態に保つ必要があるため、これは理想的ではありません。誰かがもう少し保守しやすいものを持っているなら、私は提案を受け入れます。

4

9 に答える 9

10

可能です。これを使用して、webapps でのクラスのアンロードを妨げていたいたずらなスレッドローカルにモンキーパッチを適用しました。リフレクションを使用してfinal修飾子を削除するだけで、フィールドを変更できます。

このような何かがトリックを行います:

private void killThreadLocal(String klazzName, String fieldName) {
    Field field = Class.forName(klazzName).getDeclaredField(fieldName);
    field.setAccessible(true);  
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    int modifiers = modifiersField.getInt(field);
    modifiers &= ~Modifier.FINAL;
    modifiersField.setInt(field, modifiers);
    field.set(null, null);
}

の周りにもいくつかのキャッシュがあるField#setため、一部のコードが以前に実行された場合、必ずしも機能するとは限りません....

于 2009-04-20T08:49:33.597 に答える
5

どの AOP フレームワークもニーズに適合します

getInstance メソッドのランタイム オーバーライドを定義して、必要に応じて任意のクラスを返すことができます。

Jmockit は内部で ASM フレームワークを使用して同じことを行います。

于 2009-04-20T06:36:23.227 に答える
1

本当に必要な場合は (ただし、この問題については CaptainAwesomePants のソリューションを使用することをお勧めします)、 JMockIt を参照してください。これは単体テストで使用することを意図していますが、任意のメソッドを再定義できる場合。これは、実行時にバイトコードを変更することによって行われます。

于 2009-04-20T05:47:17.637 に答える
1

以下を試すことができます。注: これはまったくスレッドセーフではなく、コンパイル時に既知の定数プリミティブに対しては機能しません (コンパイラによってインライン化されるため)。

Field field = SomeClass.class.getDeclareField("INSTANCE");
field.setAccessible(true); // what security. ;)
field.set(null, newValue);
于 2009-04-20T06:57:31.243 に答える
1

JNIで変更できるはずです...それがオプションかどうかはわかりません。

編集:可能ですが、良い考えではありません。

http://java.sun.com/docs/books/jni/html/pitfalls.html

10.9 アクセス制御規則の違反

JNI は、private や final などの修飾子を使用して Java プログラミング言語レベルで表現できるクラス、フィールド、およびメソッドのアクセス制御制限を強制しません。Java プログラミング言語レベルでネイティブ コードを記述してオブジェクトのフィールドにアクセスまたは変更すると、IllegalAccessException が発生する可能性があります。JNI の寛大さは、ネイティブ コードがヒープ内の任意のメモリ位置にアクセスして変更できることを考えると、意識的な設計上の決定でした。

ソース言語レベルのアクセス チェックをバイパスするネイティブ コードは、プログラムの実行に望ましくない影響を与える可能性があります。たとえば、ジャスト イン タイム (JIT) コンパイラがフィールドへのアクセスをインライン化した後にネイティブ メソッドが final フィールドを変更すると、不整合が生じる可能性があります。同様に、ネイティブ メソッドは、java.lang.String または java.lang.Integer のインスタンス内のフィールドなどの不変オブジェクトを変更しないでください。これを行うと、Java プラットフォームの実装で不変条件が壊れる可能性があります。

于 2009-04-20T06:29:25.257 に答える
0

私は、これがプライベート静的最終フィールドの変更についてのあなたの述べられた質問に対する実際の答えではないことを認めることによって、この答えの前置きをします。ただし、上記の特定のサンプルコードでは、doSomething()をオーバーライドできるように実際に作成できます。getInstance()がパブリックメソッドおよびサブクラスであるという事実を利用することができます。

public class MySomeClass extends SomeClass
{
   private static final INSTANCE = new MySomeClass();

   public SomeClass getInstance() {
        return INSTANCE;
   }

   public Object doSomething() {
      //Override behavior here!
   }
}

これで、SomeClass.getInstance()の代わりにMySomeClass.getInstance()を呼び出すだけで、準備は完了です。もちろん、これは、getInstance()を呼び出している場合にのみ機能し、操作している変更不可能なものの他の部分では機能しません。

于 2009-04-20T05:41:47.260 に答える
-1

ここで、問題は古き良き依存性注入 (別名制御の反転) です。あなたの目標は、モンキーパッチを適用するのではSomeClassなく、実装を注入することです。はい、このアプローチには既存の設計にいくつかの変更が必要ですが、正当な理由 (お気に入りの設計原則をここに挙げてください) - 特に、同じオブジェクトが他のオブジェクトの作成と使用の両方を担当するべきではありません。

あなたが使用している方法は、次のようになっていると思いますSomeClass

public class OtherClass {
  public void doEverything() {
    SomeClass sc = SomeClass.geInstance();
    Object o = sc.doSomething();

    // some more stuff here...
  }
}

代わりに、最初に同じインターフェースまたは拡張を実装するクラスを作成してSomeClassから、そのインスタンスをに渡してdoEverything()、クラスが の実装に依存しないようにする必要がありSomeClassます。この場合、呼び出すコードdoEverythingは正しい実装を渡す責任がありSomeClassますMySomeClass

public class MySomeClass() extends SomeClass {
  public Object doSomething() {
    // your monkeypatched implementation goes here
  }
}

public class OtherClass {
  public void doEveryting(SomeClass sc) {
    Object o = sc.doSomething();

    // some more stuff here...
  }
}
于 2009-04-20T16:32:01.503 に答える
-1

利用可能な外部ハックがない場合 (少なくとも私は認識していません)、クラス自体をハッキングしていたでしょう。必要なセキュリティ チェックを追加して、コードを変更します。そのような外部ライブラリであるため、定期的に更新を行うことはありません。とにかく多くの更新は行われません。とにかく大した作業ではないので、いつでも喜んでやり直すことができます。

于 2009-04-20T06:45:23.643 に答える