Guiceによる柔軟な構成

Javaには、 Apache Commonsなどのさまざまな構成ライブラリがありますが、通常は、非常に単純なパターンに従います。多くの構成ファイルを解析し、このデータに基づいてプロパティまたはマップを構築します。
Double double = config.getDouble("number"); Integer integer = config.getInteger("number"); 

しかし、このアプローチはいくつかの理由で私には向いていません。

しばらく前に、私はGuiceのドキュメントを読んで、これがもっとうまくできるというアイデアを思いついた段落につまずいた。 関連する文章を次に示します。
Guiceは、属性を持つ注釈のバインドをサポートしています。
それらが必要なまれなケースでは:
1) @interfaceアノテーションを作成します。
2)注釈インターフェイスを実装するクラスを作成します。 Annotation Javadocで定義されているequals()およびhashCode()のガイドラインに従ってください。 クラスのインスタンスをannotatedWith()に渡します。

そして、この手法を使用すると、必要なものを正確に取得できるという考えが浮かび上がりました。annotatedWithトリックとは関係のない計画がありましたが、より「スマート」な構成フレームワークを作成するためです。 この方法の関連性は少し後に明らかになりますが、現時点では主な目標の概要を説明します。

目標

実装したい:

どの外部インターフェイスが使用されるかは気にしません。設定の受信方法はフレームワークとは関係ありません。設定はXML、JSONの形式、ネットワーク経由またはデータベースから取得できます。 設定からMapフレームワークの入力時に、そこから取得します。

終了するまでに、次のようなことができます。
 # - properties- host=foo.com port=1234 

コードでは次の値を使用します。
 public class A { @Inject @Prop(Property.HOST) private String host; @Inject @Prop(Property.PORT) private Integer port; // ... } 

実装


プロップ注釈の定義は簡単です:
 @Retention(RUNTIME) @Target({ ElementType.FIELD, ElementType.PARAMETER }) @BindingAnnotation public @interface Prop { Property value(); } 

プロパティは、設定に必要なすべての情報を含む列挙です。 上記の例の場合:
 public enum Property { HOST("host", "The host name", new TypeLiteral<String>() {}, "foo.com"), PORT("port", "The port", new TypeLiteral<Integer>() {}, 1234); } 

列挙には、文字列文字列「プロパティ名」、説明、デフォルト値、およびそのタイプが含まれます。 この型はTypeLiteralであることに注意してください。そうしないと消去されないジェネリック型を持つプロパティを記述することができます。このトリックにより、キャッシュや他のジェネリックコレクションを導入できます。 当然のことながら、必要に応じて追加のパラメーターを追加できます(「非推奨」など)。

次のステップでは、入力で解析したすべてのプロパティをマップにバインドします-Map "allProps"を呼び出して、Guiceがそれらを注入する方法を理解できるようにします。
これを行うために、これらすべてのプロパティを調べてプロバイダーにバインドします。 型付きフィールドを使用するため、Guice APIのKey.get()の使用に注意してください。これにより、プロパティを対応する注釈にマップできます。
  for (Property prop : Property.values()) { Object value = PropertyConverters.getValue(prop.getType(), prop, allProps.asMap()); binder.bind(Key.get(prop.getType(), new PropImpl(prop))) .toProvider(new PropertyProvider(prop, value)); } 

この例には、まだ説明していない3つのクラスがあります。 最初のPropertyConvertersは、単にプロパティを文字列として読み取り、それをJava型に変換します。 2つ目はPropertyProviderです。これはGuiceの最も単純なプロバイダーです。
 public class PropertyProvider<T> implements Provider<T> { private final T value; private final Property property; public PropertyProvider(Property property, T value) { this.property = property; this.value = value; } @Override public T get() { return value; } } 

PropImplはもう少し複雑で、Guiceのドキュメントでそのちょっとしたことに出くわすまで、そのようなフレームワークを開発していたときはいつも私を止めました。 このクラスの必要性を理解するには、Key.get()の仕組みを学ぶ必要があります。 Guiceはこれを使用して、型を一意のキーにマップし、必要な値を注入するために使用します。 ここで重要な部分は、メソッドがClassおよびTypeLiteralで機能するだけでなく、対応する注釈にも結び付けられることです。 この注釈は@Named場合がありますが、私はそれが大ファンではありません。文字列で動作するため、タイプミスや独自の注釈の対象になるため、これは私たちにもっと適しています。 ただし、Javaの注釈は特別なものであり、そのインスタンスを取得することはできません。

ここで、記事の冒頭で説明したトリックが役立ちます。実際、Javaでは、通常のクラスでアノテーションを実装できます。 実装は非常にシンプルであることが判明しましたが、これは一般的に可能であるという認識に困難がありました。

これですべてのパーツが配置されました。ここにはどのような魔法があるのか​​を分析する必要があります。
  @Inject @Prop(Property.HOST) private String host; 

Guiceがこの注入ポイントに到達すると、彼はzashashnikで文字列のいくつかのバインダーを見つけます。これらは本質的にペア(String, Prop)であるKeyにバインドされているためです この場合、彼はString.Property.HOSTのペアを検索し、プロパティファイルの値でインスタンス化されたプロバイダーを見つけます。

要約する


以前、このコードは私から1か所で収集されていましたが、考えた後、他の人が使用できるようにミニフレームワークをライブラリに変えることにしました。 唯一欠けている要素は、より一般的なPropアノテーションを定義する機能でした。 上記の例では、この注釈には、アプリケーションに固有のタイプPropertyの値が含まれています。
 @Retention(RUNTIME) @Target({ ElementType.FIELD, ElementType.PARAMETER }) @BindingAnnotation public @interface Prop { Property value(); } 

より汎用的にするために、Enumを返さなければなりませんでした。
 @Retention(RUNTIME) @Target({ ElementType.FIELD, ElementType.PARAMETER }) @BindingAnnotation public @interface Prop { Enum value(); } 

残念ながら、このJavaはJLSセクション8.9に従ってこれを許可しません。Enumとそのジェネリックバージョンは列挙型ではありません。これはJ. Blochによっても確認されています。
したがって、ライブラリに変換することはできませんが、プロジェクトで使用することに興味がある場合は、ソースコードをコピーし、構成に従ってProp#valueから始めて、必要に応じて変更します。

小さな概念の証明がここにあります 、あなたがそれを役に立つと思うことを願っています。

Source: https://habr.com/ru/post/J187758/


All Articles