2013-03-16 2 views
5

Я новичок в Java EE/JSF и теперь читаю о квалификаторах CDI - возможность изменить реализацию класса. Это здорово, но у меня есть один вопрос. Насколько я понимаю, я могу изменить реализацию класса с использованием квалификатора, но мне нужно изменить его везде, где я использую эту реализацию. Какое наилучшее решение сделать это в одном месте? С моими небольшими знаниями о Java EE я понял это.Как использовать квалификаторы CDI с несколькими реализациями классов?

Давайте представим, что мы создаем простое приложение для калькулятора. Нам нужно создать несколько классов:

  1. Calculator (базовая реализация калькулятора)
  2. ScientificCalculator (научное внедрение калькулятора)
  3. MiniCalculator (с минимальной потенцией)
  4. MockCalculator (для модульных тестов)
  5. Квалификатор @Calculator (укажет на фактическую реализацию калькулятора, должен ли я создавать квалификатор для каждой реализации?)

В этом вопросе. У меня есть четыре варианта калькулятора, и я хочу использовать один из них в нескольких местах, но только по одному (в начальной фазе проекта я буду использовать 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; 
} 

Является ли это правильным решением? Пожалуйста, исправьте меня, если я ошибаюсь или если есть лучшее решение. Благодаря!.

ответ

9

Если вы хотите заменить реализацию в своем коде с использованием заводского метода, то ваш заводский метод управляет компонентами, а не CDI, и поэтому нет необходимости в @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 бобы быть CDI удалось для инъекций и управления жизненным циклом с использованием @PostConstruct и т.д., то вы можете использовать один из перечисленных ниже подходов.

Подход 1:

Преимущество: Вы можете избежать создания аннотаций с использованием @Named ("miniCaclulator")

Недостаток: не компилятор выдаст ошибку при таком подходе, если есть это изменение имени от miniCaclulator к 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; 
    } 
} 

подход 2: Рекомендуемый (компилятор отслеживает инъекции если инъекции не удается)

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

Подход 3:Если вы используете фабричный метод для создания вашего объекта. Его жизненный цикл не будет управляться CDI, но Injection будет работать отлично, используя @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); 
      ..... 
     } 
     } 
+0

Пожалуйста, смотрите мое обновление в соответствии с вашим комментарием. Кроме того, производители и все, что они возвращают, управляются CDI ... – rdcrng

+1

Нет никакой причины комментировать что-либо с помощью '@ Produces', если вы собираетесь его называть. – rdcrng

+0

Как бы вы применили эту услугу к компоненту CDI? Мы будем использовать '@Produces @myservice @WebServiceRef (lookup =" java: app/service/PaymentService ") {} @Inject @myservice MyWebService;' В этом случае контейнер не управляет жизненным циклом MyWebServiceImpl, он просто вводит его –

9

Здесь есть несколько вопросов.

  1. Каков наилучший способ изменить желаемую реализацию во всем приложении? Посмотрите на @Alternatives.
  2. Нужен ли мне квалификатор для каждой реализации? Нет, см. this ответ на подробное подробное объяснение.
  3. Должен ли я использовать производителя, чтобы решить, какая реализация была введена? Мог быть решением, которое вы хотите, но я в этом сомневаюсь. Производители обычно используются для выполнения некоторой инициализации, которая не может быть выполнена в конструкторе/@PostConstruct. Вы также можете использовать его для проверки точки инъекции и принятия решений о времени выполнения инъекций. См. Ссылку 2. для некоторых подсказок.
  4. Правильное ли решение? Это будет работать, но вам все равно придется возиться с кодом для изменения реализации, поэтому сначала рассмотрите 1.. Также @Calculator Calculator кажется крайне избыточным. Опять же, см. Ссылку на 2.

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

Update:

CDI использует спецификаторы в дополнение к типов для разрешения зависимостей. Другими словами, до тех пор, пока существует только один тип, соответствующий типу точки впрыска, достаточно только типов, и квалификаторы не нужны. Квалификаторы существуют для устранения неоднозначности, когда типов недостаточно.

Например:

public class ImplOne implements MyInterface { 
    ... 
} 

public class ImplTwo implements MyInterface { 
    ... 
} 

Чтобы иметь возможность вводить либо реализацию, вам не нужны никакие отборочные:

@Inject ImplOne bean; 

или

@Inject ImplTwo bean; 

Вот почему я говорю @Calculator Calculator является избыточным. Если вы определяете квалификатор для каждой реализации, вы не получаете много, можете просто использовать этот тип. Скажем, два отборочные @QualOne и @QualTwo:

@Inject @QualOne ImplOne bean; 

и

@Inject @QualTwo ImplTwo bean; 

Пример непосредственно над ничего не получить, так как в предыдущем примере, не рас-двусмысленности не существовало уже.

Конечно, вы можете сделать это в тех случаях, когда у вас нет доступа к определенным типам реализации:

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

и

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

Однако ОП не следует использовать @Produces когда он хочет управлять CDI.

@Avinash Singh - CDI управляет @Produces, а также все, что они вернутся, до тех пор, как это CDI, который вызывает метод. См. this section of the spec, если хотите. Это включает в себя возвращение `@ ... бобы, которые с заданной областью будет поддерживать зависимостей обратных вызовов для инъекций, жизненного цикла и т.д.

я проглядел некоторые детали здесь, так считают следующие два:

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), что возвращается от производителя, но во втором случае это не будет.

Назад к исходному вопросу - что является лучшим способом переключения между реализациями без необходимости изменения источника? Предполагается, что вы хотите, чтобы изменение было широко распространенным.

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

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

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

Тогда любое для любого @Inject MyInterface instance, ImplOne будет введен, если

<?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, которые не управляются КДИ, такие как EJBs и веб-сервисов.

Как бы вы ввели веб-сервис в управляемый bean-носитель CDI? Это действительно просто:

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

Вот именно, там вы будете иметь действительную ссылку на службу платежей, который управляется за пределами КДИ.

Но, если вы не хотите использовать полный @WebServiceRef(lookup="java:app/service/PaymentService") везде, где это необходимо? Что делать, если вы хотите только ввести его по типу? Тогда вы делаете это где-то:

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

и в любом КДИ боба, который нуждается в ссылку на эту услугу оплаты вы можете просто @Inject это с помощью CDI, как это:

@Inject PaymentService paymentService; 

Обратите внимание, что перед определением поля производителя , PaymentService не будет доступен для инъекций CDI способ. Но он всегда доступен по старому пути. Кроме того, в любом случае веб-служба не управляется CDI, но определение поля производителя просто делает ссылку на веб-службу доступной для инъекции способом CDI.

+0

Вы, кажется, путаете простые аннотации с квалификаторами CDI. Да, квалификаторы CDI - это аннотации, но не все аннотации являются квалификаторами CDI. Для рассмотрения CDI в процессе разрешения зависимости аннотация должна наследоваться от http://docs.oracle.com/javaee/6/api/javax/inject/Qualifier.html. Таким образом, аннотирование метода продюсера с помощью @WebServiceRef не влияет на CDI, поскольку оно не наследуется от «Qualifier». – rdcrng

+0

Итак, '@Produces @WebServiceRef (lookup =" java: app/service/PaymentService ") MyWebServiceImpl get()' эквивалентно '@Produces MyWebServiceImpl get()' в отношении CDI. – rdcrng

+0

Как бы вы применили эту услугу к компоненту CDI? Мы будем использовать '@Produces @myservice @WebServiceRef (lookup =" java: app/service/PaymentService ") {} @Inject @myservice MyWebService;' В этом случае контейнер не управляет жизненным циклом MyWebServiceImpl, он просто вводит его –

Смежные вопросы