【Spring】一问详解什么是Spring IoC和DI

目录

  • 一、IoC & DI入门
    • 1.1、Spring
      • 1.1.1、什么是容器
      • 1.1.2、什么是IoC
    • 1.2、IoC介绍
      • 1.2.1、传统程序开发
      • 1.2.2、问题分析
      • 1.2.3、问题解决
      • 1.2.4、 IoC优势
    • 1.3、Bean的作用域
    • 1.4、DI介绍
  • 二、IoC详解
    • 2.1、Bean的存储
      • 2.1.1、类注解的使用
      • 2.1.2、获取bean对象的其他方式
      • 2.1.3、Bean命名约定
    • 2.2、为什么要这么多类注解?
      • 2.2.1、类注解之间的关系
    • 2.3、方法注解@Bean
      • 2.3.1、方法注解需要配合类注解使用
      • 2.3.2、定义多个对象
      • 2.3.3、重命名Bean
    • 2.4、扫描路径
  • 三、DI详解
    • 3.1、属性注入
    • 3.2、构造方法注入
    • 3.3、Setter注入
    • 3.4、@Autowired存在问题
  • 四、总结

一、IoC & DI入门

1.1、Spring

通过前面的学习, 我们知道了Spring是一个开源框架, 它让我们的开发更加简单. 它支持广泛的应用场景, 有着活跃且庞大的社区, 这就是Spring能够长久不衰的原因.

但是这个概念还是比较抽象.

可以用更具体的话描述Spring, 那就是: Spring是包含了众多工具方法的IoC容器.

那问题来力, 什么是容器? 什么是IoC容器?

1.1.1、什么是容器

容器是用来容纳某种物品的(基本)装置.

我们想想, 之前接触的容器有哪些?

List/Map -> 数据存储容器

Tomcat -> Web容器

1.1.2、什么是IoC

IoC是Spring的核心思想, 也是常见的面试题, 那什么是IoC呢?

IoC我们已经使用了, 我们在前面讲到, 在类上面添加@RestController@Controller注解,就是把这个对象交给Spring管理, Spring框架启动时就会加载该类. 把对象交给Spring管理, 就是IoC思想.

IoC: Inversion of Control(控制反转), 也就是说Spring是一个"控制反转"的容器.

什么是控制反转呢?也就是控制权反转. 什么的控制权发生了反转? 获得依赖对象的过程被反转了. 也就是说, 当需要某个对象时,
传统开发模式中只需要自己通过new创建对象, 现在不需要再进行创建, 把创建对象的任务交给容器, 程序中只需要依赖注入就可以了.
这个容器称为: IoC容器. Spring是一个IoC容器, 所以有时Spring也称为Spring容器.

控制反转是一种思想, 在生活中也处处体现.

当人们斗地主时, 如果手里只剩下王炸, 可以不用管了, 整个托管即可.

在自动驾驶中, 驾驶员可以掌握驾驶的控制权, 也可以将这个控制权交给自动化驾驶系统.

1.2、IoC介绍

接下来我们通过案例来了解一下什么是IoC.

需求: 造一辆车

1.2.1、传统程序开发

我们的实现思路是这样的:

先设计轮子(Tire), 然后根据轮子的大小设计出底盘(Bottom), 接着根据底盘的设计车身(Framework), 最后根据车身设计好整辆汽车(Car). 这里就出现了一个"依赖"关系: 汽车依赖车身, 车身依赖底盘, 底盘依赖轮子.

在这里插入图片描述

最终实现的代码如下:

/**
 * @author hanson
 * @date 2024/4/8 19:16
 */
public class NewCarExample {
    public static void main(String[] args) {
        Car car = new Car();
        car.run();
    }


    /**
     * 汽车对象
     */
    static class Car {
        private FrameWork frameWork;

        public Car() {
            this.frameWork = new FrameWork();

            System.out.println("Car init...");
        }

        public void run() {
            System.out.println("Car run...");
        }
    }


    /**
     * 车身类
     */
    static class FrameWork {
        private Bottom bottom;

        public FrameWork() {
            this.bottom = new Bottom();

            System.out.println("Frame init ...");
        }
    }

    /**
     * 底盘类
     */
    static class Bottom {
        private Tire tire;

        public Bottom() {
            this.tire = new Tire();

            System.out.println("Bottom init ...");
        }
    }

    /**
     * 轮胎类
     */
    static class Tire {
        // 尺寸
        private int size;

        public Tire() {
            this.size = 17;
            System.out.println("轮胎的尺寸:" + size);
        }
    }
}

运行结果:

在这里插入图片描述

1.2.2、问题分析

这样的设计看起来没问题, 但是可维护性却很低.

接下来需求有了变更: 随着对车的需求量越来越大, 个性化需求也越来越多, 我们需要加工多种尺寸的轮胎.

那这个时候就要对上面的程序进行修改了, 修改后的代码如下:

/**
 * 轮胎类
 */
static class Tire {
    // 尺寸
    private int size;

//        public Tire() {
//            this.size = 17;
//            System.out.println("轮胎的尺寸:" + size);
//        }

    public Tire(int size) {
        this.size = size;
        System.out.println("轮胎的尺寸:" + size);
    }
}

修改之后, 其它调用程序也会报错, 我们需要修改继续修改(即每一个构造方法都要传一个size)

完整代码如下:

public class NewCarExample {
    public static void main(String[] args) {
        Car car = new Car(20);
        car.run();
    }
 
    /**
     * 汽车对象
     */
    static class Car {
        private FrameWork frameWork;
 
        public Car(int size) {
            frameWork = new FrameWork(size);
            System.out.println("Car init...");
        }
 
        public void run() {
            System.out.println("Car run...");
        }
    }
 
    /**
     * 车身类
     */
    static class FrameWork {
        private Bottom bottom;
 
        public FrameWork(int size) {
            this.bottom = new Bottom(size);
            System.out.println("Frame init...");
        }
    }
 
    /**
     * 底盘类
     */
    static class Bottom {
        private Tire tire;
 
        public Bottom(int size) {
            this.tire = new Tire(size);
            System.out.println("Bottom init...");
        }
    }
 
    /**
     * 轮胎类
     */
    static class Tire {
        //尺寸
        private int size;
 
        public Tire(int size) {
            this.size = size;
            System.out.println("轮胎尺寸: " + size);
        }
    }
}

从以上代码可以看出, 以上程序的问题是: 当最底层代码改动之后, 整个调用链上的所有代码都需要修改.

程序的耦合度非常高(修改一处代码, 影响其它处代码的修改).

1.2.3、问题解决

在上面的程序当中, 我们是根据轮子的尺寸设计底盘, 轮子尺寸一改, 底盘的设计就得修改. 同样因为我们是根据底盘设计的车身, 那么车身也得修改, 同理汽车设计也得修改, 也就是整个设计都会改.

我们尝试换一种思路, 我们先设计汽车的大概样子, 然后根据汽车的样子来设计车身, 根据车身来设计底盘, 最后根据底盘来设计轮子, 这时, 依赖关系就倒置过来了: 轮子依赖底盘, 底盘依赖车身, 车身依赖汽车.

在这里插入图片描述
如何来实现呢?

我们可以尝试不在每个类中创建自己的下级类, 如果自己创建下级类就会出现下级类改变操作, 自己也要跟着修改.

此时, 我们只需要将原来由自己创建的下级类, 改为传递的方式(也就是注入的方式), 因为我们不需要在当前类中创建下级类了, 所以下级类即使发生变化(创建或者减少参数), 当前类不用再改变代码了, 这就实现了程序的解耦.

/**
 * @author hanson
 * @date 2024/4/8 19:16
 */
public class NewCarExample1 {
    public static void main(String[] args) {
        Tire tire = new Tire(20);
        Bottom bottom = new Bottom(tire);
        FrameWork frameWork = new FrameWork(bottom);
        Car car = new Car(frameWork);
        car.run();
    }


    /**
     * 汽车对象
     */
    static class Car {
        private FrameWork frameWork;

        public Car(FrameWork frameWork) {
            this.frameWork = frameWork;

            System.out.println("Car init...");
        }

        public void run() {
            System.out.println("Car run...");
        }
    }


    /**
     * 车身类
     */
    static class FrameWork {
        private Bottom bottom;

        public FrameWork(Bottom bottom) {
            this.bottom = bottom;

            System.out.println("Frame init ...");
        }
    }

    /**
     * 底盘类
     */
    static class Bottom {
        private Tire tire;

        public Bottom(Tire tire) {
            this.tire = tire;

            System.out.println("Bottom init ...");
        }
    }

    /**
     * 轮胎类
     */
    static class Tire {
        // 尺寸
        private int size;

//        public Tire() {
//            this.size = 17;
//            System.out.println("轮胎的尺寸:" + size);
//        }

        public Tire(int size) {
            this.size = size;
            System.out.println("轮胎的尺寸:" + size);
        }
    }
}

代码通过以上调整, 无论底层类如何变化, 整个调用类是不用做任何改变的, 这样就实现了代码之间的解耦, 从而实现更加灵活, 通用的程序设计了.

1.2.4、 IoC优势

在传统的代码中对象的创建对象的顺序是: Car -> FrameWork -> Bottom -> Tire

改进之后解耦的代码的对象的创建顺序是: Tire -> Bottom -> FrameWork -> Car

在这里插入图片描述

我们发现一个规律, 通用程序的实现代码, 类的创建顺序是反的, 传统代码是Car控制并创建了FrameWork, 依次向下,
而改进之后的控制权发生了反转, 不再是使用方对象创建并控制依赖对象了, 而是把依赖对象注入到当前对象中,
依赖对象的控制权不再由当前类控制了.

因此即使依赖类发生任何改变, 当前类都是不受影响的, 这就是典型的控制反转, 也就是IoC的实现思想.

学到这里, 我们就大概知道什么是控制反转了, 那什么是控制反转容器呢,也就是IoC容器.

在这里插入图片描述

这部分代码就是IoC容器所做的工作.

从上面也可以看出, IoC具有以下优点:

资源不再由资源的双方管理, 而由不使用资源的第三方管理, 这可以带来很多好处.

第一: 资源的集中管理, 实现资源的可配置和易管理, 用的时候只需要从IoC容器中取即可.

第二:降低了使用资源双方的依赖程度, 也就是我们说的耦合度.

1.3、Bean的作用域

Spring Bean支持五种作用域,后三种在web环境下才生效:

作用域说明
singleton容器内同名称的bean只有一个实例(默认)
prototype每次请求该bean时会创建新的实例(非单例)
request每个请求范围内会创建新的实例(web环境中,了解)
session每个会话范围内会创建新的实例(web环境中,了解)
application每个应用范围内会创建新的实例(web环境中,了解)

配置Bean的作用域需要加上下面这个注解
@Scope

首先测试单例模式

@Scope("singleton")
@Controller  //将对象存储到Spring中
public class MyController1 {
    public void sayHi(){
        System.out.println("Hi, UserController...");
    }
}
@SpringBootApplication
public class SpringIocDiApplication {

    public static void main(String[] args) {
        //获取Spring上下文对象
        ApplicationContext context = SpringApplication.run(SpringIocDiApplication.class, args);

        for (int i = 0; i < 10; i++) {
            MyController1 myController1 = context.getBean(MyController1.class);
            myController1.sayHi();
            System.out.println(myController1);
        }
    }
}

运行后发现
在这里插入图片描述
我们调用10次getBean方法,得到的始终是一个对象。

现在将singleton变成prototype再次测试

@Scope("prototype")
@Controller  //将对象存储到Spring中
public class MyController1 {
    public void sayHi(){
        System.out.println("Hi, UserController...");
    }
}

在这里插入图片描述
我们调用10次getBean方法,得到了10个bean对象

🎈注意

  • 默认singleton的bean,在容器启动的时候被创建,可以使用@Lazy注解来延迟初始化(延迟到第一次使用时)
  • prototype的bean,每一次使用该bean的时候都会创建一个新的实例
  • 实际开发中,大多数Bean的单例的,也就是说大部分bean不需要配置scope属性

1.4、DI介绍

DI:Dependency Injection(依赖注入).

容器在运行期间, 动态的为应用程序提供运行时所依赖的资源, 称之为依赖注入.

程序运行时需要某个资源, 容器就可以提供这个资源.

从这点来看, IoC(控制反转)和DI(依赖注入)是从不同角度描述同一件事情, 就是指通过引入IoC容器, 利用依赖关系注入的方式,
实现对象之间的解耦.

之前的代码中, 就是通过构造函数的方式, 将依赖的对象注入到需使用对象中.

在这里插入图片描述

DI是IoC的一种实现.

二、IoC详解

通过上面的案例, 我们已经知道了IoCDI的基本操作, 接下来我们来系统地学习Spring IoC和DI的操作.

前面我们提到的IoC控制反转, 就是将对象的控制权交给Spring的IoC容器, 由IoC容器创建及管理对象. (也就是Bean的存储).

2.1、Bean的存储

我们之前只讲到了@Component注解来使得对象交给IoC容器管理. 而Spring为了更好地管理Web应用程序, 提供了更丰富的注解.

当前有两类注解:

1.类注解@Controller(控制器存储), @Service(服务存储), @Reposity(仓库), @Component(组件), @Configuration(配置)
2.方法注解: @Bean

2.1.1、类注解的使用

由于这里五个类注解在功能上基本是一致的, 所以这里用@Controller进行介绍.

使用@Controller存储bean的代码如下所示:

@Controller  //将对象存储到Spring中
public class MyController{
    public void sayHi(){
        System.out.println("Hi, UserController...");
    }
}

如何观察这个对象已经存在Spring容器当中了呢?

接下来我们学习如何从Spring容器中获取对象.


@SpringBootApplication
public class SpringIocDiApplication {

    public static void main(String[] args) {
        //获取Spring上下文对象
        ApplicationContext context = SpringApplication.run(SpringIocDiApplication.class, args);
        //从Spring上下文中获取对象
        MyController myController = context.getBean(MyController.class);
        //获取对象
        myController.sayHi();
    }
}

ApplicationContext 翻译过来就是: Spring上下文.

因为对象交给Spring管理, 所以获取对象要从Spring中获取, 那么就得先得到Spring的上下文.

关于上下文的概念

在计算机领域, 上下文这个概念, 它是指支持进程调度的重要属性.
等下次调度回CPU时,会把寄存器上的回复回来.

这里的上下文, 就是指当前的运行环境, 也可以看作是一个容器, 容器里存很多内容, 这些内容是当前运行的环境.

观察运行结果, 发现成功从Spring中获取到Controller对象, 并执行Controller的SayHi方法.

在这里插入图片描述

2.1.2、获取bean对象的其他方式

上述代码是根据类型来查找对象, 如果Spring容器中, 同一个类型存在多个bean的话, 怎么获取呢?

ApplicationContext也提供了其它获取bean的方式, ApplicationContext获取bean对象的功能, 是父类BeanFactory提供的功能.

在这里插入图片描述

可以发现, 我们获取bean共有五种方法, 而常用的是第1, 2, 4三种. 第一种是根据名称获取bean对象, 第二种是通过名称, 类型获取bean对象. 第三种是通过类型获取对象.

其中1,2种都涉及根据名称来获取对象, bean的名称是什么呢?

Spring bean是Spring框架在运行时管理的对象, Spring会给管理的对象起一个名字.

比如学校管理学生, 会给每个学生分配一个学号, 根据学号, 可以找到对应学生.

Spring也是如此, 给每个对象起一个名字, 根据Bean名称(BeanId)就可以获取到对应对象.

2.1.3、Bean命名约定

程序开发人员不需要为bean指定名称(BeanId), 如果没有显式提供名称(BeanId), Spring容器将为该bean生成唯一的名称.

命名约定使用Java标准约定作为实例字段名, 也就是说bean名称以小写字母开头, 然后使用驼峰时大小写.

eg. MyController -> myController

也有一些特殊情况, 当有多个字符且第一个和第二个字符都大写时, 将保留原始的大小写.

eg. UController -> UController

根据这个规则, 我们来获取Bean.

@SpringBootApplication
public class SpringIocDiApplication {

    public static void main(String[] args) {
        //获取Spring上下文对象
        ApplicationContext context = SpringApplication.run(SpringIocDiApplication.class, args);
        //根据bean类型, 从Spring上下文中获取对象.
        MyController myController1 = context.getBean(MyController.class);
        //根据bean名称, 从Spring上下文中获取对象
        MyController myController2 = (MyController) context.getBean("myController");
        //根据bean名称+对象, 从Spring上下文中获取对象
        MyController myController3 = context.getBean("myController", MyController.class);
        //使用对象
        System.out.println(myController1);
        System.out.println(myController2);
        System.out.println(myController3);
    }

}

运行结果:

在这里插入图片描述
地址一样, 说明对象是一个.

获取bean对象, 是父类BeanFactory提供的功能.

ApplicationContext VS BeanFactory (面试题)

继承关系和功能来说: Spring有两个顶级的接口: ApplicationContext 和BeanFactory. 其中

BeanFactory提供了基础的访问容器的能力, 而Application属于BeanFactory的子类.
它除了继承BeanFactory的所有功能之外, 还有独特的特性: 对国际化的支持, 资源访问支持, 以及事件传播方面的支持.

从性能来说:
ApplicationContext是一次性加载并初始化的所有Bean对象(可类似于饿汉模式),BeanFactory是需要哪个再去加载哪个,
因此更加轻量.(空间换时间) (一般建议用ApplicationContext, 因为现在机器的性能更高了).

2.2、为什么要这么多类注解?

这个也是和前面讲的应用分层相呼应. 让程序员看到注解之后, 就能知道这个类的用途.

@Controller: 控制层, 接收请求, 对请求进行处理, 并进行响应.

@Service: 业务逻辑层,处理具体的逻辑.

@Respository: 数据访问层, 也称为持久层. 负责数据的访问操作.

@Configuration: 配置层. 处理项目中的一些配置信息.

这个就类似于车牌号的功能, 一看开头, 不管后面的字符串是什么, 就能知道这个车是哪里的.

程序的应用分层, 调用逻辑如下:

在这里插入图片描述

2.2.1、类注解之间的关系

查看@Controller, @Configuration, @Repository, @Service的源码发现:

这些注解中其实都有一个元注解: @Component, 说明它们本身就属于@Component的"子类", 说明它们本身就是属于@Component的"子类". @Component是一个元注解, 也就是说可以注解其它类注解, 如@Controller, @Service, @Repository, 这些注解都可以说是@Component的衍生注解.

2.3、方法注解@Bean

类注解是添加到某个类上的, 但是存在两个问题:

1.使用外部包里的类, 没办法添加类注解

2.一个类, 需要多个对象, 比如多个数据源.

这种场景, 我们就需要注解@Bean

2.3.1、方法注解需要配合类注解使用

在Spring框架的设计中, 方法注解@Bean要配合类注解才能将对象正常地存储到Spring容器中.

举个栗子:

@Component
public class BeanConfig {

    @Bean
    public User user(){
        User user = new User();
        user.setName("Hanson");
        user.setAge(20);
        return user;
    }
}

运行下述代码:

@SpringBootApplication
public class SpringIocDiApplication {

    public static void main(String[] args) {
        //获取Spring上下文对象
        ApplicationContext context = SpringApplication.run(SpringIocDiApplication.class, args);
        User user = context.getBean(User.class);
        System.out.println(user);
    }
}

得到运行结果:

在这里插入图片描述

2.3.2、定义多个对象

对于同一个类, 如何定义多个对象呢?

比如多数据源的场景, 类是同一个, 但是配置不同, 指向不同的数据源.

我们看下@Bean的使用

@Component
public class BeanConfig {

    @Bean
    public User user1(){
        User user = new User();
        user.setName("Hanson1");
        user.setAge(20);
        return user;
    }    
    
    @Bean
    public User user2(){
        User user = new User();
        user.setName("Hanson2");
        user.setAge(20);
        return user;
    }
}

当定义到多个对象时, 我们继续使用上面的代码, 能获取到什么对象? 我们来运行一下:

在这里插入图片描述

报错信息显示:期望只有一个匹配, 结果却发现了两个: user1, user2.

从报错信息中, 可以看出来, @Bean注解的bean, bean名称就是它的方法名.

接下来以正确的方式来获取Bean对象.

public static void main(String[] args) {
        //获取Spring上下文对象
        ApplicationContext context = SpringApplication.run(SpringIocDiApplication.class, args);
        User user1 = (User) context.getBean("user1");
        User user2 = (User) context.getBean("user2");
        System.out.println(user1);
        System.out.println(user2);
    }

运行结果:

在这里插入图片描述

可以看到, @Bean针对同一个类, 定义多个对象.

2.3.3、重命名Bean

@Bean(name = {“u1”, “user1”})

添加类似的注解仍可以运行成功, 这是将user1重命名为u1的一种方式. 类似地, 还有如下方式:

@Bean({“u1”, “user1”})

//只有一个名称时, 其它的内容可以省略.

@Bean(“u1”)

2.4、扫描路径

使用前面注解声明的bean, 一定会生效吗?

不一定(原因: bean想要生效, 还需要被Spring扫描).

当在同一个包内, 可以直接被Spring扫描到, 如果不在同一个包, 就可以通过@ComponentScan来配置扫描路径.

@ComponentScan(value = "com.hanson.ioc.controller")
@SpringBootApplication
public class SpringIocDiApplication {

    public static void main(String[] args) {
        //获取Spring上下文对象
        ApplicationContext context = SpringApplication.run(SpringIocDiApplication.class, args);
		// 从上下文中获取对象
        User user = context.getBean("u1",User.class);
        System.out.println(user);
    }
}

也可以使用@ComponentScans配置多个包路径.

这种做法仅作了解, 不做推荐使用.

三、DI详解

接下来学习一下依赖注入DI的细节.

依赖注入是一个过程, 是指IoC容器在创建Bean时, 去提供运行时所依赖的资源, 而资源指的就是对象. 在之前的案例中, 使用了@Autowired这个注解, 完成了依赖注入这个操作.

简单来说, 就是把对象取出来放到某个类的属性中.

在一些文章中, 依赖注入也称为"对象注入", “属性装配”, 具体含义需要结合文章的上下文理解.

关于依赖注入, Spring提供了三种方式:

1.属性注入(Field Injection)

2.构造方法注入(Constructor Injection)

3.Setter注入(Setter Injection).

3.1、属性注入

属性注入通过@Autowired实现的, 这里将Service类注入到Controller类中.

@Service
public class MyService {
    public void sayHi() {
        System.out.println("Hi, MyService");
    }
}
@Controller //将对象存储到Spring中
public class MyController2 {
    //注入方法1: 属性注入
    @Autowired 
    private MyService myService;
 
    public void sayHi() {
        System.out.println("Hi, UserController...");
        myService.sayHi();
    }
}

使用:

@SpringBootApplication
public class SpringbootDemoApplication {
	public static void main(String[] args) {
		//获取Spring上下文对象
		ApplicationContext context = SpringApplication.run(SpringbootDemoApplication.class);
		MyController2 myController2 = context.getBean(MyController2.class);
		myController2.sayHi();
	}
}

最终运行结果如下:

在这里插入图片描述

3.2、构造方法注入

构造方法注入是在类的构造方法中实现注入, 如下所示:

@Controller //将对象存储到Spring中
public class MyController3 {
    private MyService myService;

    //注入方法2: 构造方法注入
    @Autowired
    public MyController3(MyService myService) {
        this.myService = myService;
    }

    public void sayHi() {
        System.out.println("Hi, UserController...");
        myService.sayHi();
    }
}

注意事项: 如果类中只有一个构造方法, 那么@Autowired注解可以省略(在Spring中, 如果一个类只有一个构造方法,
并且该构造方法不包含任何参数, 那么Spring在实例化这个类的时候会自动将其作为一个Bean注入到容器中); 如果类中有多个构造方法,
那么需要添加上@Autowired来明确指明到底使用哪个构造方法
.

3.3、Setter注入

Setter注入和属性的Setter方法实现类似, 只不过在设置set方法的时候需要加上@Autowired注解:

@Controller //将对象存储到Spring中
public class MyController4 {
    private MyService myService;
 
    //注入方法3: Setter方法注入
    @Autowired
    public void setMyService(MyService myService) {
        this.myService = myService;
    }
 
    public void sayHi() {
        System.out.println("Hi, UserController...");
        myService.sayHi();
    }
}

这里注意, 对于Setter方法, 是一定要写@Autowired的.

3.4、@Autowired存在问题

当同一类型存在多个bean时, 使用@Autowired会存在问题.

@Component
public class BeanConfig {
    @Bean("u1")
    public User user1() {
        User user = new User();
        user.setName("Hanson1");
        user.setAge(20);
        return user;
    }
 
    @Bean
    public User user2() {
        User user = new User();
        user.setName("Hanson2");
        user.setAge(18);
        return user;
    }
}
@Controller
public class MyController5 {
    @Autowired
    private User user;
 
    public void sayHi() {
        System.out.println("hi, UserController5...");
        System.out.println(user);
    }
}

在这里插入图片描述

报错的原因是, 非唯一的Bean对象.

如何解决上述问题呢? Spring提供了以下几种解决方案:

使用@Primary注解: 当存在多个相同类型的Bean注入时, 加上@Primary注解, 来确定默认的实现.

@Component
public class BeanConfig {
    @Primary // 指定该bean为默认的bean实现.
    @Bean("u1")
    public User user1() {
        User user = new User();
        user.setName("lisi");
        user.setAge(20);
        return user;
    }
 
    @Bean
    public User user2() {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }
}

使用@Qualifier注解: 指定要注入的bean对象. 在@Qualifiervalue属性中,指定注入bean的名称.

@Qualifier注解不能单独使用, 必须配合@Autowired使用.

@Controller
public class MyController6 {
    @Qualifier("user2") //指定bean的名称.
    @Autowired
    private User user;
 
    public void sayHi() {
        System.out.println("hi, UserController6...");
        System.out.println(user);
    }
}

使用@Resource注解: 是按照bean的方式注入. 通过name属性指定要注入的bean名称.

@Controller
public class MyController7 {
    @Resource(name = "user2")
    private User user;
 
    public void sayHi() {
        System.out.println("hi, UserController7...");
        System.out.println(user);
    }
}

常见面试题:

@Autowired和@Resource的区别

@Autowired是Spring框架提供的注解, 而@Resource是JDK提供的注解.(@Primary,@Qualifier是Spring提供的注解).

@Autowired默认是按照类型注入, 而@Resource是按名称注入. 相比于@Autowired来说,
@Resource支持更多的参数配置, 例如name设置, 通过name获取bean.

四、总结

在项目中,我们自定义一个类,如果我们想把这个类交给ioc容器管理,加上@Component衍生注解即可,
如果这个类不是我们自己自定义的,是我们引入第三方依赖中的,而且我们还想把这个类交给ioc容器管理,那么我们应该定义一个方法,在这个方法上加上@Bean

文章代码:GitHub

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/527286.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构】数组(稀疏矩阵、特殊矩阵压缩、矩阵存储、稀疏矩阵的快速转置、十字链表)

稀疏矩阵、矩阵压缩、稀疏矩阵的快速转置、十字链表 目录 稀疏矩阵、矩阵压缩、稀疏矩阵的快速转置、十字链表1.数组2.数组的顺序表示和实现3.特殊矩阵的压缩存储&#xff08;1&#xff09;. 上三角矩阵—列为主序压缩存储&#xff08;2&#xff09;. 下三角矩阵—**行为主序压…

uni-app项目创建方式

原生小程序与uni-app的区别 创建uni-app的方式 1.通过HBuilderX创建 2.通过命令行创建 vue3ts版&#xff1a;npx degit dcloudio/uni-preset-vue#vite-ts 项目名称 用vscode开发uni-app项目 安装命令&#xff1a;npm i -D types/wechat-miniprogram uni-helper/uni-app-typ…

HarmonyOS 应用开发-边缓存边播放案例

介绍 OhosVideoCache是一个支持边播放边缓存的库&#xff0c;只需要将音视频的url传递给OhosVideoCache处理之后再设置给播放器&#xff0c; OhosVideoCache就可以一边下载音视频数据并保存在本地&#xff0c;一边读取本地缓存返回给播放器&#xff0c;使用者无需进行其他操作…

【爬虫+数据清洗+可视化】用Python开发舆情分析文本挖掘“淄博烧烤“可视化大屏

先上效果截图&#xff1a; 动态演示效果&#xff1a; 【大屏演示】Python可视化舆情大屏「淄博烧烤」 主要用到的技术栈&#xff1a; requests 爬虫发送请求json 解析返回数据re 正则表达式清洗文本pandas保存csv文件sqlalchemy 保存MySQL数据pyecharts 可视化开发snownlp 情感…

17 - 微程序控制

---- 整理自B站UP主 踌躇月光 的视频 1. 实验目标 将 RAM 中 0 地址内容和 1 地址内容相加&#xff0c;结果存入 2 地址。 需要从 RAM 读取数据 需要寄存器暂存数据 需要从 ROM 中读取控制程序 3 4 > 7 2. 实验过程 【17 - 微程序控制】 2.1 改造 ALU 2.2 程序计数…

成都欣丰洪泰文化传媒有限公司电商服务的新锐力量

在当今电商行业风起云涌的时代&#xff0c;成都欣丰洪泰文化传媒有限公司以其独特的视角和专业的服务&#xff0c;成为了业内的佼佼者。该公司专注于电商服务&#xff0c;致力于为广大商家提供全方位、多层次的解决方案&#xff0c;助力商家在激烈的市场竞争中脱颖而出。 一、…

位置编码学习

基本概念 关于位置编码的一切&#xff1a;https://kexue.fm/archives/8130#T5%E5%BC%8F 残差连接 Post Norm 关注深度 残差的意思是给前面的层搞一条“绿色通道”&#xff0c;让梯度可以更直接地回传&#xff0c;但是在Post Norm中&#xff0c;这条“绿色通道”被严重削弱…

今日arXiv最热大模型论文:人民大学发布,拯救打工人!Office真实场景下的大模型表格处理

引言&#xff1a;大语言模型事实召回机制探索 该论文深入研究了基于Transformer的语言模型在零射击和少射击场景下的事实记忆任务机制。模型通过任务特定的注意力头部从语境中提取主题实体&#xff0c;并通过多层感知机回忆所需答案。作者提出了一种新的分析方法&#xff0c;可…

牛客 2024春招冲刺题单 ONT82 腐烂的苹果【中等 BFS Java,Go】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/54ab9865ce7a45968b126d6968a77f34 思路 广度优先搜索。首先找到2坐标集合&#xff0c;然后每次往四周为1的坐标扩展参考答案Java import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数…

RAG基础知识及应用

简单介绍下RAG的基础知识和RAG开源应用 “茴香豆" 一. RAG 基础知识 1. RAG工作原理 RAG是将向量数据库和大模型问答能力的有效结合&#xff0c;从而达到让大模型的知识能力增加的目的。首先将知识源存储在向量数据库中&#xff0c;当提出问题时&#xff0c;去向量数据库…

客户案例丨拓数派向量计算引擎PieCloudVector助力东吴证券AIGC应用升级

1.项目背景 随着人工智能技术的不断创新和应用&#xff0c;我们可以看到人工智能在各个领域的应用越来越广泛。深度学习技术在图像识别、语音识别、自然语言处理等领域表现出色。机器学习算法的改进将解决更多实际问题&#xff0c;如增强学习、迁移学习和联合学习等&#xff…

学生用什么品牌的台灯好,学生护眼台灯推荐品牌

在童年的岁月里&#xff0c;我们常常无知于如何正确地使用眼睛&#xff0c;对于何种光线最有益眼睛健康也缺乏了解。这些年轻时的疏忽&#xff0c;如今在我的眼镜度数上得到了反映&#xff0c;近视已接近千度&#xff0c;这是许多同样经历的视友都能共鸣的体验。随着时间的推移…

MoonBit 最新动态:MoonBit 引入实验性的测试覆盖率统计工具

MoonBit更新 支持 array.iter intrinsic 并且已经对标准库中的函数进行标注&#xff0c;从而可以在特定情况下将循环进行内联&#xff0c;以提升运行效率 /// intrinsic %array.iter pub fn iter[T](self : Array[T], f : (T) -> Unit) -> Unit {for i 0; i < self…

pymilvus创建IVF_FLAT向量索引

索引简介 索引的作用是加速大型数据集上的查询。 目前&#xff0c;向量字段仅支持一种索引类型&#xff0c;即只能创建一个索引。 milvus支持的向量索引类型大部分使用近似最近邻搜索算法(ANNS,approximate nearest neighbors search) 。ANNS 的核心思想不再局限于返回最准确…

棋牌室计时吧台计费收费灯控管理系统软件操作流程

棋牌室计时吧台计费收费灯控管理系统软件操作流程 一、前言 以下软件操作教程以&#xff0c;佳易王棋牌桌球计时计费管理系统软件灯控版V17.87为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 该计时计费软件可以是棋牌和桌球混合同时计时计费 …

uniapp:Hbuilder没有检测到设备请插入设备或启动模拟器的问题解决

问题 使用模拟器调试运行项目时&#xff0c;出现以下提示&#xff0c;“没有检测到设备&#xff0c;请插入设备或启动模拟器后点击刷新再试”。排查了一天最终找到原因。 解决 已确认模拟器是已经正常启动&#xff0c;并且Hbuilder设置中的adb路径和端口都配置没有问题&#…

123124

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话&#xff1a; 知不足而奋进&#xff0c;望远山而前行&am…

如何挂载img镜像以及lvm分区

上一章节&#xff0c;我在win10下利用qemu安装了一个aarch64的 kylin-server-v10的ISO系统镜像包。安装时将系统安装到了虚拟硬盘kylin-server-v10.img 里&#xff0c;现在有个需求&#xff0c;要读出kylin-server-v10.img中文件系统的内容。 通过fdisk命令可以看到 kylin-ser…

element-ui drawer 组件源码分享

今日简单分享 drawer 组件的源码实现&#xff0c;从以下五个方面来分享&#xff1a; 1、drawer 组件页面结构 2、drawer 组件属性 3、drawer 组件 slot 4、drawer 组件方法 5、drawer 组件事件 一、drawer 组件页面结构 二、drawer 组件属性 2.1 append-to-body 属性&am…

vulhub之fastjson篇-1.2.27-rce

一、启动环境 虚拟机:kali靶机:192.168.125.130/172.19.0.1(docker地址:172.19.0.2) 虚拟机:kali攻击机:192.168.125.130/172.19.0.1 本地MAC:172.XX.XX.XX 启动 fastjson 反序列化导致任意命令执行漏洞 环境 1.进入 vulhub 的 Fastjson 1.2.47 路径 cd /../../vulhub/fa…