高质量 @Qualifier

工程 | Josh Long | 2014年11月4日 | ...

有时,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.RELEASEjavax.inject:javax.inject:1

有什么新的吗?没有。这就是重点。这从 Spring 2.5(我们在 2007 年发布)起就已成为可能。令人惊讶的是,人们仍然不知道这个功能,但希望这篇博客能使人们更容易上手。接下来,查看文档(从 2.5 开始!),它深入介绍了每一个细节 - 包括 XML 替代方案!

我应该提到 - 在实践中 - 我在我的代码中并不需要经常这样做。也许在过去的 7 年里只有十几次。不过,它可能会派上用场!

获取 Spring 简讯

通过 Spring 简讯保持联系

订阅

领先一步

VMware 提供培训和认证,以加快您的进度。

了解更多

获得支持

Tanzu Spring在一个简单的订阅中提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部