当前位置: 首页 > article >正文

spring @Autowired对属性、set方法,构造器的分别使用,以及配合 @Autowired 和 @Qualifier避免歧义性的综合使用案例

代码结构

在这里插入图片描述

依赖注入

在Spring IoC容器的概念中,主要是使用依赖注入来实现Bean之间的依赖关系的

举例

例如,人类(Person)有时候会利用动物(Animal)来完成一些事情,狗(Dog)是用来看门的,猫(Cat)是用来抓老鼠的,鹦鹉(Parrot)是用来迎客的……于是人类做一些事情就依赖于那些可爱的动物了,如图
在这里插入图片描述
为了更好地展现这个过程,首先定义两个接口,一个是人类接口(Person),另一个是动物接口(Animal)。人类是通过动物来提供一些特殊服务的,如下代码

*人类接口

package com.burns.test.test1;

/********人类接口********/
public interface Person {
    // 使用动物服务
    public void service();

    // 设置动物
    public void setAnimal(Animal animal);
}

动物接口

package com.burns.test.test1;

/********动物接口********/

public interface Animal {
    public void use();
}

这样我们就拥有了两个接口。我们还需要两个实现类,如代码

人类实现类

package com.burns.test.test1; /********人类实现类********/

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

/**
 * imports
 **/
@Component
public class BussinessPerson implements Person {
    @Autowired
    private Animal animal = null;

    @Override
    public void service() {
        this.animal.use();
    }

    @Override
    public void setAnimal(Animal animal) {
        this.animal = animal;
    }
}

狗——动物实现类

package com.burns.test.test1; /********狗——动物实现类********/

import org.springframework.stereotype.Component;

/**
 * imports
 **/
@Component
public class Dog implements Animal {
    @Override
    public void use() {
        System.out.println("狗【" + Dog.class.getSimpleName() + "】是看门用的。");
    }
}

注意,注解@Autowired也是Spring中最常用的注解之一,十分重要,它会按属性的类型找到对应的Bean进行注入。狗是动物的一种,所以IoC容器会把Dog实例注入BussinessPerson实例中。这样通过IoC容器获取BussinessPerson实例的时候就能够使用Dog实例来提供服务了,下面是测试的代码

配置类-AppConfig

package com.burns.test.test1;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class AppConfig {
}

测试入口类-MainTest

package com.burns.test.test1;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainTest {
    public static void main(String[] args) {
        // 使用配置文件AppConfig.java创建IoC容器
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        try {
            Person person = ctx.getBean(BussinessPerson.class);
            person.service();
        } finally {
            // 关闭IoC容器
            ctx.close();
        }
    }
}

执行结果

狗【Dog】是看门用的。

15:47:09.330 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext -- Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6108b2d7
15:47:09.355 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
15:47:09.434 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner -- Identified candidate component class: file [D:\workspace\idea_proj\springboot3-demo\target\classes\com\burns\test\test1\BussinessPerson.class]
15:47:09.435 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner -- Identified candidate component class: file [D:\workspace\idea_proj\springboot3-demo\target\classes\com\burns\test\test1\Dog.class]
15:47:09.613 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
15:47:09.619 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
15:47:09.622 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
15:47:09.626 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
15:47:09.638 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'appConfig'
15:47:09.647 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'bussinessPerson'
15:47:09.664 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'dog'
狗【Dog】是看门用的。
15:47:09.707 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext -- Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@6108b2d7, started on Thu Mar 27 15:47:09 CST 2025

显然,测试是成功的,这个时候IoC容器已经通过注解@Autowired成功地将Dog实例注入了BussinessPerson实例中。但是,这只是一个比较简单的例子,我们有必要继续探讨@Autowired

注解@Autowired

@Autowired是Spring中使用得最多的注解之一,在本节中需要进一步地进行探讨。@Autowired的注入策略中最基本的一条是按类型,我们回顾IoC容器的顶级接口BeanFactory,就可以知道IoC容器是通过getBean()方法获取对应的Bean的,而getBean()方法又支持按名称或者按类型。再回到上面的例子,我们只是创建了一个动物——狗,而实际上动物还可以有猫(Cat),猫可以为我们抓老鼠,于是我们又创建了一个猫类,如代码

猫类-Cat

package com.burns.test.test1;

import org.springframework.stereotype.Component;

@Component
public class Cat implements Animal {
    @Override
    public void use() {
        System.out.println("猫【" + Cat.class.getSimpleName() + "】是抓老鼠。");
    }
}

猫类创建完成了。如果我们还使用代码的BussinessPerson类,那么麻烦就来了,因为这个类只是定义了一个动物(Animal)属性,而我们却有两个动物——一只狗和一只猫,IoC容器如何注入呢?如果重新进行测试,很快就可以看到IoC容器抛出异常,如下面的日志所示:

No qualifying bean of type 'com.burns.test.test1.Animal' available: expected single matching bean but found 2: cat,dog
15:54:50.783 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext -- Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6108b2d7
15:54:50.809 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
15:54:50.889 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner -- Identified candidate component class: file [D:\workspace\idea_proj\springboot3-demo\target\classes\com\burns\test\test1\BussinessPerson.class]
15:54:50.890 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner -- Identified candidate component class: file [D:\workspace\idea_proj\springboot3-demo\target\classes\com\burns\test\test1\Cat.class]
15:54:50.891 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner -- Identified candidate component class: file [D:\workspace\idea_proj\springboot3-demo\target\classes\com\burns\test\test1\Dog.class]
15:54:51.074 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
15:54:51.080 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
15:54:51.082 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
15:54:51.086 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
15:54:51.105 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'appConfig'
15:54:51.114 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'bussinessPerson'
15:54:51.143 [main] WARN org.springframework.context.annotation.AnnotationConfigApplicationContext -- Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bussinessPerson': Unsatisfied dependency expressed through field 'animal': No qualifying bean of type 'com.burns.test.test1.Animal' available: expected single matching bean but found 2: cat,dog
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bussinessPerson': Unsatisfied dependency expressed through field 'animal': No qualifying bean of type 'com.burns.test.test1.Animal' available: expected single matching bean but found 2: cat,dog
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:767)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:747)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:145)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:492)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1416)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:597)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:973)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:950)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:616)
	at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:93)
	at com.burns.test.test1.MainTest.main(MainTest.java:8)
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.burns.test.test1.Animal' available: expected single matching bean but found 2: cat,dog
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:218)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1395)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:764)
	... 15 more

从日志可以看出,IoC容器并不能知道你需要向BussinessPerson类对象注入什么动物(狗?猫?),从而引发错误。那么使用@Autowired能处理这个问题吗?答案是肯定的。假设我们目前需要让狗提供服务,可以把属性名称转化为dog,也就是把原来的

@Autowired
private Animal animal = null;
修改为
@Autowired
private Animal dog = null;
package com.burns.test.test1; /********人类实现类********/

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

/**
 * imports
 **/
@Component
public class BussinessPerson implements Person {
    @Autowired
    private Animal dog = null;

    @Override
    public void service() {
        this.dog.use();
    }

    @Override
    public void setDog(Animal dog) {
        this.dog = dog;
    }
}

在上述代码中,我们只是将属性的名称从animal修改为了dog,再次测试时,可以看到是采用狗来提供服务的。这是因为Autowired提供以下规则:按类型找到对应的Bean,如果对应类型的Bean不是唯一的,那么它会根据其属性名称和Bean的名称进行匹配。如果匹配得上,就会使用该Bean;如果还无法匹配,就会抛出异常。

15:59:29.526 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'cat'
狗【Dog】是看门用的。
15:59:29.572 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext -- Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@6108b2d7, started on Thu Mar 27 15:59:29 CST 2025

注意,@Autowired是一个默认必须找到对应Bean的注解,如果不能确定其标注属性一定会存在并且允许这个被标注的属性为null,那么可以配置@Autowired的required属性为false,例如:

@Autowired(required = false)

当然,在大部分情况下,我都不推荐这样做,因为这样极其容易抛出“臭名昭著”的空指针异常(NullPointerException)。同样,它除了可以标注属性,还可以标注方法,如setAnimal()方法

@Override
@Autowired
public void setAnimal(Animal animal) {
this.animal = animal;
}

注意,新版的Spring规范建议将@Autowired标注在setter()方法上。本书只是为了节省篇幅,将@Autowired标注在类的属性上。这样@Autowired也会使用setAnimal()方法从IoC容器中找到对应的动物进行注入 ,甚至我们还可以将@Autowired标注在方法的参数上

消除歧义性——@Primary和@Qualifier

歧义性问题

当既有猫又有狗的时候,为了使@Autowired能够继续使用,我们做了一个决定:将BussinessPerson的属性名称从animal修改为dog。显然这不是一个好的做法,因为这里并不限制使用什么动物,而声明的属性名称却成了狗。产生注入失败问题的根本原因是按类型查找,正如动物可以有多种类型,这会造成IoC容器依赖注入的困扰,我们把这样的问题称为歧义性。那么,@Primary和@Qualifier这两个注解是从哪个角度解决问题的呢?

@Primary

先来谈@Primary,它是一个修改优先权的注解,当既有猫、又有狗的时候,假设这次需要使用猫,那么只需要在猫类的定义上加入@Primary就可以了,类似下面这样:

package com.burns.test.test1;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Component
@Primary
public class Cat implements Animal {
    @Override
    public void use() {
        System.out.println("猫【" + Cat.class.getSimpleName() + "】是抓老鼠。");
    }
}

在上述代码中,@Primary告诉IoC容器:“当发现有多个同样类型的Bean时,请优先使用我进行注入。”于是再次进行测试时会发现,系统将用猫提供服务。当Spring进行注入的时候,虽然发现存在多个动物,但因为Cat被标注为@Primary,所以优先采用Cat实例进行注入,这样就通过优先级变换使得IoC容器知道注入哪个具体的实例来满足依赖注入。

16:09:28.346 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'dog'
猫【Cat】是抓老鼠。
16:09:28.397 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext -- Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@6108b2d7, started on Thu Mar 27 16:09:27 CST 2025

@Qualifier

有时候@Primary也可以使用在多个类上,无论是猫是狗都可能带上注解@Primary,其结果是IoC容器还是无法区分采用哪个Bean的实例进行注入,因此我们需要更加灵活的机制来实现注入,Qualifier可以满足这个需求。@Qualifier的配置项value需要用一个字符串定义,它将与@Autowired组合在一起,通过名称和类型一起找到Bean。我们知道Bean名称在IoC容器中是唯一的标识,利用它就可以消除歧义性了。此时你是否想起了BeanFactory接口中的这个getBean()方法呢? T getBean(String name, lass requiredType) throws BeansException;使用getBean()方法就能够按名称和类型的组合找到Bean了。下面假设猫已经标注了Primary,而我们需要狗提供服务,因此需要修改ussinessPerson的animal属性的标注,以适应我们的需要:

package com.burns.test.test1; /********人类实现类********/

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

/**
 * imports
 **/
@Component
public class BussinessPerson implements Person {
    @Autowired
    @Qualifier("dog")
    private Animal animal = null;

    @Override
    public void service() {
        this.animal.use();
    }

    @Override
    public void setAnimal(Animal animal) {
        this.animal = animal;
    }
}

一旦这样声明,IoC容器将会按名称和类型来寻找对应的Bean进行依赖注入,显然也只能找到狗为我们服务了

带有参数的构造方法类的装配

上述场景都是在不带参数的构造方法下实现依赖注入。但事实上,有些类只有带有参数的构造方法,于是上述场景都不再适用了。为了对构造方法的参数进行依赖注入,我们可以使用注解Autowired。例如,修改类BussinessPerson来实现这个功能,如代码

package com.burns.test.test2;

import com.burns.test.test1.Animal;
import com.burns.test.test1.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

/******** imports ********/
@Component
public class BussinessPerson implements Person {
    private Animal animal = null;

    public BussinessPerson(@Autowired @Qualifier("dog") Animal animal) {
        this.animal = animal;
    }

    @Override
    public void service() {
        this.animal.use();
    }

    @Override
    public void setAnimal(Animal animal) {
        this.animal = animal;
    }
}

代码取消了注解@Autowired对属性和方法的标注。注意,加粗的代码在方法的参数上加入了注解@Autowired和注解@Qualifier,使得参数能够注入进来。这里使用@Qualifier是为了按名称进行依赖注入,避免歧义性。当然,如果你的环境中不是既有猫、又有狗,完全可以不使用@Qualifier,只使用@Autowired就可以了。


http://www.kler.cn/a/612459.html

相关文章:

  • 通过 ECNWP 洋流、海浪可视化开发的方法和架构
  • 【NLP 48、大语言模型的神秘力量 —— ICL:in context learning】
  • Windows10清理机器大全集
  • 软件功能性测试工具有哪些?专业软件测试服务推荐
  • Linux下EC11旋转编码器驱动调试
  • 16、Python继承与多态机制深度解析
  • 【linux指令】一文掌握 Linux 基础命令(Linux 命令速查)
  • STM32F103_LL库+寄存器学习笔记07 - 串口接收缓冲区非空中断
  • 一文速通Python并行计算:01 Python多线程编程-基本概念、切换流程、GIL锁机制和生产者与消费者模型
  • C++11大数加减
  • MyBatis-Plus LambdaQueryWrapper 详解:优雅构建类型安全的查询条件
  • Python Web 框架 Django、Flask 和 FastAPI 对比
  • 5G核心网(5GC)开户中,DNN(Data Network Name,数据网络名称)
  • 【Android】SharedMemory获取文件描述符
  • 【Python】天气数据可视化
  • Hyperlane 似乎是一个轻量级、高性能的 Rust HTTP 服务器库
  • 弱电系统:从基础原理到家庭与小区网络部署
  • C语言 二维线性查表linearInterpolation 100行实现通用线性查表
  • 深入理解Golang标准库`testing/fstest`包的用法和技巧进行文件系统测试
  • linux之 内存管理(5)-CMA 连续内存管理器