Javaの「private staticfinal」フィールドの値をクラスの外から変更する方法はありますか?
-
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...
}
}