Javaは優れたツールなので、このような不便さと修正方法について説明します。
だから不便
Javaには、Beanプロパティを参照する構文はありません。 例で説明する方が簡単です。 タイプ
Customer
customer
プロパティを持つ
Account
があり、
name
プロパティを持つ
Account
があるとします。 言い換えれば:
public class Account { public Customer getCustomer() { ... } } public class Customer { public String getName() { ... } }
また、インターフェイス上にタブレットを作成してBeanのリストを表示できる
TableBuilder
があります。表示するプロパティ(ネストされている可能性があります)を指定するだけで、すべてのルーチン作業が既に実行されます。
name customer
「
Account
」
name customer
を表示したいと言うにはどうすればいいですか? 通常、文字列リテラルを使用します。
TableBuilder<Account> tableBuilder = TableBuilder.of(Account.class); ... tableBuilder.addColumn("customer.name");
この方法の欠点は、これが単なる文字列ではないことをコンパイラが認識せず、その正確性を検証できないことです。つまり、すべての入力ミスは実行時にのみエラーになります。 同じ理由で、開発環境は
Customer
どのプロパティを
Customer
いるかを知ることができません。 そして、すべてをチェックしてデバッグしても、最初のリファクタリングは努力を破壊します。
それほど明らかではない別の不便な点があります
addColumn(String)
入力しても、このメソッドが文字列だけでなく、一連のプロパティを期待していることはまったくわかりません。 コンパイラーにすべてをチェックさせ、環境にプロンプトを表示させ、リファクタリングは中断しませんでした。 それほど多くはありませんが、これに必要なすべての情報は既にそこにあります。
ソリューションに向けて
タスクは解決できないように見えます。Javaでは、クラスのメンバーにアクセスせずに参照する構文構造は実際にはありません。 ただし、これにより、モックフレームワークがエレガントかつ厳密に「メソッドが呼び出されるタイミング」を表現できなくなりました。たとえば、Mockitoでは次のことが可能です。
Account account = mock(Account.class); when(account.getCustomer()).thenReturn(...);
mock()
メソッドは、
Account
ように見えるプロキシを作成して返しますが、まったく異なる方法で動作します。呼び出されたメソッドに関する情報をThreadLocal変数に保存し、それを取得して
when()
を使用
when()
ます。 同じトリックを使用して問題を解決できます。
Account account = root(Account.class); tableBuilder.addColumn( $( account.gertCustomer().getName() ) );
root()
は、呼び出されたメソッドをThreadLocal変数に格納するプロキシを返し、次のプロキシを返します。これにより、プロパティのチェーンになる呼び出しのチェーンを作成できます。
$()
は文字列ではなく、オブジェクト指向の方法でプロパティのチェーンを表す
BeanPath
型のオブジェクトを
BeanPath
ます。 このチェーンの個々の要素をナビゲートする(各要素について、名前とタイプが保存される)か、すでにわかっている文字列に変換することができます。
$( account.gertCustomer().getName() ).toDotDelimitedString() => "customer.name"
$()
、その主な機能に加えて、チェーンのタイプ(チェーンの最後のプロパティ)を
TableBuilder
ます。
TableBuilder
、
TableBuilder
別のタイプのドロップを追加できます。
public <T> ColumnBuilder<T> addColumn(BeanPath<T> path) {...}
ここでは、CUSTISでこのような小さなフレームワークを作成し、それを自分で使用して、GitHubに投稿しました 。
使用面
動的プロキシを介した実装には、次の制限があります。 第一に、チェーンの「ルート」プロパティと非終了プロパティは、最終クラス(
enum
、string、
jlInteger
などを含む)にすることはできません。 フレームワークはそれらをプロキシできず、
null
を返し
null
。
$( account.getCustomer().getName().length() ) // => NPX!
それにもかかわらず、どのタイプのプロパティでもチェーンを閉じることができます:最終クラスとプリミティブ(チェーンの途中では無意味で不可能です)。
第二に、ゲッターはフレームワークから見えるようにする必要があります。つまり、ゲッターは
private
または
package-local
であってはなりません。 ただし、一般にデフォルトのコンストラクターとパブリックコンストラクターは存在しない場合があります。プロキシーは、コンストラクターをバイパスしてインスタンス化されます。 これを正当な方法で行うことはできないため、HotSpot JVMの組み込み
sun.misc.Unsafe.allocateObject()
組み込みが使用され、フレームワークが他のJVMに耐えられなくなります。 Rutsは再利用可能であり、再利用する必要がありますが、状態は含まれていません。
Account account = root(Account.class); tableBuilder.addColumn( $( account.getCustomer().getName() ) ); tableBuilder.addColumn( $( account.getNumber() ) ); tableBuilder.addColumn( $( account.getOpenDate() ) );
root()
および
$()
メソッドは、単なる静的メソッドであるため、エイリアスを作成できます。
public class BeanPathMagicAlias { public static <T> BeanPath<T> path(T callChain) { return BeanPathMagic.$(callChain); } }
これを使用して、好みに合わせてメソッドの名前を変更したり、便利なショートカットを作成したりできます。 特に、そのようなものは
beanpath
ですでに宣言されてい
beanpath
:
public static String $$(Object callChain) { return $(callChain).toDotDelimitedString(); }
文字列リテラルを期待するコードでbeanpathを使用するのに役立ちます。 BeanPath
BeanPath
手動で構築することもでき
BeanPath
-その動作は、構築中に設定される状態によって完全に決定されます。 だから:
BeanPath<String> bp1 = $( account.getCustomer().getName()); BeanPath<String> bp2 = BeanPath.root(Account.class) .append("customer", Customer.class) .append("name", String.class); bp1.equals(bp2) // => true
これは、上記の制限を回避するのに役立ちます(チェーンに最終クラスがあるか、パブリックゲッターがない場合)。 同時に、チェーンの正確性は開発者の良心に残ります。
今後の計画
beanpathはソースコードでのみ使用できるようになりました。 したがって、まず、Maven Centralで完全なアセンブリと展開を確立したいと思います。 次に、
sun.misc.Unsafe
をObjnesisに置き換えて、Beanpathを移植可能にします。 まあ、かなりの見通し-別のエッジから問題の解決策にアプローチする:JPA静的メタモデルで静的コード生成を使用します。
このオプションにはいくつかの利点があります。
- ゼロのランタイムオーバーヘッド。
- チェーンの「ルート」の類型化をキャプチャする機能。
- 生成されたクラスAPIでは、不要なメソッド(プロパティに関連しない)を除外できます。
PS Querydslには同様の機能があり、同じ方法で実装されますが、Querydslインフラストラクチャに強く結び付けられています。