2013-03-16 16 views
5

Jestem nowy w Java EE/JSF i teraz czytam o kwalifikatorach CDI - możliwość zmiany implementacji klas. To jest świetne, ale mam jedno pytanie. O ile rozumiem, mogę zmienić implementację klas za pomocą kwalifikatora, ale muszę go zmienić wszędzie, gdzie używam tej implementacji. Jakie jest najlepsze rozwiązanie, aby zrobić to w jednym miejscu? Mając małą wiedzę na temat Java EE, wymyśliłem tę.Jak używać kwalifikatorów CDI z wieloma implementacjami klas?

Wyobraźmy sobie, że tworzymy prostą aplikację kalkulatora. Musimy stworzyć kilka klas:

  1. Calculator (podstawowa implementacja kalkulatora)
  2. ScientificCalculator (realizacja naukowego kalkulatora)
  3. MiniCalculator (z minimalnym potencjalności)
  4. MockCalculator (dla testów jednostkowych)
  5. Kwalifikator @Calculator (wskaże rzeczywistą implementację kalkulatora, czy powinienem utworzyć kwalifikator dla każdej implementacji?)

Tutaj jest pytanie. Mam cztery implementacje kalkulatora i chcę użyć jednego z nich w kilku miejscach, ale tylko jeden na czas (w początkowej fazie projektu użyję MiniCalculator, następnie Calculator itd.). Jak mogę zmienić implementację bez kodu zmiany w każdym miejscu, w którym wstrzyknięty jest obiekt? Czy powinienem stworzyć fabrykę, która będzie odpowiedzialna za wstrzykiwanie i będzie działać jako method injector? Czy moje rozwiązanie jest poprawne i sensowne?

Fabryka

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

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

klasy, która wykorzystuje Kalkulator

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

Czy to prawidłowe rozwiązanie? Proszę mnie poprawić, jeśli się mylę lub istnieje lepsze rozwiązanie. Dzięki!.

Odpowiedz

9

Jeśli chcesz zamienić implementację w swoim kodzie przy użyciu metody fabrycznej, to twoja metoda fabryczna zarządza ziarnami, a nie CDI i tak naprawdę nie ma potrzeby, aby @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(){} 
} 

Jednak jeśli chcesz, aby fasola caclulator być CDI udało do wstrzykiwań i zarządzanie cyklem życia korzystających @PostConstruct etc można użyć jednej z poniższych metod.

Podejście 1:

Advantage: Można uniknąć tworzenia adnotacji przy użyciu @Named ("miniCaclulator")

Wada: kompilator nie da się błąd z tym podejściem, jeśli istnieje to zmiana nazwy z say miniCaclulator na xyzCaclulator.

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

@ApplicationScoped 
public class CalculatorFactory implements Serializable { 
    private calc; 

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

Podejście 2: Zalecane (Compiler śledzi zastrzyku jeśli wtrysk nie)

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

@ApplicationScoped 
public class CalculatorFctory implements Serializable { 
    private calc; 

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

Podejście 3:Jeśli używasz metody fabryki wygenerować obiekt. Nie będzie można zarządzać cyklem życia, ale wtrysk będzie działał poprawnie przy użyciu @Inject.

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

Wszystkie trzy podejścia będzie działać do testowania, że ​​masz klasę o nazwie 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); 
     .... 
    } 
    } 

jeśli chcesz używać mock wdrożenia w swoim teście następnie zrobić coś takiego,

class CalculatorTest{   
     Caclulator calc;   
     @PostConstruct 
       init() { 
        this.calc = createMockCaclulator(); 
       } 
     @Test 
     public void testAddition(int a,int b){ 
      calc.add(a,b); 
      ..... 
     } 
     } 
+0

Proszę zobaczyć moją aktualizację zgodnie z komentarzem. Również producenci i wszystko, co wracają, są zarządzane przez CDI ... – rdcrng

+1

Nie ma powodu do opisywania czegokolwiek z '@ Produces', jeśli zamierzasz to nazwać. – rdcrng

+0

Jak wprowadzić tę usługę do komponentu bean CDI? Użylibyśmy '@Produces @myservice @WebServiceRef (lookup =" java: app/service/PaymentService ") {} @Inject @myservice MyWebService;' W tym przypadku kontener nie zarządza cyklem życia MyWebServiceImpl, jest to po prostu wstrzyknięcie go –

9

Istnieje kilka problemów tutaj.

  1. Jaki jest najlepszy sposób na zmianę pożądanej implementacji w całej aplikacji? Zajrzyj do @Alternatives.
  2. Czy potrzebuję kwalifikatora do każdej realizacji? Nie, odpowiedź na pytanie this można znaleźć w obszernym i szczegółowym wyjaśnieniu.
  3. Czy powinienem użyć producenta, aby zdecydować, która implementacja zostanie wprowadzona? Może być rozwiązaniem, które chcesz, ale wątpię w to. Producenci są zwykle wykorzystywani do przeprowadzania inicjalizacji, których nie można wykonać w konstruktorze/@PostConstruct. Można również użyć go do sprawdzenia punktu wtrysku i podejmowania decyzji czasu wykonywania, co należy wstrzyknąć. Zobacz link 2. dla niektórych wskazówek.
  4. Czy to rozwiązanie jest poprawne? To zadziała, ale nadal będziesz musiał zadzierać z kodem, aby zmienić implementację, więc pomyśl 1. Najpierw. Również @Calculator Calculator wydaje się bardzo zbędny. Ponownie zobacz link pod adresem 2.

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

Aktualizacja:

CDI używa kwalifikatorów oprócz do rodzajów rozwiązywania zależności. Innymi słowy, o ile istnieje tylko jeden typ, który odpowiada typowi punktu wtrysku, wystarczą same typy, a kwalifikatory nie są potrzebne. Kwalifikatory służą do ujednoznacznienia, gdy same typy nie wystarczą.

Na przykład:

public class ImplOne implements MyInterface { 
    ... 
} 

public class ImplTwo implements MyInterface { 
    ... 
} 

aby móc wprowadzić zarówno realizację, nie trzeba żadnych kwalifikatorów:

@Inject ImplOne bean; 

lub

@Inject ImplTwo bean; 

Dlatego mówię @Calculator Calculator jest zbędny. Jeśli zdefiniujesz kwalifikator dla każdej implementacji, nie zyskujesz zbyt wiele, możesz również użyć tego typu. Powiedzieć, dwie eliminacje @QualOne i @QualTwo:

@Inject @QualOne ImplOne bean; 

i

@Inject @QualTwo ImplTwo bean; 

Przykład bezpośrednio powyżej nic nie zyskać, ponieważ w poprzednim przykładzie nie dis-dwuznaczność już istniał.

Oczywiście, można to zrobić w przypadkach, gdzie nie ma dostępu do poszczególnych rodzajów realizacji:

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

i

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

jednak PO nie należy używać @Produces kiedy chce, aby kalkulatory były zarządzane CDI.

@Avinash Singh - CDI zarządza @Produces jak cokolwiek wrócą, tak długo, jak to jest CDI który wywołuje metodę. Jeśli chcesz, zobacz this section of the spec. Obejmuje powrocie `@ ... scoped fasola, które będą wspierać zależnościami callbacków wtrysk, cyklu życia, itp

przeoczyłem pewne szczegóły tutaj, więc należy wziąć pod uwagę następujące dwa:

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; 
     } 
    } 
} 

i

public class SomeProducer { 

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

Następnie, w pierwszym przykładzie, CDI będzie zarządzać cyklem życia (tj. Wsparcie @PostConstruct i @Inject) tego, co zostało zwrócone przez producenta, ale w drugim przypadku nie będzie.

Powrót do pierwotnego pytania - jaki jest najlepszy sposób przełączania się między implementacjami bez konieczności modyfikowania źródła? Założeniem jest, że chcesz, aby zmiana miała szeroki zasięg.

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

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

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

Następnie każdy z każdym @Inject MyInterface instance, ImplOne zostanie wstrzyknięty, chyba

<?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> 

jest określona, ​​w którym to przypadku ImplTwo zostanie wstrzyknięty wszędzie.

Dalsze Aktualizacja

Istnieje rzeczywiście rzeczy w środowisku Java EE, które nie są zarządzane przez CDI, takich jak EJB i usług internetowych.

Jak wstrzyknąć usługę sieci Web do komponentu zarządzanego przez komponent CDI? To naprawdę proste:

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

To jest to, tam będziesz mieć ważne odniesienie do usługi płatności, która jest zarządzana poza CDI.

Ale, jeśli nie chcesz korzystać z pełnej wersji @WebServiceRef(lookup="java:app/service/PaymentService") wszędzie tam, gdzie jej potrzebujesz? Co jeśli chcesz wstrzyknąć tylko według typu? Następnie należy zrobić to gdzieś:

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

aw każdym CDI fasoli, która potrzebuje odniesienie do tej usługi płatniczej można po prostu @Inject go przy użyciu CDI tak:

@Inject PaymentService paymentService; 

Należy pamiętać, że przed określeniem pola producenta , PaymentService nie byłby dostępny do wstrzyknięcia CDI way. Ale zawsze jest dostępny stary sposób. Ponadto, w obu przypadkach usługa internetowa nie jest zarządzana przez CDI, ale określenie pola producenta po prostu sprawia, że ​​ten odnośnik do usługi sieciowej jest dostępny do wstrzyknięcia metodą CDI.

+0

Wydaje się, że mylicie proste adnotacje z kwalifikatorami CDI. Tak, kwalifikatory CDI to adnotacje, ale nie wszystkie adnotacje to kwalifikatory CDI. Aby zostać uznanym przez CDI w procesie rozwiązywania zależności, adnotacja musi dziedziczyć po http://docs.oracle.com/javaee/6/api/javax/inject/Qualifier.html. W związku z tym, przypisywanie metody producenta za pomocą @WebServiceRef nie ma wpływu na CDI, ponieważ nie dziedziczy po 'Qualifier'. – rdcrng

+0

Tak więc '@Produces @WebServiceRef (lookup =" java: app/service/PaymentService ") MyWebServiceImpl get()' jest równoważne '@Produces MyWebServiceImpl get()' w odniesieniu do CDI. – rdcrng

+0

Jak wprowadzić tę usługę do komponentu bean CDI? Będziemy używać '@Produces @myservice @WebServiceRef (lookup =" java: app/service/PaymentService ") {} @Inject @myservice MyWebService;' W tym przypadku kontener nie zarządza cyklem życia MyWebServiceImpl, jest to jedynie jego wstrzykiwanie –