Статьи

Качество @Qualifier

Первоначально опубликовано в блоге SpringSource  Джош Лонг 


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

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

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

@Autowired
private MarketPlace[] marketPlaces; 

Если вы хотите ввести только один, вам нужно устранить неоднозначность ссылок. В простом случае вы можете просто сделать это по идентификатору компонента:

@Autowired 
@Qualifier( "ios") // the use is unique to Spring. It's darned convenient, too!
private MarketPlace marketPlace ;




Это предполагает, что вы в другом месте определили компонент, чей ID  ios. Это использование является уникальным для Spring. Вы также можете использовать  @Qualifier для создания безопасной привязки типа, которая связывает определение компонента с сайтом внедрения по качествам аннотации квалификатора. Вот пример, основанный на аннотациях pureplay 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, чтобы рассматривать ее как классификатор. Определения bean-компонентов помечаются соответствующим образом: с  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.

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

Я должен отметить, что — на практике — мне не нужно было делать это много в моем коде. Может быть, дюжину раз за последние 7 лет. Хотя это может быть удобно!