Достоинство @Qualifier

Временами teh Twitterz — удивительное место. Просто на прошлой неделе я потратил некоторое время, помогая понять поведение аннотации @Qualifier, которая старше JSR 330 и предоставляет больше возможностей, чем такая же аннотация в JSR 330. Некоторые из пришедших оказались под впечатлением от того, что Spring аннотация не предлагает такой же уровень безопасности, как и JSR 330 аннотация. Я не знаю, может это потому что они не читали о поддержке(которая является достаточно новой, всего лишь с 2007-го) или потому что они работали на компании, которые зарабатываю на том, что вы прекращаете использовать Spring, но в любом случае это было прекрасной возможностью повысить свою квалификацию!

Квалификатор аннотации позволяет устранить неоднозначность ссылок бина, когда сам Spring не сможет этого сделать. Spring XML конфигурация поддерживает такой вариант, но, конечно, без поддержки безопасности типов. В этом примере мы обращаем внимание на использование Java конфигурации и сканировании компонентов для регистрации бинов. Поскольку все больше людей переходят на стиль Java конфигураций, этот вопрос все чаще поднимается. Spring Boot является первопроходцем в Java конфигурацию для построения приложений, а эта техника может просто пригодится в большом приложении на основе Spring Boot.

Его использование простое. Предположим, что у вас есть два бина, которые реализуют MarketPlace интерфейс. Если вы объявите массив типа MarketPlaces, тогда Spring предоставит всем бинам реализацию интерфейса:

@Autowired
private MarketPlace[] marketPlaces;

Если вы хотите внедрить только одну из реализаций, вам нужно разрешить неоднозначность ссылок. Сделать это можно, просто указав ID бина.

@Autowired 
@Qualifier( "ios") // использование уникального для Spring. Это чертовски удобно!
private MarketPlace marketPlace;

Это предполагает, что где-то вы уже описали бин с ID ios. Такое использование уникально для Spring. Вы также можете использовать @Qualifier для создания типо-безопасного соответствия ссылок с определением бинов в месте инъекции @Qualifier аннотации. Ниже пример, основанный на Spring аннотациях:

package spring;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static spring.Spring.Platform;

@Configuration
@ComponentScan
public class Spring {

    public static void main(String[] args) {
        new AnnotationConfigApplicationContext(Spring.class);
    }

    @Autowired
    @Platform(Platform.OperatingSystems.ANDROID)
    private MarketPlace android;

    @Autowired
    @Platform(Platform.OperatingSystems.IOS)
    private MarketPlace ios;

    @PostConstruct
    public void qualifyTheTweets() {
        System.out.println("ios:" + this.ios);
        System.out.println("android:" + this.android);
    }

    // the type has to be public!
    @Target({ElementType.FIELD,
            ElementType.METHOD,
            ElementType.TYPE,
            ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Qualifier
    public static @interface Platform {

        OperatingSystems value();

        public static enum OperatingSystems {
            IOS,
            ANDROID
        }
    }
}

interface MarketPlace {
}

@Component
@Platform(Platform.OperatingSystems.IOS)
class AppleMarketPlace implements MarketPlace {

    @Override
    public String toString() {
        return "apple";
    }
}

@Component
@Platform(Platform.OperatingSystems.ANDROID)
class GoogleMarketPlace implements MarketPlace {

    @Override
    public String toString() {
        return "android";
    }
}

Скомпилируйте и запустите этот пример, убедившись в наличии org.springframework.boot:spring-boot-starter:1.1.8.RELEASE в вашем CLASSPATH.

Этот пример показывает описание двух MarketPlace реализаций, один для GoogleMarketPlace, другой — для AppleMarketPlace. Мы описываем аннотацию @Platform, которая имеет параметр типа Platform.OperatingSystems. Эта аннотация сама аннотирована @Qualifier, что говорит Spring обращаться к ней как к квалификатоору. Бины аннотированы соответственно: GoogleMarketPlace аннотирован @Platform(Platform.OperatingSystems.ANDROID), AppleMarketPlace@Platform(Platform.OperatingSystems.IOS. Инъектирование каждого(Spring класса) становится таким же простым, как и использование аннотации @Qualifier. Здесь я использую инъекцию поля, хотя это всего лишь сделано ради пояснения. Понятное дело, что в реальном коде вы должны отдавать предпочтение инъекция конструкторам и методам установки.

Spring изначально поддерживает JSR 330. Ведь мы помогали в продвижении этой инициативы. Ниже эквивалентный пример использования JSR 330. @Component превратился в @Named, @Autowired — в @Inject, а @Qualifier — в @javax.inject.Qualifier, но в любом случае это должно быть вам знакомо.

package jsr330;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static jsr330.Jsr330.Platform;

@Configuration
@ComponentScan
public class Jsr330 {

    public static void main(String[] args) {
        new AnnotationConfigApplicationContext(Jsr330.class);
    }

    @Inject
    @Platform(Platform.OperatingSystems.ANDROID)
    private MarketPlace android;

    @Inject
    @Platform(Platform.OperatingSystems.IOS)
    private MarketPlace ios;

    @PostConstruct
    public void qualifyTheTweets() {
        System.out.println("ios:" + this.ios);
        System.out.println("android:" + this.android);
    }

    // the type has to be public!
    @Target({ElementType.FIELD,
            ElementType.METHOD,
            ElementType.TYPE,
            ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @javax.inject.Qualifier
    public static @interface Platform {

        OperatingSystems value();

        public static enum OperatingSystems {
            IOS,
            ANDROID
        }
    }
}

interface MarketPlace {
}

@Named
@Platform(Platform.OperatingSystems.IOS)
class AppleMarketPlace implements MarketPlace {

    @Override
    public String toString() {
        return "apple";
    }
}

@Named
@Platform(Platform.OperatingSystems.ANDROID)
class GoogleMarketPlace implements MarketPlace {

    @Override
    public String toString() {
        return "android";
    }
}

Скомпилируйте и запустите этот пример, убедившись в наличии org.springframework.boot:spring-boot-starter:1.1.8.RELEASE и javax.inject:javax.inject:1 в вашем CLASSPATH.

Есть ли здесь что-то новое? Нет. Это точка. Это было возможно с Spring 2.5(выпущено в 2007). Это удивляет тех людей, которые не были с этим знакомы, но будем надеяться, что эта статья поможет людям разобраться с этим. Следующий шаг — документация, которая охватывает каждую деталь подробнее, включая альтернативы XML.

comments powered by Disqus