@Inject @Default
private NumberGenerator numberGenerator;
@Default — это встроенный квалификатор, сообщающий CDI, когда нужно внедрить реализацию компонента по умолчанию. Если вы определите компонент без квалификатора, ему автоматически присвоится квалификатор @Default. Таким образом, код в листинге 2.6 идентичен коду в листинге 2.5.
@Default
public class IsbnGenerator implements NumberGenerator {
··public String generateNumber() {
····return "13-84356-" + Math.abs(new Random(). nextInt());
··}
}
Если у вас есть только одна реализация компонента для внедрения, применяется поведение по умолчанию, а реализация будет внедрена непосредственно с использованием @Inject. Схема класса на рис. 2.4 показывает реализацию @Default (IsbnGenerator), а также точку внедрения по умолчанию (@Inject @Default). Но иногда приходится выбирать между несколькими реализациями. Это как раз тот случай, когда нужно использовать квалификаторы.
Рис. 2.4. Схема класса с квалификатором @Default
Во время инициализации системы контейнер должен подтвердить, что каждой точке внедрения соответствует строго один компонент. Это означает, что при отсутствии доступной реализации NumberGenerator контейнер сообщит вам о неудовлетворенной зависимости и не будет развертывать приложение. При наличии только одной реализации внедрение будет работать, используя квалификатор @Default (см. рис. 2.4). Если бы в наличии имелось несколько реализаций по умолчанию, то контейнер проинформировал бы вас о неоднозначной зависимости и не стал бы развертывать приложение. Это происходит потому, что в работе типобезопасного алгоритма разрешения происходит сбой, когда контейнер не может определить строго один компонент для внедрения.
Итак, как же компонент выбирает, какая реализация (IsbnGenrator или IssnGenerator) будет внедряться? При объявлении и внедрении компонентов в большинстве фреймворков активно задействуется XML-код. CDI использует квалификаторы: в основном это аннотации Java, осуществляющие типобезопасное внедрение и однозначно определяющие тип без необходимости отката к строковым именам.
Предположим, у нас есть приложение с сервисом BookService, который создает книги с тринадцатизначным номером ISBN, и с LegacyBookService, создающим книги с восьмизначным номером ISSN. Как вы можете видеть на рис. 2.5, оба сервиса внедряют ссылку на один интерфейс NumberGenerator. Сервисы различаются реализациями благодаря использованию квалификаторов.
Рис. 2.5. Сервисы, использующие квалификаторы для однозначного внедрения
Квалификатор представляет определенную семантику, ассоциированную с типом и удовлетворяющую отдельной реализации этого типа. Это аннотация, которая определяется пользователем и, в свою очередь, сопровождается аннотацией @javax.inject.Qualifer. Например, мы можем использовать квалификаторы для замещения тринадцати- и восьмизначных генераторов чисел. Оба примера показаны в листингах 2.7 и 2.8.
@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD})
public @interface ThirteenDigits { }
@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD})
public @interface EightDigits { }
После того как вы определили требуемые квалификаторы, необходимо применить их к соответствующей реализации. Как показано в листингах 2.9 и 2.10, квалификатор @ThirteenDigits применяется к компоненту IsbnGenerator, a @EightDigits — к IssnGenerator.
@ThirteenDigits
public class IsbnGenerator implements NumberGenerator {
··public String generateNumber() {
····return "13-84356-" + Math.abs(new Random(). nextInt());
··}
}
@EightDigits
public class IssnGenerator implements NumberGenerator {
··public String generateNumber() {
····return "8-" + Math.abs(new Random(). nextInt());
··}
}
Затем эти квалификаторы применяются к точкам внедрения, чтобы отличить, какой реализации требует клиент. В листинге 2.11 BookService явным образом определяет тринадцатизначную реализацию посредством внедрения ссылки на генератор чисел @ThirteenDigits, а в листинге 2.12 LegacyBookService внедряет восьмизначную реализацию.