10

最初に、この投稿は CDI を批判することを意図したものではなく、CDI の設計の背後にある考え方と仮定を発見し、CDI を使用する Web アプリの設計に明らかな影響を与えることを明確にする必要があります。

(Java EE 6 の) CDI の最も優れた機能の 1 つは、タイプ セーフです。Jboss Seam はタイプが安全ではありませんでした。名前を使用して、注入するインスタンスを修飾します。以下のように:

   @Name("myBean")
   public class MyBean implements Bean {
     ...
   }

   @Name("yourBean")
   public class YourBean implements Bean {
     ...
   }

MyBean を注入するときに、次のことができます。

   @In
   private Bean myBean; //myBean is injected

   @In
   private Bean yourBean; //yourBean is injected  

Spring の以前のバージョン (3.0 より前) では、このタイプのインジェクションは次のように発生しました。

Bean 構成ファイルで Bean を定義するだけです。

   <bean id="myBean" class="com.example.common.MyBean">
       ...
   </bean>

   <bean id="yourBean" class="com.example.common.YourBean">
       ...
   </bean>

そして、名前付き修飾子を使用して、使用するものを決定します。

   @Autowired
   @Qualifier("myBean")
   private Bean bean;

   @Autowired
   @Qualifier("yourBean")
   private Bean bean; 

Qualifierしかし、現在の CDI では、まず、特定のタイプのオブジェクトに対してカスタム アノテーションを定義する必要があります。次に、そのアノテーションを使用してそのオブジェクトを修飾します。一日の終わりにソース コードを見ると、依存性注入のために多くのカスタム アノテーションを記述するのにかなりの時間を浪費していることがわかります。Java コミュニティは、XML ベースの構成 (詳細な XML )を後にして、注釈に移行しています。これ (カスタム アノテーションを使用した型の安全性) を冗長なアノテーションとしてではなく、CDI の優れた優れた機能として考えるように説得するものはありますか?

編集:

ポイントを押してハイライト表示

  1. サービスごとまたは dao (インターフェースごと) ごとに型安全性のためにカスタム修飾子を使用すると、複数の実装を持つ 1000 以上のサービスまたは dao クラスを持つような大規模なアプリケーションの場合、面倒になります。次に、大規模なアプリケーションの場合、タイプセーフインジェクションを使用することは可能ですか?
  2. 上記の質問の答えが「いいえ」の場合、タイプ セーフを使用するポイントは何ですか?
  3. 大規模なアプリケーションでタイプ セーフのための注釈を記述することが可能であるとしても、冗長な xml構成を回避するだけの努力をするだけの価値があるのでしょうか?
  4. Bean 名修飾子の代わりに型安全性が実際に必要になるのはいつですか?

上記の点に関する短い議論

  1. タイプ セーフ インジェクションが実際に必要になるケースはあまり多くありません。特に、インターフェイスの実装が 1 つしかない場合は@Name、修飾子として使用する必要があります。そうです、大規模なアプリケーションでは、実際に必要なときにタイプ セーフを使用することは現実的です。
  2. もちろん、タイプ セーフは CDI の優れた機能の 1 つであり、受け入れられた回答には、タイプ セーフを使用することを選択した理由の網羅的でないリストがあります。
  3. あなたは頭の良いプログラマーであり、型安全性をいつ使用すべきかを正確に知っているので、本当に必要なときに努力する価値は間違いなくあります。
  4. 受け入れられた回答のほとんどの部分は、型の安全性がいつ必要になるかを実際に語っています。この記事も理解するのに非常に役立ちます。

ありがとう、ハッピーコーディング!

4

3 に答える 3

18

CDI は冗長ですか? 修飾子は必要ですか?

  1. まず、インターフェイスの実装が 1 つしかない場合、修飾子は必要ありません。
  2. インターフェースの実装が複数ある場合は、展開後にそれらを区別する必要があるかどうか自問してください。

    • 答えが「いいえ」の場合は、代替手段の使用を検討してください。
    • 答えが「はい」の場合でも、修飾子は必要ありません。理由は次のとおりです。

      独自の例を使用するには:

      public class MyBean implements Bean {
          ...
      }
      
      public class YourBean implements Bean {
          ...
      }
      

      次に、次のようにします。

      @Inject MyBean bean;
      

      また

      @Inject YourBean bean;
      

      インスタンス変数を具象型にするのが好きではなく、インターフェイスを表示したい場合は、次のようにします。

      private Bean bean;
      
      @Inject
      public void setBean(MyBean bean) {
          this.bean = bean;
      }
      

      また

      private Bean bean;
      
      @Inject
      public void setBean(YourBean bean) {
          this.bean = bean;
      }
      

      上記のすべてのケースで、完全に修飾子がなく、完全にタイプ セーフであり、決して冗長ではありません。

  3. 次に、最後の点について詳しく説明します - 開発者は適切な実装を選択する必要がありますか、それとも問題のある選択を行うことができますか?

    • 開発者が選択する場合は、上記の 2 で説明したように行います。
    • 選択に問題がある場合は、プロデューサーを使用します。

      @Produces
      public Bean obtainTheAppropriateBean(InjectionPoint ip) {
          if (meetsConditionA(ip)) {
              return getBeanImplA();
          } else if (meetsConditionB(ip)) {
              return getBeanImplB();
          } else if (...) {
              ...
          } else {
              return getDefaultBeanImpl();
          }
      }
      

      まだ修飾子がなく、タイプ セーフであり、おそらくまだ冗長ではありません (自動化のトレード チョイス)。

    この点の優れた拡張と、InjectionPoint APIの使用方法に関するアイデアについては、この記事を参照してください。

修飾子はまったく必要ですか?

上記の例の後にこの質問が発生することがわかります。答えは「はい」です。これを使用することを選択した理由の非網羅的なリストを以下に示します。

  • 上記の例の 1 つで、修飾子の使用を避けるためにインターフェイスの特定の実装を注入することについて言及しました。コードが内部のものであり、内部の開発者がどれがどれであるかを知っている場合、それはまったく問題ありません。しかし、コードがライブラリまたはフレームワークであり、パブリック API で特定の実装を公開したくない場合はどうでしょうか? 次に、いくつかの修飾子を定義し、それらを適切に文書化します。これは詳細な XML とどう違うのですか? ライブラリ作成者としてのあなたが同じくらい冗長な作業を行っているとしても、ユーザーはそうする必要はありません。代わりに、注入ポイントの上に 1 つの単語を書き込むだけで、XML を書き込ませなくてもよかったと思うでしょう。個人的には、非常に非常に満足しています。:)
  • 上記のプロデューサーの例では、ほとんどのケースをカバーできるかもしれませんが、すべてをプロデューサー メソッドのロジックでカバーできるわけではありません。または、特定の注入ポイントでそのロジックをオーバーライドする機能が必要な場合もあります。次に、プロデューサーを保持し、修飾子を作成して、特定の実装に注釈を付けます。プロデューサー ロジックを実行したくない場合は、修飾子を使用します。
  • 複数のインターフェースと複数の実装がある状況を想像してみてください。特定の実装には共通の識別可能な特性があり、それらの特性はすべてのインターフェースに共通です。例として、Java コレクション フレームワーク、具体的には List、Set、および Map インターフェイスを見てみましょう。それぞれに複数の実装がありますが、すべてまたは一部のインターフェースに共通の特性があります。たとえば、リンクされたノード (高速反復) - LinkedList、LinkedHashSet、LinkedHashMap を考えてみてください。ソート済み (順序付け) - TreeSet、TreeMap を考えてください。ハッシュテーブルベース (高速挿入/削除/含む) - HashSet、LinkedHashSet、HashMap、LinkedHashMap を考えてください。同時; ランダムアクセス; @Linkedなど。これで、、、@Sortedおよび@Hash注釈を定義できます。次に注入します。

    @Inject @Linked @Hash private Map map;
    @Inject @Sorted private Map map;
    @Inject @Hash private Set set;
    @Inject @Hash private Set set;
    

    では、コレクション フレームワークに対してこれを行う価値はありますか? 私はそれをしませんが、私が現在取り組んでいるプロジェクトでここで説明しているものと同様のケースがあります (申し訳ありませんが、議論できません)。

  • 最後に、修飾子を使用して、 と組み合わせてプロデューサーにパラメーターを渡すことができます@Nonbinding。上記のコレクション フレームワークを続けて、次を定義します。

    @Qualifier
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Hash {
        @Nonbinding int capacity() default 16;
        @Nonbinding float loadFactor() default 0.75f;
    }
    

    このようにして、必要な容量と負荷係数をプロデューサーに渡し、次のようなハッシュ テーブルに基づいて何かを返すことができます。

    @Inject @Hash(capacity = 256, loadFactor = 0.85f) private Set set;
    @Inject @Hash private Set set;
    @Inject @Hash(capacity = 8, loadFactor = 0.65f) private Map map;
    

これがあなたの質問に答えることを願っています。私が CDI を好きな理由の 1 つです。

于 2013-03-09T16:35:32.337 に答える
4

カスタム修飾子はいつ使用する必要がありますか?

CDI カスタム修飾子は次の場合にのみ使用する必要があります。(b) コードで示されているよりも具体的なタイプのオブジェクトを注入したい。(c) 事前定義された修飾子 @Named によって提供されるよりも柔軟なタイプの選択と構成可能性が必要な場合。

修飾子は、CDI によって作成/挿入される型を明確にします。
(a) & (b) は、コードで高レベルの型を使用して、再利用および柔軟に調整できる強力な一般アルゴリズムを提供する場合に発生します。たとえば、List、Map、または Queue に対するアルゴリズムなど、特定のクラスではなくインターフェイスに対してアルゴリズム全体をコーディングすることが可能であり、推奨されることがよくあります。しかし、多くの場合、これらのアルゴリズムは、SortedList、TreeMap、または PriorityQueue などのインターフェイスの特定の実装にコミットしている場合にのみうまく機能します。CDI がオブジェクト ファクトリとして機能し、オブジェクトのライフサイクル管理を実行し、注入と依存関係管理のために正しいオブジェクトを初期化する場合、完全な詳細を知る必要があります。

カスタム修飾子は、CDI によって作成/注入される型を明確にする方法においてスマートで構成可能です。@Named は、より鈍い手段です。(c) について、「型の選択」について言及するとき、開発者が複数の修飾子を組み合わせて、正確なクラスに名前を付けることなく、最も適切な型を「論理的に選択」できることを意味します。@Named は事実上、正確なクラスの指定を必要とします。複数の修飾子により、CDI がそれを解決できるようになります。構成可能性とは、beans.xml を変更することによって、展開時に CDI の動作を変更できることを意味します (コード/注釈を変更する必要はありません)。つまり、CDI が行っていることを論理的に調整することができ、コードや注釈に触れることなく、展開時までそれを行うことができます。

カスタム修飾子はいつ宣言する必要がありますか?

CDI カスタム修飾子は、注入できる個々の型ごとに作成する必要はありません。これが最も重要です。

注入ポイントと型宣言の両方で複数の修飾子を使用できます。CDI は、注入するタイプを決定するときに、Bean タイプと SET OF 修飾子を一致させます。宣言する修飾子の数を減らすには、型を説明するいくつかの属性/懸念事項に型を分解することをお勧めします。次に、タイプごとに1つの修飾子の代わりに、タイプ「属性」ごとに1つの修飾子を持つことができます-そのような属性がHash / Tree / Enumなどの複数値であっても(次の段落を参照)。

次に、修飾子はパラメーターを賢く使用して、必要な宣言の数をさらに減らす必要があります。10 個の修飾子を作成するのではなく、10 個の値を取ることができる文字列またはできれば列挙型のパラメーターを持つ 1 つの修飾子を作成できます。

これらの 2 つのアイデア (qualifiers=type attribute PLUS use qualifier parameters) をコレクション ライブラリの例に組み合わせます。ハッシュ/ツリー/配列? リンクされている/リンクされていない? 変数を指定すると

@Inject @Sorted @Organisation("hash") @Navigation("linked") Map myMap;

次に、これらの各側面が満たされることが保証されているため、強力な型選択があります。特定のパッケージで非常に特定のクラスが必要であることを知らずに、ソートされたリンク付きハッシュ マップがあります。

まだ対処されていない点:

パラメータ化された Bean 名で注釈を使用すると、実際にどのようにタイプセーフになりますか?

次のものと完全に一致する必要があるため、強い型付けが保証されます。

  • 豆の種類
  • 修飾子のセット
  • パラメータ値のセット

注入された型は、これらの指定されたものに違反することはできません。タイプセーフを単一の文字列 (packagename.classname) のマッチングと考えるのではなく、開発者によって完全に制御される複数の文字列/値のマッチングと考えてください。これらの同じ文字列/値は、宣言側と注入側の両方で使用され、安全なマッチングを提供します。また、最下位レベルの具体的なクラスに一致する必要があると考える代わりに、継承階層の上位レベルのクラスに一致し、型で @Default 修飾子を賢く使用できることを覚えておいてください (Java EE 6 の「設定よりもスマートなデフォルト」を思い出してください)。 ?) は、優先する具体的なタイプに移動します。

サービスごとまたは dao (インターフェースごと) ごとに型安全性のためにカスタム修飾子を使用すると、複数の実装を持つ 1000 以上のサービスまたは dao クラスを持つような大規模なアプリケーションの場合、面倒になります。次に、大規模なアプリケーションの場合、タイプセーフインジェクションを使用することは可能ですか?

  • 1000以上のサービスまたはdaoクラス? 本当??!これは、多くの場合、大きな設計上の問題をすぐに示します。これが、税務署や NASA などの複雑なビジネス向けの超メガサイズのギネス記録申請である場合を除きます。それでも、クラス数がはるかに少ない、より小さな Java EE モジュール / SOA サービスに分解するのが普通です。これがあなたの持っているものなら、あなたはあなたのデザインを単純化することを検討したいと思うかもしれません - あなたは切り貼りされた修飾子の定義よりもはるかに大きな問題を抱えているかもしれません...

  • 修飾子は、型があいまいである場合にのみ必要です。つまり、同じ型階層内に多くの祖先/子孫クラスがあります。あいまいなサービスまたは DAO クラスは比較的少ないことがよくあります。

  • 上記のように、実装クラスごとに 1 つの修飾子パターンを使用しないでください。代わりに、タイプ アスペクトごとの修飾子を使用するようにリファクタリングし、修飾子ごとに記述的なパラメーターの実装パターンも使用します。

  • 大規模なアプリケーションでタイプ セーフ インジェクションを使用することは可能です。

上記の質問の答えが「いいえ」の場合、タイプ セーフを使用するポイントは何ですか?

  • 答えは「はい」です
  • 提案されたシナリオ (1000 以上のサービス/DAO クラス) は非常にまれです

大規模なアプリケーションでタイプ セーフのための注釈を記述することが可能であるとしても、冗長な xml 構成を回避するだけの努力をするだけの価値があるのでしょうか?

CDI の目的は、「冗長な xml 構成を回避する」ことだけではありません。CDI には次の目標があります。

  • オブジェクトのライフサイクル管理によるスコープ (新しい Web 会話スコープを含む) のサポート
  • 詳細な構成なしで、開発時または展開時に依存関係を選択する機能を含む、タイプセーフな依存関係注入メカニズム
  • Java EE モジュール性と Java EE コンポーネント アーキテクチャのサポート — Java EE コンポーネント間の依存関係を解決する際に、Java EE アプリケーションのモジュール構造が考慮されます。
  • 統一式言語 (EL) との統合により、コンテキスト オブジェクトを JSF または JSP ページ内で直接使用できます。
  • 注入されたオブジェクトを装飾する機能
  • タイプセーフ インターセプター バインディングを介してインターセプターをオブジェクトに関連付ける機能
  • イベント通知モデル
  • Java サーブレット仕様で定義された 3 つの標準 Web コンテキストに加えて、Web 会話コンテキスト
  • 移植可能な拡張機能をコンテナーときれいに統合できるようにする SPI

これは、アノテーションに変換された基本的な XML 構成ファイルよりもはるかに異なり、より有用です。以前の基本的な Java EE 5 リソース注入アノテーションでさえ、XML 構成がアノテーションに変換されたものとは異なり、より有用でした。

于 2013-03-11T05:31:34.900 に答える
2

@Qualifier毎回カスタムを作成する必要はありません。いくつかのパラメーターを使用して 1 つだけ作成するだけで十分です。CDI は@CustomQualifier("myBean")、 と@CustomQualifier("yourBean")を異なる修飾子として扱います。

さらに、そのような修飾子はすでに CDI パッケージに含まれています - @Named

ただし、たとえば静的修飾子が非常に役立つ場合があります ( @Alternativeを参照)。

@Alternative
@Qualifier
@Documented
@Retention(value=RUNTIME)
public @interface Mock

また、一部の Bean をテストのみに使用するようにマークすることもできます(テスト クラスパスでセクション@Mockを有効にすることを忘れないでください。alternativebeans.xml

于 2013-03-05T21:23:19.523 に答える