Javaには、
Apache Commonsなどのさまざまな構成ライブラリがありますが、通常は、非常に単純なパターンに従います。多くの構成ファイルを解析し、このデータに基づいてプロパティまたはマップを構築します。
Double double = config.getDouble("number"); Integer integer = config.getInteger("number");
しかし、このアプローチはいくつかの理由で私には向いていません。
- まず、非常に冗長であることが判明しました。
- パラメーターが1つだけ必要な場合でも、構成オブジェクト全体を転送する必要があります。
- キーを間違えて間違ったデータを読み取るのは非常に簡単です。
しばらく前に、私はGuiceのドキュメントを読んで、これがもっとうまくできるというアイデアを思いついた段落につまずいた。 関連する文章を次に示します。
Guiceは、属性を持つ注釈のバインドをサポートしています。
それらが必要なまれなケースでは:
1) @interface
アノテーションを作成します。
2)注釈インターフェイスを実装するクラスを作成します。 Annotation Javadocで定義されているequals()およびhashCode()のガイドラインに従ってください。 クラスのインスタンスをannotatedWith()に渡します。
そして、この手法を使用すると、必要なものを正確に取得できるという考えが浮かび上がりました。annotatedWithトリックとは関係のない計画がありましたが、より「スマート」な構成フレームワークを作成するためです。 この方法の関連性は少し後に明らかになりますが、現時点では主な目標の概要を説明します。
目標
実装したい:
- 個々の構成値をコードベースに挿入する機能は、可能な限りタイプセーフです。
@Named
およびその他の文字列識別子はありません。 - タイプ、デフォルト値、ドキュメント、およびさらなる改善の可能性とともに、アプリケーションで利用可能なすべてのプロパティのリスト(たとえば、設定が必要/オプション、未使用の設定を自動的に検出、推奨しない設定をマークするなど)。
どの外部インターフェイスが使用されるかは気にしません。設定の受信方法はフレームワークとは関係ありません。設定はXML、JSONの形式、ネットワーク経由またはデータベースから取得できます。 設定からMapフレームワークの入力時に、そこから取得します。
終了するまでに、次のようなことができます。
コードでは次の値を使用します。
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
から始めて、必要に応じて変更します。
小さな概念の証明が
ここにあり
ます 、あなたがそれを役に立つと思うことを願っています。