领先一步
VMware 提供培训和认证,以加快您的进度。
了解更多有时,Twitter 真是个奇妙的地方。就在上周,我花了一些时间帮助澄清 Spring 的@Qualifier
注解的行为,它既比 JSR 330 更早出现,又提供了比 JSR 330 的@Qualifier
注解更丰富的超集。一些误解的人似乎认为 Spring 的注解没有提供与 JSR 330 注解相同的类型安全级别。我不知道这是因为他们根本没有阅读过相关的支持信息(这相当新,因为自2007年以来才出现),还是因为他们为那些如果停止使用 Spring 就能赚钱的公司工作,但无论如何,这是一个很好的复习机会!
限定符注解有助于在 Spring 否则无法做到这一点的情况下消除 bean 引用歧义。Spring 的 XML 配置支持此功能的一个版本,但当然,它没有类型安全。在本例中,我们将重点关注使用 Java 配置和组件扫描来注册 bean。随着越来越多的人转向 Spring 8 年前的 Java 配置样式,这个问题似乎越来越频繁地出现。Spring Boot 是一种以 Java 配置为首要方法的构建应用程序的方法,这种技术在基于 Spring Boot 的大型应用程序中可能派上用场。
它的用法很简单。假设您有两个实现MarketPlace
接口的 bean。如果您声明一个MarketPlace
数组 - 那么 Spring 将提供所有实现该接口的 bean
@Autowired
private MarketPlace[] marketPlaces;
如果您只想注入一个,则需要消除引用的歧义。在简单的情况下,您可以通过 bean ID 来实现。
@Autowired
@Qualifier( "ios") // the use is unique to Spring. It's darned convenient, too!
private MarketPlace marketPlace ;
这假设您在其他地方定义了一个 ID 为ios
的 bean。此用法是 Spring 独有的。您还可以使用@Qualifier
创建类型安全的绑定,该绑定通过限定符注解的特性将 bean 定义链接到注入位置。这是一个基于纯 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";
}
}
要编译并运行此示例,请确保您的 CLASSPATH 中包含org.springframework.boot:spring-boot-starter:1.1.8.RELEASE
。
此示例显示了两个MarketPlace
实现的定义,一个用于GoogleMarketPlace
,一个用于AppleMarketPlace
。我们定义了一个注解@Platform
,它采用Platform.OperatingSystems
类型的参数。该注解本身用@Qualifier
注解,这告诉 Spring 将其视为限定符。bean 定义相应地进行了注解:GoogleMarketPlace
用@Platform(Platform.OperatingSystems.ANDROID)
注解,AppleMarketPlace
用@Platform(Platform.OperatingSystems.IOS)
注解。然后,注入任何一个(在Spring
类中)就变得像在注入位置使用@Qualifier
注解一样简单。我在这里使用字段注入,但这只是一个草稿来完善事情。显然,在任何“真实”代码中,您都应该更喜欢构造函数和 setter 注入。
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";
}
}
要编译并运行此示例,请确保您的 CLASSPATH 中包含org.springframework.boot:spring-boot-starter:1.1.8.RELEASE
和javax.inject:javax.inject:1
。
有什么新的吗?没有。这就是重点。这从 Spring 2.5(我们在 2007 年发布)起就已成为可能。令人惊讶的是,人们仍然不知道这个功能,但希望这篇博客能使人们更容易上手。接下来,查看文档(从 2.5 开始!),它深入介绍了每一个细节 - 包括 XML 替代方案!
我应该提到 - 在实践中 - 我在我的代码中并不需要经常这样做。也许在过去的 7 年里只有十几次。不过,它可能会派上用场!