Arhn - архитектура программирования

Как использовать квалификаторы CDI с несколькими реализациями классов?

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

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

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

Это правильное решение? Пожалуйста, поправьте меня, если я ошибаюсь или есть лучшее решение. Спасибо!.


Ответы:


1

Если вы хотите изменить реализацию в своем коде с помощью фабричного метода, тогда ваш фабричный метод управляет bean-компонентами, а не 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(){}
}

Однако если вы хотите, чтобы ваши bean-компоненты Caclulator управлялись CDI для внедрения и управления жизненным циклом с использованием @PostConstruct и т. д., вы можете использовать один из следующих подходов.

Подход 1:

Преимущество: можно не создавать аннотацию с помощью @Named("miniCalculator").

Недостаток: компилятор не выдаст ошибку при таком подходе, если имя изменится, скажем, с miniCalculator на 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);
          .....
        }
        }
16.03.2013
  • Пожалуйста, смотрите мое обновление в соответствии с вашим комментарием. Кроме того, производители и все, что они возвращают, управляются CDI... 16.03.2013
  • Нет причин аннотировать что-либо с помощью @Produces, если вы собираетесь вызывать его самостоятельно. 16.03.2013
  • Как бы вы внедрили эту службу в bean-компонент CDI? Мы будем использовать @Produces @myservice @WebServiceRef(lookup="java:app/service/PaymentService") {} @Inject @myservice MyWebService;. В этом случае контейнер не управляет жизненным циклом MyWebServiceImpl, он просто внедряет его 17.03.2013
  • Компоненты @Produces по-прежнему управляются CDI, если только они не создаются в методе как new(). Я исправил свое определение того, когда использовать подход 3. 17.03.2013

  • 2

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

    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 использует квалификаторы в дополнение к типам для разрешения зависимостей. Другими словами, пока существует только один тип, соответствующий типу точки внедрения, одних типов достаточно и квалификаторы не нужны. Квалификаторы нужны для устранения неоднозначности, когда одних типов недостаточно.

    Например:

    public class ImplOne implements MyInterface {
        ...
    }
    
    public class ImplTwo implements MyInterface {
        ...
    }
    

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

    @Inject ImplOne bean;
    

    or

    @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
    

    Однако OP не должен использовать @Produces, когда он хочет, чтобы реализации калькулятора управлялись CDI.

    @Avinash Singh - CDI управляет @Produces, а также всем, что они возвращают, если именно CDI вызывает метод. Если хотите, см. этот раздел спецификации. Это включает в себя возврат компонентов `@...Scoped, которые будут поддерживать внедрение зависимостей, обратные вызовы жизненного цикла и т. д.

    Здесь я упустил некоторые детали, поэтому рассмотрим следующие два:

    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 действительно есть вещи, которые не управляются CDI, например EJB и веб-службы.

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

    @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, которому нужна ссылка на эту платежную службу, вы можете просто @Inject использовать CDI следующим образом:

    @Inject PaymentService paymentService;
    

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

    16.03.2013
  • Кажется, вы путаете простые аннотации с квалификаторами CDI. Да, квалификаторы CDI являются аннотациями, но не все аннотации являются квалификаторами CDI. Чтобы аннотация рассматривалась CDI в процессе разрешения зависимостей, она должна наследоваться от docs.oracle.com/javaee/6/api/javax/inject/Qualifier.html. Таким образом, аннотирование метода производителя с помощью @WebServiceRef не влияет на CDI, поскольку он не наследуется от Qualifier. 16.03.2013
  • Таким образом, @Produces @WebServiceRef(lookup="java:app/service/PaymentService") MyWebServiceImpl get() эквивалентен @Produces MyWebServiceImpl get() в том, что касается CDI. 16.03.2013
  • Как бы вы внедрили эту службу в bean-компонент CDI? Мы будем использовать @Produces @myservice @WebServiceRef(lookup="java:app/service/PaymentService") {} @Inject @myservice MyWebService;. В этом случае контейнер не управляет жизненным циклом MyWebServiceImpl, он просто внедряет его 17.03.2013
  • Спасибо за Ваш ответ. Есть еще один сценарий, скажем, у нас есть два PamentService веб-сервиса, и мы хотим использовать @Produces @service1 @WebServiceRef(lookup="java:app/service/PaymentService1") PaymentService paymentService;, тогда нам понадобится квалификатор @service1 в объявлении переменной, иначе он выдаст ошибку, поскольку не сможет разрешить правильную реализацию. В любом случае жизненный цикл этого веб-сервиса или любого компонента не будет управляться, пока мы не объявим сам компонент с аннотацией @Named. 17.03.2013
  • Да, я думаю, в этом случае он будет жаловаться на неудовлетворенные зависимости. Вот почему я настоятельно рекомендовал не использовать квалификаторы, если достаточно разрешения по типу (пункт об избыточности). Это не значит, что квалификаторы не следует использовать, существует множество случаев, когда они очень полезны. :) Что касается того, чем управляет CDI, то к named это не имеет никакого отношения. CDI по умолчанию сканирует затем все развертывание и может внедрить все обнаруженные типы, которые являются допустимыми bean-компонентами, см. docs.jboss.org/cdi/spec/1.0/html_single/#whatclassesarebeans. 17.03.2013
  • Таким образом, вы можете внедрить все, что CDI определяет как действительный компонент. Таким образом, технически это также может быть PaymentProcessor в зависимости от реализации, но на самом деле это не будет веб-службой JAX-WS, поскольку CDI не будет знать, как правильно ею управлять. 17.03.2013
  • Кроме того, вы были частично правы изначально относительно того, управляются ли вещи, возвращенные от производителя, CDI или нет, пожалуйста, смотрите мое обновление прямо под зачеркнутым текстом. 17.03.2013
  • спасибо за подробный ответ, я исправил свой ответ. Мне было не очень ясно, для чего используется @Produces @xyz , но, исходя из моего понимания, теперь кажется, что он используется для различения двух методов @Produces, возвращающих один и тот же тип родительского объекта. 17.03.2013
  • Я думаю, что между нами двумя и комментарием мы предоставили достаточно деталей :) 17.03.2013
  • Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..