Java Beanプロパティへの静的に検証されたリンク

ツールを長期間、真剣に使用する場合、必然的に不満があります。最初は我慢しなければならない不便ですが、ある時点で、常に苦しむよりも一度修正する方が簡単だと気づきます。 自分で「仕上げる」ことができる優れたツール。



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静的メタモデルで静的コード生成を使用します。

このオプションにはいくつかの利点があります。



  1. ゼロのランタイムオーバーヘッド。
  2. チェーンの「ルート」の類型化をキャプチャする機能。
  3. 生成されたクラスAPIでは、不要なメソッド(プロパティに関連しない)を除外できます。


PS Querydslには同様の機能があり、同じ方法で実装されますが、Querydslインフラストラクチャに強く結び付けられています。



All Articles