Spring高级装配

处理自动装配的歧义性

  在自动装配时,仅有一个bean装配所需的结果时,自动装配是最有效的。但是不仅有一个bean能够匹配的话,这种歧义性会阻碍Spring自动装配属性、构造器参数或方法参数。
  为了阐述自动装配的歧义性,假设使用@Autowired注解标注了setDessert()方法:

    @Autowired
    public void setDessert(Dessert dessert) {
        this.dessert = dessert;
    }

  在本例中,Dessert是一个接口,并且有三个类实现了这个接口,分别为Cake、Cookies和IceCream:

@Component
public class Cake implements Dessert {……}
@Component
public class Cookies implements Dessert {……}
@Component
public class IceCream implements Dessert {……}

  因为这三个类均使用了@Autowired注解,在组件扫描时,能够发现它们并将其创建为Spring应用上下文里面的bean。然后,当Spring试图自动装配setDessert()中的Dessert采纳数时,发现它并没有唯一的、无歧义的可选值,Spring会抛出org.springframework.beans.factory.NoUniqueBeanDefinitionException:

nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.mec.about_auto.Dessert' available: expected single matching bean but found 3: cake,cookies,iceCream

  为了解决这个问题,可以选择bean中的某一个设为首选(primary)的bean,或者使用限定符(qualifier)来帮助Spring将可选的bean的范围缩小到只有一个。

标示首选的bean
  在声明bean的时候通过将其中一个可选的bean设置为首选(primary)笨啊能够避免自动装配时的歧义性。通过@Primary来表示首选。@Primary能够与@Component组合使用在组件扫描的bean上,也可以与@bean组合使用在Java配置的bean声明中。

@Component
@Primary
public class IceCream implements Dessert {……}

  如果使用XML配置bean的话,同样可以实现这样的功能。<bean>元素有一个primary属性用来指定首选的bean。但是如果你标示了两个或更多的首选bean,那么它就无法正常工作了。显然,当不知一个bean被设置成了首选bean,那实际上就没有首选bean了。

限定自动装配的bean
  在标示首选的bean中所提到的@Primary注解仅仅可也标示一个优先选择方案,如果当首选bean的数量超过一个时,我们没有其他的方法进一步缩小可选范围。使用Spring的限定符能够在所有可选的bean上进行进一步的缩小范围,最终达到只有一个bean满足所有的限定条件。@Qualifier注解是使用限定符的主要方式。它与@Autowired@Inject注解协同使用,在注入的时候指定想要注入的是哪一个bean。例如:

    @Autowired
    @Qualifier("iceCream")
    private Dessert dessert;

    @org.junit.Test
    public void dessertShouldNotBeNull() {
        assertNotNull(dessert);
    }

  在这里,为@Qualifier注解所设置的参数就是想要注入的bean的ID。所有使用@Component注解声明的类都会被创建为bean,并且bean的ID为首字母变为小写的类名。因此,@Qualifier("iceCream")指向的组件扫描时创建的bean,并且这个bean是IceCream 的实例。
  基于默认的bean ID作为限定符是非常简单的,但可能存在一些问题。如果重构了IceCream类,将其重命名为Gelato,此时bean的ID和默认的限定名会变为gelato(ID与默认的限定符为类名称的全小写),这就无法匹配setDessert()方法的限定符,就会失败。这是因为setDessert()方法上的限定符与IceCream类的bean ID是紧耦合的,对类名称做任意改动都会导致注入的失败。不过可以通过创建自定义的限定符来解决这个问题。

创建自定义的限定符
  为bean设置自定义限定符,不依赖于bean ID作为限定符。这样就可以解决类的bean ID与类名称紧耦合的问题。需要做的是在bean的声明上添加@Qualifier注解(它可以与@Component注解组合使用)。

    @Autowired
    @Qualifier("cold")
    private Dessert dessert;

  当前情况下,cold限定符被分配给了IceCreambean。因为它没有耦合类名,所以可以随意重构IceCream类的类名,而不用担心破坏自动装配。只需在注入的地方,引用cold限定符即可。

@Component
@Qualifier("cold")
public class IceCream implements Dessert {……}

  当使用Java配置显示定义bean时,@Qualifier可以与@Bean同时使用。

使用自定义的限定符注解
  如果多个bean具有相同的特性,则使用特性限定符也会出现问题:同时又多个带有相同特性限定符的bean,使Spring不知道选取哪一个bean进行注入,再次出现了二义性问题。此时就需要使用更多的限定符来缩小范围,最终限定到只有一个bean。
  可能想到的方法就是在注入点和bean定义的地方同时添加另外一个@Qualifier系统注解。

@Component
@Qualifier("cold")
@Qualifier("ceramy")
public class IceCream implements Dessert {……}

  虽然这样做理论上可以解决二义性的问题,但是Java不允许在同一个条目上重复出现相同的多个注解(虽然Java 8允许出现重复的注解,只要这个注解在定义的时候带有@Repeatable注解就可以,但是Spring中的@Qualifier注解并没有在定义的时候添加@Repeatable注解。)。如果某一个类上出现了两个相同的注解,则编译器会提示错误。不过可以创建自定限定符注解来解决这一个问题。
  这里需要做的就是创建一个注解,本身要使用@Qualifier注解来标注,这样就不需要再使用@Qualifier("cold")注解,而是改用自定义的@Cold注解,该注解的定义如下所示:

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {}

  在定义时,添加了@Qualifier注解,它们就具有了@Qualifier注解的特性,它们本身实际上就称为了限定符注解。在是哟昂了自定义注解@Cold@Creamy后,IceCream类如下所示:

@Component
@Cold
@Creamy
public class IceCream implements Dessert {……}

  通过自定义注解的限定符注解,可以同时使用多个限定符,不再会出现编译器的限制或错误。同时,现对于使用原始的@Qualifier并借助String类型来指定限定符,自定义注解也更为类型安全。
  通过本文简单介绍了Spring在自动装配时遇到歧义性时的处理办法,通过学习简单记录下来,希望对于读者有少许帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值