一个高质量的 @Qualifier

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

有时候,推特是个很棒的地方。就在上周,我花了一些时间来澄清 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 年里大概只有十几次。 不过它可能会派上用场!

获取 Spring 新闻资讯

随时了解 Spring 新闻资讯

订阅

取得领先

VMware 提供培训和认证,以加速您的进步。

了解更多

获取支持

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

了解更多

即将举行的活动

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

查看所有