• Spring入门(八):自动装配的歧义性


    1. 什么是自动装配的歧义性?

    在Spring中,装配bean有以下3种方式:

    1. 自动装配
    2. Java配置
    3. xml配置

    在这3种方式中,自动装配为我们带来了很大的便利,大大的降低了我们需要手动装配bean的代码量。

    不过,自动装配也不是万能的,因为仅有一个bean匹配条件时,Spring才能实现自动装配,如果出现不止1个bean匹配条件时,Spring就会不知道要装配哪个bean,抛出org.springframework.beans.factory.NoUniqueBeanDefinitionException异常,这就是自动装配的歧义性。

    为了方便理解,我们举个具体的例子。

    首先,我们新建个接口Dessert,该接口仅有1个方法showName():

    package chapter03.ambiguity;
    
    public interface Dessert {
        void showName();
    }
    

    然后定义3个该接口的实现类Cake,Cookies,IceCream:

    package chapter03.ambiguity;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class Cake implements Dessert {
        @Override
        public void showName() {
            System.out.println("蛋糕");
        }
    }
    
    package chapter03.ambiguity;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class Cookies implements Dessert {
        @Override
        public void showName() {
            System.out.println("饼干");
        }
    }
    
    package chapter03.ambiguity;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class IceCream implements Dessert {
        @Override
        public void showName() {
            System.out.println("冰激凌");
        }
    }
    

    然后新建甜点店类DessertShop,该类的setDessert()方法需要装配1个Dessert的实例bean:

    package chapter03.ambiguity;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class DessertShop {
        private Dessert dessert;
    
        public Dessert getDessert() {
            return dessert;
        }
    
        @Autowired
        public void setDessert(Dessert dessert) {
            this.dessert = dessert;
        }
    
        public void showDessertName() {
            this.dessert.showName();
        }
    }
    

    不过现在符合装配条件的有3个bean,它们的bean ID(默认情况下是类名首字母小写)分别为cake,cookies,iceCream,Spring该自动装配哪个呢?

    带着这个疑问,我们先新建配置类AmbiguityConfig:

    package chapter03.ambiguity;
    
    import org.springframework.context.annotation.ComponentScan;
    
    @ComponentScan
    public class AmbiguityConfig {
    }
    

    这个类的关键是添加了@ComponentScan注解,让Spring自动扫描已经定义好的bean。

    最后,新建类Main,在其main()方法中添加如下测试代码:

    package chapter03.ambiguity;
    
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class Main {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AmbiguityConfig.class);
    
            DessertShop dessertShop = context.getBean(DessertShop.class);
            dessertShop.showDessertName();
    
            context.close();
        }
    }
    

    运行代码,发现抛出org.springframework.beans.factory.NoUniqueBeanDefinitionException异常,如下所示:

    那么如何解决自动装配的歧义性呢?Spring提供了以下2种方案:

    1. 标记首选的bean
    2. 使用限定符

    2. 标记首选的bean

    既然现在有3个匹配条件的bean,我们可以通过@Primary注解标记下哪个是首选的bean,这样当Spring发现有不止1个匹配条件的bean时,就会选择这个首选的bean。

    比如3种甜点里,我最喜欢吃饼干,那么我就把Cookies标记为首选的bean:

    package chapter03.ambiguity;
    
    import org.springframework.context.annotation.Primary;
    import org.springframework.stereotype.Component;
    
    @Component
    @Primary
    public class Cookies implements Dessert {
        @Override
        public void showName() {
            System.out.println("饼干");
        }
    }
    

    再次运行测试代码,输出结果如下所示:

    饼干

    圆满解决了歧义性的问题,不过有一天,有个同事不小心在IceCream上也添加了@Primary注解:

    package chapter03.ambiguity;
    
    import org.springframework.context.annotation.Primary;
    import org.springframework.stereotype.Component;
    
    @Component
    @Primary
    public class IceCream implements Dessert {
        @Override
        public void showName() {
            System.out.println("冰激凌");
        }
    }
    

    编译都正常,因此都没注意,但发布后运行时,却抛出如下异常:

    意思就是发现了不止1个首选的bean,因为此时Spring又不知道该选择哪个了,也就是有了新的歧义性,所以甩锅抛出了异常。

    3. 使用限定符

    3.1 基于bean ID的限定符

    Spring还提供了另一个注解@Qualifier注解来解决自动装配的歧义性,它可以与@Autowired或者@Inject一起使用,在注入的时候指定想要注入哪个bean。

    比如,我们把IceCream注入到setDessert()的方法参数之中:

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

    这里传递的iceCream指的是IceCream类默认生成的bean ID。

    再次运行测试代码,输出结果如下所示:

    冰激凌

    我们可以发现,使用了@Qualifier注解后,我们之前标记的@Primary注解被忽略了,也就是说,@Qualifier注解的优先级比@Primary注解的优先级高。

    使用默认的限定符虽然解决了问题,不过可能会引入一些问题。比如我在重构代码时,将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个优点:

    1. 可以同时使用多个自定义注解,但@Qualifier注解只能使用1个
    2. 使用自定义注解比@Qualifier注解更为类型安全

    4. 源码及参考

    源码地址:https://github.com/zwwhnly/spring-action.git,欢迎下载。

    Craig Walls 《Spring实战(第4版)》

    原创不易,如果觉得文章能学到东西的话,欢迎点个赞、评个论、关个注,这是我坚持写作的最大动力。

    如果有兴趣,欢迎添加我的微信:zwwhnly,等你来聊技术、职场、工作等话题(PS:我是一名奋斗在上海的程序员)。

  • 相关阅读:
    JSP application用法
    JSP到底内置了几大对象?
    ConcurrentHashMap之实现细节 5
    假如我是JAVA开发人员
    jBPM
    ServletContext与ServletConfig分析
    oracle建立索引原则
    70个新鲜实用的JavaScript和Ajax技术(上)
    ConcurrentHashMap之实现细节
    ConcurrentHashMap之实现细节3
  • 原文地址:https://www.cnblogs.com/zwwhnly/p/11355879.html
Copyright © 2020-2023  润新知