Javaの「private staticfinal」フィールドの値をクラスの外から変更する方法はありますか?

StackOverflow https://stackoverflow.com/questions/767008

  •  12-09-2019
  •  | 
  •  

質問

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

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() は私のコードではなく外部ライブラリによって呼び出されるため、サブクラス化だけでは問題は解決されません。

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

役に立ちましたか?

解決

それは可能です。私は、Webアプリケーションのクラスアンロードを防止し、いたずらthreadlocalsをモンキーパッチするために、これを使用しました。あなただけの、あなたはフィールドを変更することができ、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の周りにもあるので、それは必ずしも動作しない場合があります前に、いくつかのコードが実行された場合....

他のヒント

任意のAOPフレームワークは、あなたのニーズに合うでしょう。

それはあなたがあなたのニーズに合ったどんなクラス戻すことができるgetInstanceメソッドの実行時のオーバーライドを定義することができるようになります。

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

あなたは次のことを試すことができます。注意:これは、安全なすべてのスレッドではないが、これはコンパイル時に知られている定数のプリミティブのために動作しません(これらは、コンパイラによってインライン化されているよう)

Field field = SomeClass.class.getDeclareField("INSTANCE");
field.setAccessible(true); // what security. ;)
field.set(null, newValue);

あなたはそれがあなたのための選択肢であるかどうかわからない... JNIでそれを変更することができる必要があります。

編集:それは良いアイデアも可能ですが、ありません。

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

  

アクセス制御ルールに違反10.9

     

JNIは、クラス、フィールドを強制することはありません   そして、メソッドのアクセス制御制限   それは、Javaで表現することができます   プログラミング言語レベル   など民間やなどの修飾子の使用   最後の。ネイティブの書き込みが可能です   コードは、アクセスしたりのフィールドを変更するには   でもでそうするものの、オブジェクト   Javaプログラミング言語レベルだろう   IllegalAccessExceptionがにつながります。   JNIの許容性を意識しました   設計上の意思決定、ネイティブのことを考えます   コードは、任意のメモリにアクセスして変更することができます   とにかく、ヒープ内の場所ます。

     迂回

ネイティブコード   ソース言語レベルでのアクセスチェック   上の望ましくない影響を与えることがあります   プログラムの実行。たとえば、   矛盾があれば作成することができます   ネイティブメソッドは、最後のフィールドを変更します   ジャストインタイム(JIT)コンパイラの後   フィールドへのアクセスをインライン化しています。   同様に、ネイティブメソッドはいけません   以下のような不変オブジェクトを変更   のインスタンスのフィールド   java.lang.Stringでまたはjava.lang.Integerで。   そうすることの破損につながる可能性   Javaプラットフォームでの不変量   実装ます。

本当に必要な場合は(ただし、私たちの問題では、CaptainAwesomePants のソリューションを使用することをお勧めします)、以下を見てください。 Jモックイット. 。これは単体テストで使用することを目的としていますが、任意のメソッドを再定義できる場合があります。これは、実行時にバイトコードを変更することによって行われます。

私は、これは実際にプライベート静的最終フィールドの変更についてのあなたの述べられた質問への答えではないことを認めることで、この答えを序文ます。あなたがdoSomethingの()をオーバーライドすることができるようにしかし、上記の具体例のコードでは、私が実際にそれを作ることができます。何ができることのgetInstance()は、パブリックメソッドとサブクラスであるという事実を利用することです。

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

   public SomeClass getInstance() {
        return INSTANCE;
   }

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

今だけMySomeClass.getInstance()の代わりに、SomeClass.getInstance()を呼び出して、あなたが行ってもいいです。あなたがたgetInstanceを呼び出す1()とないあなたが作業している変更不可能なもののいくつかの他の部分ならもちろん、これはのみ動作します。

mockito のとは非常に簡単です。

import static org.mockito.Mockito.*;

public class SomeClass {

    private static final SomeClass INSTANCE = new SomeClass();

    public static SomeClass getInstance() {
        return INSTANCE;
    }

    public Object doSomething() {
        return "done!";
    }

    public static void main(String[] args) {
        SomeClass someClass = mock(SomeClass.getInstance().getClass());
        when(someClass.doSomething()).thenReturn("something changed!");
        System.out.println(someClass.doSomething());
    }
}

このコードを印刷「に変更何か!」;簡単にあなたのシングルトンインスタンスを置き換えることができます。私の0.02 $セントます。

使用可能な外部ハックは(少なくとも私は知りません)が存在しない場合は、

私はクラス自体をハッキングしているだろう。あなたが望むセキュリティチェックを追加することで、コードを変更します。このようにその外部ライブラリとして、あなたもない多くのアップデートがとにかく起こり、定期的に更新を服用されることはありません。それが起こるたびに、それは大きな課題がとにかくないよう、私は喜んでそれを再実行することができます。

ここでは、あなたの問題は良い古い依存性注入(コントロールの別名反転)です。あなたの目標は、それをmonkeypatchingのではなく、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たりmonkeypatched 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...
  }
}
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top