Spring journey -- 处理自动装配歧义性问题

当需要把一个 Bean 装配到另一个 Bean 中时,一般都是给传一个接口,但是如果这个接口有多个实现类的话,Spring 就会不知道应该把哪一个实现类传过去,这个时候就会出现歧义性,像下面的这段代码:

Dessert 是一个接口:

package com.springinaction.dessert;

/**
 * Created by user on 3/4/17.
 */
public interface Dessert {
}
它的三个实现类:

Cake:

package com.springinaction.dessert;

import org.springframework.stereotype.Component;

/**
 * Created by user on 3/4/17.
 */
@Component
public class Cake implements Dessert {
}
Cookie:

package com.springinaction.dessert;

import org.springframework.stereotype.Component;

/**
 * Created by user on 3/4/17.
 */
@Component
public class Cookie implements Dessert {
}

IceCream:

package com.springinaction.dessert;

import org.springframework.stereotype.Component;

/**
 * Created by user on 3/4/17.
 */
@Component()
public class IceCream implements Dessert {
}
写一个测试类:

package com.springinaction.dessert;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.junit.Assert.assertNotNull;

/**
 * Created by user on 3/4/17.
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DessertConfig.class)
public class DessertTest {

    @Autowired
    private Cookie cookie;

    @Test
    public void cdShouldNotBeNull(){
        assertNotNull(cookie);
    }
}
运行之后的结果就是这样:


解决办法:可以给三个当中任意一个实现加一个@Primary 注解,用来确定维一性。但是如果给了两个实现上都加了@Primary 呢,结果是必然的,因为这是肯定不是唯一了撒。

@Qualifier 是使用限定符的主要方式,用来缩小选择范围。像下面这样,是不会报错的(此时@Primary 已经不在使用了):

package com.springinaction.dessert;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

/**
 * Created by user on 3/4/17.
 */
@Component
public class HandleQualifier {

    private Dessert dessert;

    @Autowired
    @Qualifier("iceCream")
    public void setDessert(Dessert dessert){
        this.dessert = dessert;
    }
}
从上面的代码可以看到@Qualifier 里指定了『iceCream』,这个名字是IceCream 类在组件扫描时自动创建的,也就默认的名字,但是如果后面对这个类进行重构后,名字发生了改变,这里肯定就会报错了,自动装配就会失败。即 setDessert()上所指定的限定符与要注入的 bean 的名称是紧耦合的。对类名称的任意改动都会导致限定符失效。就像下面这样,我把 IceCream 的名字改成这样:

package com.springinaction.dessert;


import org.springframework.stereotype.Component;

/**
 * Created by user on 3/4/17.
 */
@Component("aa")
public class IceCream implements Dessert {
}
上面这里已经改了 IceCream 的名字,再去运行上面的测试代码时就会报错了,解决方法是要把 setDessert() 的 @Qualifier 改成下面这样:

package com.springinaction.dessert;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

/**
 * Created by user on 3/4/17.
 */
@Component
public class HandleQualifier {

    private Dessert dessert;

    @Autowired
    @Qualifier("aa")
    public void setDessert(Dessert dessert){
        this.dessert = dessert;
    }
}
所以这里就充分的说明了 setDessert()上面的限定符与要注入的 bean 的名称是紧耦合的关系。

那么解决办法是创建自定义的限定符,我们可以了 bean 设置自己的限定符,而不是依赖于将 beanID 作为出限定符。

现在把 IceCream 类改成这样:

package com.springinaction.dessert;


import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

/**
 * Created by user on 3/4/17.
 */
@Component()
@Qualifier("cold")
public class IceCream implements Dessert {
}
那么在 setDessert()方法上面就可以这样写了:

package com.springinaction.dessert;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

/**
 * Created by user on 3/4/17.
 */
@Component
public class HandleQualifier {

    private Dessert dessert;

    @Autowired
    @Qualifier("cold")
    public void setDessert(Dessert dessert){
        this.dessert = dessert;
    }
}
面向特性的限定符要比基于 bean ID 的限定符更好一些。但是这个时候如果多个 bean 出现相同的特性的话,那上面的这种方法又会不能唯一确定到一个指定的 bean 了。如果你想再 setDessert()上面加多个限定符,情理之中,但是 Java 不允许在同一个条目上重复出现相同类型的多个注解,解决办法:创建自定义的限定符注解

自定义注解@Cold:

package com.springinaction.dessert;

import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by user on 3/4/17.
 */
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {
}
再来一个:

package com.springinaction.dessert;

import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by user on 3/4/17.
 */
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Creamy {
}
现在把这两个注解添加到 IceCream 上面:

package com.springinaction.dessert;

import org.springframework.stereotype.Component;

/**
 * Created by user on 3/4/17.
 */
@Component()
@Cold
@Creamy
public class IceCream implements Dessert {
}
为了得到 IceCream bean , 在 setDessert()上面就应该这样写了:

package com.springinaction.dessert;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Created by user on 3/4/17.
 */
@Component
public class HandleQualifier {

    private Dessert dessert;

    @Autowired
    @Cold
    @Creamy
    public void setDessert(Dessert dessert){
        this.dessert = dessert;
    }
}
现在回过头来看 setDessert 方法以及它上面的注解,可以发现没有任何地方明确指定要把 IceCream 自动装配到该方法中,所以 setDessert()方法可以与特定的 bean 保持解耦关系。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值