Javaでモンキーパッチを適用することは可能ですか?
-
22-08-2019 - |
質問
可能であればというだけで、このアプローチの利点については議論したくありません。私は答えは「ノー」だと信じています。でも、もしかしたら誰かが私を驚かせるかも知れません!
コア ウィジェット クラスがあると想像してください。それには方法がある calculateHeight()
, 、高さを返します。高さが大きすぎる - その結果、(たとえば) ボタンが大きすぎます。DefaultWidget を拡張して独自の NiceWidget を作成し、独自の NiceWidget を実装できます。 calculateHeight()
より良いサイズを返すために。
ライブラリ クラス WindowDisplayFactory は、かなり複雑なメソッドで DefaultWidget をインスタンス化します。NiceWidget を使用したいと考えています。ファクトリ クラスのメソッドは次のようになります。
public IWidget createView(Component parent) {
DefaultWidget widget = new DefaultWidget(CONSTS.BLUE, CONSTS.SIZE_STUPIDLY);
// bunch of ifs ...
SomeOtherWidget bla = new SomeOtherWidget(widget);
SomeResultWidget result = new SomeResultWidget(parent);
SomeListener listener = new SomeListener(parent, widget, flags);
// more widget creation and voodoo here
return result;
}
それが契約だ。結果として、他のオブジェクトの階層の奥深くに DefaultWidget が含まれます。質問は、独自の NiceWidget を使用するためにこのファクトリ メソッドを取得する方法です。または少なくとも自分のものを入手してください calculateHeight()
そこで。理想的には、DefaultWidget にモンキー パッチを適用して、calculateHeight が正しく行われるようにしたいと考えています...
public class MyWindowDisplayFactory {
public IWidget createView(Component parent) {
DefaultWidget.class.setMethod("calculateHeight", myCalculateHeight);
return super.createView(parent);
}
}
これは Python や Ruby などでできることです。名前は私が考え出しました setMethod()
けれど。私に残された他の選択肢は次のとおりです。
- のコードをコピーして貼り付けます。
createView()
ファクトリクラスから継承した独自のクラスにメソッドを追加します - 大きすぎるウィジェットを使用する
ファクトリ クラスは変更できません。これはコア プラットフォーム API の一部です。(最終的に)追加されたウィジェットに到達するために、返された結果をリフレクションしようとしましたが、それはいくつかのウィジェットレイヤーの下にあり、どこかで他のものを初期化するために使用され、奇妙な副作用が発生します。
何か案は?これまでのところ、私の解決策はコピー&ペーストのジョブですが、これはプラットフォームの新しいバージョンにアップグレードするときに親ファクトリクラスの変更を追跡する必要がある対処法であり、他のオプションを知りたいと思っています。
解決
おそらくあなたは、その機能にトラップ呼び出しにアスペクト指向プログラミングを使用することができ、代わりに独自のバージョンを返す?
春には、いくつかのAOPの機能を提供していますが、同様にそれを行う他のライブラリがあります。
他のヒント
一つの醜いソリューションは、通常の実装よりもクラスパスに以前(同じFQCN付き)DefaultWidgetの独自の実装を置くことであろう。それはひどいハックですが、私は考えることができるすべての他のアプローチがさらに悪くなる。
ちょうど私の概念のアイデア、
calculateHeight方法の態様を注入するために、バイトコードエンジニアリング方法と、AOPを使用することが可能です。
次に、あなたはThreadLocalのか、他の変数によって、あなたのパッチを有効にすることができます。
CGLIB の猿のパッチに似たいくつかのことを行うことができますJavaライブラリがある - それはでバイトコードを操作することができますランタイムは、特定の動作を変更します。私はそれはあなたが必要な正確に何ができるかはわからないが、それは一見の価値がある...
Unsafe.putObjectとクラスのファインダーを使用して、Javaでモンキーパッチすることは全く可能です。ここにブログの記事を書いた:
https://tersesystems.com/blog/2014/ 3月2日/ monkeypatching-javaの-クラス/ の
これを行うためのオブジェクト指向の方法はcalculateHeight、のようなものを除いて、実際のウィジェットに対するすべての呼び出しを、委任、iWidgetのを実装するラッパーを作成することです
class MyWidget implements IWidget {
private IWidget delegate;
public MyWidget(IWidget d) {
this.delegate = d;
}
public int calculateHeight() {
// my implementation of calculate height
}
// for all other methods: {
public Object foo(Object bar) {
return delegate.foo(bar);
}
}
これが機能するためには、、あなたはおそらくWidgetFactoryのための同様のラッパーを作成することを意味する、交換したいウィジェットのすべての作品を傍受する必要があります。そして、あなたはWidgetFactory、使用する設定できるようにする必要があります。
また、バックDefaultWidget ...
へのiWidgetをキャストしようとしていないクライアントに依存します私が思いつく提案のみ:
ライブラリ API を調べて、デフォルトとサイズ設定をオーバーライドする方法があるかどうかを確認してください。(少なくとも私にとっては) setMinimum、setMinimum、setdefault、setDefaultOnMonday などのサイジングは、スイングでは混乱を招く可能性があります。。方法がある可能性があります。ライブラリの設計者に連絡できれば、不快なハッキングの必要性を軽減する答えが見つかるかもしれません。
おそらく、いくつかのデフォルトのサイズ設定パラメータをオーバーライドするだけでファクトリを拡張しますか?工場にもよりますが、可能な場合もあります。
同じ名前のクラスを作成することが唯一の他の選択肢かもしれません。他の人が指摘しているように、それは醜く、API ライブラリを更新したり、別の環境にデプロイしたりするときに忘れて壊れてしまい、なぜそのクラスを作成したのか忘れてしまう可能性があります。クラスパスはそのように設定されています。
あなたはPowerMock / Mockitoのようなツールを使用して試すことができます。あなたがテストでモックことができれば、あなたも生産にモックすることができます。
しかし、これらのツールは...本当にその方法を使用するように設計されていないので、あなたは、環境を自分で用意する必要がありますし、あなたがテストで行うようにJUnitのランナーを使用することはできません。
まあ、私は提案を投稿するには努力を続ける、と私は、彼らが仕事をしたり、あなたがすでにあなたがそれらを試してみました言及したことがないであろうことがわかります。
私は考えることができる最善の解決策は、その後、サブクラスのCREATEVIEW()メソッド、最初の呼び出しのsuper.createView()で、その後、WindowDisplayFactoryサブクラスオブジェクトは完全にウィジェットを捨てるのインスタンスでそれを置き換えるために戻っ変更することです何をしたいんサブクラス。しかし、ウィジェットがものを初期化するために使用されているので、あなたがそれらのすべてを変更するに行かなければならないと思います。
は、それから私は、CREATEVIEWから返されたオブジェクトにリフレクションを使用して()とそのように物事を解決しようとすると思うが、それほどのものはウィジェットで初期化されたため、再び、それは毛深いです。私はそれをコピー&ペーストの上にそれを正当化するのに十分に簡単だった場合、私は、しかし、そのアプローチを使用しようと思います。
私はこれを見て、プラス私は、他のアイデアを思い付くことができるかどうかを確認することが考えられます。 Javaのリフレクションは確かにいいですが、それは、私はPerlやPythonなどの言語で利用できる見てきた動的なイントロスペクションを打つことができない。