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

news2024/11/26 2:55:10

目录

  • 一、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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

k8s_入门_命令详解

命令详解 kubectl是官方的CLI命令行工具&#xff0c;用于与 apiserver进行通信&#xff0c;将用户在命令行输入的命令&#xff0c;组织并转化为 apiserver能识别的信息&#xff0c;进而实现管理k8s各种资源的一种有效途径 1. 帮助 2. 查看版本信息 3. 查看资源对象等 查看No…

小型企业网络安全指南

许多小型企业刚刚起步&#xff0c;没有大公司所拥有的相同资源来保护其数据。他们不仅可能没有资金来支持多样化的安全计划&#xff0c;而且也可能没有人力或时间。 网络犯罪分子知道小型企业缺乏这些资源&#xff0c;并利用这些资源来谋取利益。遭受网络攻击后&#xff0c;小…

c语言:操作符

操作符 一.算术操作符: + - * % / 1.除了%操作符之外,其他的几个操作符可以作用与整数和浮点数,如:5%2.0//error. 2.对于操作符,如果两个操作数都为整数,执行整数除法而只要有浮点数执行的就是浮点数除法。 3.%操作符的两个操作数必须为整数。 二.移位操作符:<&…

Windows(Win11) 安装 Docker (Docker Desktop)

目录 前言 下载 安装 wsl 安装 Docker Desktop 启动 Docker Desktop 配置国内镜像 拉取镜像 前言 一般 docker 都是直接安装在 Linux 服务器上&#xff0c;用来快速部署一些中间件&#xff08;比如 redis&#xff0c;rocketmq等等&#xff09;&#xff0c;省去繁琐的安…

Qt QML的插件(Qt Quick 2 Extension Plugin)方法

Qt Quick的插件方法 序言环境前置注意概念——Qt Quick插件的相关知识插件里的qml文件模块名的相关知识模块名本身注意事项模块名版本注意事项 以示例来说明创建插件qmltypes的生成qmltypes的可能性失效 插件的编码注意1、插件模块版本控制2、pro里的注意 调用插件插件信息输入…

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…

(Atcoder Beginner Contest 348)题解

前言 这是我第 4 4 4 次做出 F F F 题&#xff0c;庆祝上蓝&#xff01; 正题 本题解提供 A − F A-F A−F 题题解&#xff0c;欢迎诸位大佬参考。 第 1 题 Penalty Kick 照例很水&#xff0c;模拟即可。 #include <bits/stdc.h> using namespace std; #define…

一种新兴的身份安全理念:身份结构免疫

文章目录 前言一、从身份管理到身份结构免疫二、身份结构免疫应用实践三、典型应用场景前言 随着组织的数字身份数量激增,基于身份的网络攻击活动也在不断增长。在身份优先的安全原则下,新一代身份安全方案需要更好的统一性和控制度。而在现有的身份管理模式中,组成业务运营…

MySQL相关问题快问快答

我写这篇文章的目的只有一个&#xff1a;通过这些问题来帮助我去将我脑子里的MySQL脑图给巩固熟悉&#xff0c;通过回答这些问题&#xff0c;让我对脑子里的MySQL知识有更深的印象&#xff0c;当什么时候我的MySQL脑图不熟的时候&#xff0c;我就可以拿这篇文章来去巩固一下&am…

ctfshow web入门 php特性 web123--web139

web123 必须传CTF_SHOW&#xff0c;CTF_SHOW.COM 不能有fl0g 在php中变量名字是由数字字母和下划线组成的&#xff0c;所以不论用post还是get传入变量名的时候都将空格、、点、[转换为下划线&#xff0c;但是用一个特性是可以绕过的&#xff0c;就是当[提前出现后&#xff0c;…

【javaWeb 原理篇】底层实现原理(快速学习配置原理,Bean管理)

Spring底层 配置优先级Bean管理获取beanBean的作用域第三方Bean SpringBoot原理起步依赖自动配置自动配置的原理自定义starter 配置优先级 Spring中的配置文件如果配置了相同的内容则根据配置优先级进行配置: application.properties>application.yml>application.yaml …

90天玩转Python—08—基础知识篇:Python优秀代码的编程规范

90天玩转Python系列文章目录 90天玩转Python—01—基础知识篇:C站最全Python标准库总结 90天玩转Python--02--基础知识篇:初识Python与PyCharm 90天玩转Python—03—基础知识篇:Python和PyCharm(语言特点、学习方法、工具安装) 90天玩转Python—04—基础知识篇:Pytho…

Python技能树学习-函数

题目一&#xff1a;递归调用 函数的参数&#xff1a; def dump(index, default0, *args, **kw): print(打印函数参数) print(---) print(index:, index) print(default:, default) for i, arg in enumerate(args): print(farg[{i}]:, arg) for…

跨越网络边界:借助C++编写的下载器程序,轻松获取Amazon商品信息

背景介绍 在数字化时代&#xff0c;数据是新的石油。企业和开发者都在寻找高效的方法来收集和分析网络上的信息。亚马逊&#xff0c;作为全球最大的电子商务平台之一&#xff0c;拥有丰富的商品信息&#xff0c;这对于市场分析和竞争情报来说是一个宝贵的资源。 问题陈述 然…

ArcGIS和ArcGIS Pro快速加载ArcGIS历史影像World Imagery Wayback

ArcGIS在线历史影像网站 World Imagery Wayback(网址:https://livingatlas.arcgis.com/wayback/)提供了数期历史影像在线浏览服务,之前不少自媒体作者在文中宣称其能代表Google Earth历史影像。 1、一点对比 (1)同一级别下的版本覆盖面 以下述区域为例,自2014年2月20…

面试题:ConcurrentHashMap

ConcurrentHashMap 是一种线程安全的高效Map集合 底层数据结构&#xff1a; JDK1.7底层采用分段的数组链表实现 JDK1.8 采用的数据结构跟HashMap1.8的结构一样&#xff0c;数组链表/红黑二叉树。 1. JDK1.7 数据结构 提供了一个segment数组&#xff0c;在初始化Concurre…

SQL注入利用学习-Union联合注入

联合注入的原理 在SQL语句中查询数据时&#xff0c;使用select 相关语句与where 条件子句筛选符合条件的记录。 select * from person where id 1; #在person表中&#xff0c;筛选出id1的记录如果该id1 中的1 是用户可以控制输入的部分时&#xff0c;就有可能存在SQL注入漏洞…

【学习】软件验收测试,能否选择第三方检测机构进行测试?

随着信息技术的快速发展&#xff0c;软件已经成为各行各业中不可或缺的一部分。为了保证软件的质量和稳定性&#xff0c;验收测试成为了软件开发过程中至关重要的一环。那么&#xff0c;第三方软件测试机构可以做验收测试吗&#xff1f;我们一起来看下今日的分享。 一、验收测…

目标检测——色素性皮肤病数据集

一、重要性及意义 首先&#xff0c;色素性皮肤病变是一类常见的皮肤疾病&#xff0c;其发病率有逐年增高的趋势。这些病变可能由遗传或环境因素导致黑素细胞生成异常&#xff0c;如黑色素瘤等。黑色素瘤具有极高的恶性率和致死率&#xff0c;而且恶化可能性大&#xff0c;容易…

大模型基础知识 - 语言模型及其演进

开场白 人工智能发展到现在&#xff0c;在2个重要领域取得了重大突破&#xff0c;有望达到人类水平&#xff1a; 计算机视觉 &#xff08;Computer Vision, CV&#xff09;-> 希望机器帮助人类处理图像数据自然语言处理&#xff08;Natural Language Processing, NLP&…