使用默认的限定符虽然解决了问题,不过可能会引入一些问题。比如我在重构代码时,将IceCream类名修改成了Gelato:
package chapter03.ambiguity; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @Component @Primary public class Gelato implements Dessert { @Override public void showName() { System.out.println("冰激凌"); } }此时运行代码,会发现抛出org.springframework.beans.factory.NoSuchBeanDefinitionException异常,如下所示:
这是因为IceCream重命名为Gelato之后,bean ID由iceCream变成了gelato,但我们注入地方的代码仍然使用的是iceCream这个bean ID,导致没有找到匹配条件的bean。
鉴于使用默认的限定符的这种局限性,我们可以使用自定义的限定符来解决这个问题。
为不影响后面代码的测试结果,将Gelato类再改回IceCream
3.2 基于面向特性的限定符为了避免因为修改类名而导致自动装配失效的问题,我们可以在@Component或者@Bean注解声明bean时添加上@Qualifier注解,如下所示:
package chapter03.ambiguity; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @Component @Qualifier("cold") public class IceCream implements Dessert { @Override public void showName() { System.out.println("冰激凌"); } }然后在注入的地方,不再使用默认生成的bean ID,而是使用刚刚指定的cold限定符:
@Autowired @Qualifier("cold") public void setDessert(Dessert dessert) { this.dessert = dessert; }运行测试代码,输入结果如下所示:
冰激凌
此时将IceCream类重命名为Gelato,代码可以正常运行,不会受影响。
然后有一天,某位开发又新建了类Popsicle,该类也使用了cold限定符:
package chapter03.ambiguity; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @Component @Qualifier("cold") public class Popsicle implements Dessert { @Override public void showName() { System.out.println("棒冰"); } }此时又带来了新的歧义性问题,因为Spring又不知道该如何选择了,运行代码会抛出org.springframework.beans.factory.NoUniqueBeanDefinitionException异常,如下所示:
此时,我们就需要用到自定义的限定符了。
3.3 自定义的限定符注解首先,我们新建以下3个注解:
package chapter03.ambiguity; 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; @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Cold { } package chapter03.ambiguity; 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; @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Creamy { } package chapter03.ambiguity; 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; @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Fruity { }注意事项:这3个注解在定义时都添加了@Qualifier注解,因此它们具有了@Qualifier注解的特性
然后将IceCream类修改为:
package chapter03.ambiguity; import org.springframework.stereotype.Component; @Component @Cold @Creamy public class IceCream implements Dessert { @Override public void showName() { System.out.println("冰激凌"); } }将Popsicle类修改为:
package chapter03.ambiguity; import org.springframework.stereotype.Component; @Component @Cold @Fruity public class Popsicle implements Dessert { @Override public void showName() { System.out.println("棒冰"); } }最后,修改下注入地方的代码,使其只能匹配到1个满足条件的bean,如下所示:
@Autowired @Cold @Creamy public void setDessert(Dessert dessert) { this.dessert = dessert; }运行测试代码,输出结果如下所示:
冰激凌
由此,我们也可以发现,自定义注解与@Qualifier注解相比,有以下2个优点:
可以同时使用多个自定义注解,但@Qualifier注解只能使用1个
使用自定义注解比@Qualifier注解更为类型安全
4. 源码及参考源码地址:https://github.com/zwwhnly/spring-action.git,欢迎下载。
Craig Walls 《Spring实战(第4版)》
5. 最后打个小广告,欢迎扫码关注微信公众号:「申城异乡人」,定期分享Java技术干货,让我们一起进步。