当需要把一个 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 保持解耦关系。