10

私は Java EE/JSF の初心者で、CDI 修飾子 (クラスの実装を変更する可能性) について読みました。これは素晴らしいことですが、1 つ質問があります。私が理解している限り、修飾子を使用してクラスの実装を変更できますが、この実装を使用するすべての場所で変更する必要があります。一か所でそれを行うための最良の解決策は何ですか? Java EEに関する私の小さな知識で、これを理解しました。

簡単な電卓アプリケーションを作成しているとしましょう。いくつかのクラスを作成する必要があります。

  1. Calculator(電卓の基本実装)
  2. ScientificCalculator(電卓の科学的実装)
  3. MiniCalculator(最小限の可能性で)
  4. MockCalculator(単体テスト用)
  5. 修飾子@Calculator(電卓の実際の実装を示します。実装ごとに修飾子を作成する必要がありますか?)

これが質問です。私は電卓の実装を 4 つ持っていますが、そのうちの 1 つをいくつかの場所で使用したいのですが、一度に 1 つだけ使用したいと考えています (最初のプロジェクト段階では、 を使用しMiniCalculator、その後Calculatorなど)。オブジェクトが挿入されるすべての場所でコードを変更せずに実装を変更するにはどうすればよいですか? 注入を担当し、として機能する工場を作成する必要がありますmethod injectorか? 私の解決策は正しくて意味がありますか?

工場

@ApplicationScoped
public class CalculatorFctory implements Serializable {
    private Calculator calc;

    @Produces @Calculator Calculator getCalculator() {
        return new Calculator();
    }
}

電卓を使用するクラス

public class CalculateUserAge {
    @Calculator
    @Inject
    private Calculator calc;
}

これは正しい解決策ですか?私が間違っている場合、またはより良い解決策がある場合は、私を修正してください。ありがとう!。

4

2 に答える 2

16

ここにはいくつかの問題があります。

  1. アプリケーション全体で目的の実装を変更する最善の方法は何ですか? を調べ@Alternativesます。
  2. 実装ごとに修飾子が必要ですか? いいえ、長くて詳細な説明については、この回答を参照してください。
  3. プロデューサーを使用して、どの実装を挿入するかを決定する必要がありますか? あなたが望む解決策かもしれませんが、私はそれを疑っています。プロデューサーは通常、コンストラクター / では実行できないある種の初期化を実行するために使用されます@PostConstruct。また、インジェクション ポイントを検査し、何をインジェクトするかを実行時に決定するために使用することもできます。いくつかの手がかりについては、リンク 2. を参照してください。
  4. この解決策は正しいですか?これは機能しますが、実装を変更するにはコードをいじる必要があるため、最初に 1. を検討してください。また@Calculator Calculator、冗長性が高いようです。もう一度、2 のリンクを参照してください。

    @ApplicationScoped
    public class CalculatorFctory implements Serializable {
        private Calculator calc;
    
        @Produces @Calculator Calculator getCalculator() {
            return new Calculator();
        }
    }
    

アップデート:

CDI は、依存関係の解決に型に加えて修飾子を使用します。つまり、注入ポイントの型と一致する型が 1 つしかない場合は、型だけで十分であり、修飾子は必要ありません。修飾子は、型だけでは不十分な場合に曖昧さをなくすためにあります。

例えば:

public class ImplOne implements MyInterface {
    ...
}

public class ImplTwo implements MyInterface {
    ...
}

どちらの実装も注入できるようにするために、修飾子は必要ありません。

@Inject ImplOne bean;

また

@Inject ImplTwo bean;

だから@Calculator Calculator冗長だと言っているのです。実装ごとに修飾子を定義しても、多くは得られず、単に型を使用した方がよいでしょう。たとえば、2 つの修飾子@QualOne@QualTwo:

@Inject @QualOne ImplOne bean;

@Inject @QualTwo ImplTwo bean;

前の例ではあいまいさの解消がまだ存在していないため、すぐ上の例では何も得られません。

確かに、特定の実装タイプにアクセスできない場合は、これを行うことができます。

@Inject @QualOne MyInterface bean; // to inject TypeOne

@Inject @QualTwo MyInterface bean; // to inject TypeTwo

ただし、電卓の実装をCDIで管理したい場合、OPは@Producesを使用すべきではありません。

@Avinash Singh -@Producesメソッドを呼び出すのが CDI である限り、CDI はそれらが返すものと同様に管理します。よろしければ、仕様のこのセクションを参照してください。これには、依存性注入、ライフサイクル コールバックなどをサポートする `@...Scoped Bean を返すことが含まれます。

ここではいくつかの詳細を見落としているため、次の 2 つを考慮してください。

public class SomeProducer {

    @Inject ImplOne implOne;
    @Inject ImplTwo implTwo;
    @Inject ImplThree implThree;

    @Produces
    public MyInterface get() {
        if (conditionOne()) {
            return implOne;
        } else if (conditionTwo()) {
            return implTwo;
        } else {
            return implThree;
        }
    }
}

public class SomeProducer {

    @Produces
    public MyInterface get() {
        if (conditionOne()) {
            return new ImplOne();
        } else if (conditionTwo()) {
            return new ImplTwo();
        } else {
            return new ImplThree;
        }
    }
}

次に、最初の例では、CDI はプロデューサから返されたもののライフ サイクル (つまり@PostConstruct@Injectサポート) を管理しますが、2 番目の例では管理しません。

元の質問に戻る - ソースを変更せずに実装を切り替える最良の方法は何ですか? 前提として、変更をアプリケーション全体に適用する必要があります。

@Default
public class ImplOne implements MyInterface {
    ...
}

@Alternative
public class ImplTwo implements MyInterface {
    ...
}

@Alternative
public class ImplThree implements MyInterface {
    ...
}

次に、 any for anyが注入されます@Inject MyInterface instanceImplOne

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
    <alternatives>
        <class>ImplTwo</class>
    </alternatives>
</beans>

が指定されている場合、ImplTwoどこにでも注入されます。

更なるアップデート

Java EE 環境には、EJB や Web サービスなど、CDI によって管理されないものがあります。

Web サービスを CDI マネージド Bean にどのように注入しますか? 本当に簡単です:

@WebServiceRef(lookup="java:app/service/PaymentService")
PaymentService paymentService;

以上で、CDI の外部で管理される支払いサービスへの有効な参照が得られます。

しかし、必要な場所でフルを使用したくない場合はどうすれば@WebServiceRef(lookup="java:app/service/PaymentService")よいでしょうか? 型だけで注入したい場合はどうしますか?次に、これをどこかで行います:

@Produces @WebServiceRef(lookup="java:app/service/PaymentService")
PaymentService paymentService;

その支払いサービスへの参照が必要な CDI Bean では、次のように CDI@Injectを使用して簡単に参照できます。

@Inject PaymentService paymentService;

プロデューサー フィールドを定義する前は、 CDI の方法PaymentServiceで注入できないことに注意してください。しかし、それは常に古い方法で利用できます。また、いずれの場合も Web サービスは CDI によって管理されませんが、プロデューサー フィールドを定義することで、その Web サービス参照が CDI の方法で注入できるようになります。

于 2013-03-16T16:58:17.233 に答える
12

ファクトリ メソッドを使用してコード内の実装を交換する場合、ファクトリ メソッドは CDI ではなく Bean を管理するため、@Calculator.

    @ApplicationScoped
     public class CalculatorFactory implements Serializable {
     enum CalculatorType{MiniCaculator,ScientificCaculator,MockCalculator};   
     Calculator getCalculator(CalculatorType calctype) {
                switch(calctype)
                  case MiniCaculator : return new MiniCalculator();
                  case ScientificCalculator : new ScientificCalculator();
                  case MockCalculator : new MockCalculator();
                  default:return null;
            }
        }
public class CalculatorScientificImpl {       
    private Calculator calc    =  
          CalculatorFactory.getCaclulator(CaclutorType.ScientificCalculator);
    doStuff(){}
}

public class CalculatorTest {       
    private Calculator calc    =
               CalculatorFactory.getCaclulator(CaclutorType.MockCalculator);
    doStuff(){}
}

ただし 、Caclulator Bean をインジェクションおよび @PostConstruct などを使用したライフサイクル管理のために CDI 管理する場合は、以下のいずれかの方法を使用できます。

アプローチ 1 :

利点:を使用して注釈を作成することを避けることができます@Named("miniCalculator")

欠点miniCalculator: コンパイラは、名前が sayから に変更された場合、このアプローチでエラーを出しませんxyzCalculator

@Named("miniCalculator")
class MiniCalculator implements Calculator{ ... }

@ApplicationScoped
public class CalculatorFactory implements Serializable {
    private calc;

    @Inject 
    void setCalculator(@Named("miniCalculator") Caclulator calc) {
        this.calc = calc;
    }
}

アプローチ 2: 推奨(インジェクションが失敗した場合、コンパイラはインジェクションを追跡します)

@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD})
public @interface MiniCalculator{
}

@ApplicationScoped
public class CalculatorFactory implements Serializable {
    private calc;

    @Inject 
    void setCalculator(@MiniCalculator calc) {
        this.calc = calc;
    }
}

アプローチ 3: ファクトリ メソッドを使用してオブジェクトを生成している場合。そのライフサイクルは CDI では管理されませんが、 @Inject を使用するとインジェクションは正常に機能します。

@ApplicationScoped
public class CalculatorFactory implements Serializable {
    private Calculator calc;    
    @Produces Calculator getCalculator() {
        return new Calculator();
    }
}    
public class CalculateUserAge {
    @Inject
    private Calculator calc;
}

CaculatorTest という名前のクラスがあるとします。

class ScientificCalculatorTest{        
    Caclulator scientificCalculator;        
    @Inject 
    private void setScientificCalculator(@ScientificCalculator calc) {
                this.scientificCalculator = calc;
            }        
    @Test
    public void testScientificAddition(int a,int b){
      scientificCalculator.add(a,b);
      ....
    } 
    }

テストでモック実装を使用する場合は、次のようにします。

   class CalculatorTest{        
        Caclulator calc;        
        @PostConstruct 
                init() {
                    this.calc = createMockCaclulator();
                }
        @Test
        public void testAddition(int a,int b){
          calc.add(a,b);
          .....
        }
        }
于 2013-03-16T18:12:56.733 に答える