一个高质量的 @Qualifier

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

有时,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 年里在我的代码中并没有过多地需要这样做。也许只有十几次。不过,它确实很有用!

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

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

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

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

查看所有