Spring IoC&DI
作者:mmseoamin日期:2023-12-25

Spring IoC&DI,在这里插入图片描述,第1张

文章目录

  • 前言
  • 什么是Spring
    • 1. 什么是 IoC 容器
      • 1.1 什么是容器
      • 1.2 什么是 IoC
      • 2. 什么是DI
      • IoC & DI 的使用
      • IoC详解
        • Bean的存储
          • @Controller注解
          • 如何获取Bean
            • 1. 根据Bean的名称获取Bean
            • 2. 根据Bean类型获取Bean
            • 3. 根据Bean名和Bean类型获取Bean
            • @Service注解
            • @Repository注解
            • @Component注解
            • @Configuration注解
            • 为什么会有这么多类注解
            • 方法注解
            • 重命名Bean
            • 扫描路径
            • DI 详解
              • 1. 属性注入
              • 构造方法注入
              • Setter 注入
              • 三种注入的优缺点
              • Autowired 存在的问题

                前言

                前面我们大概知道了什么是 Spring,以及 Spring 家族中 Spring Boot 和 Spring MVC的开发,但是 Spring 到底是什么呢?

                什么是Spring

                前面我为大家简单介绍了什么是 Spring 【Spring】什么是Spring,不过前面的介绍较为简单,要想知道Spring 的原理,这些知识不不足以帮助我们了解 Spring 的,所以这篇文章我将详细为大家介绍什么是 Spring。

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

                但是这个概念对于我们来说,还是太抽象了,用一句话概括:Spring 是包含了众多工具的 IoC 容器。那么什么是 IoC 容器呢?

                1. 什么是 IoC 容器

                1.1 什么是容器

                容器是指能够容纳某种物品的装置。在生活中,储物箱、垃圾桶、冰箱等这些都属于容器,而在计算机中,我们前面学习的List/map就是数据存储的容器,Tomcat就是Web容器。

                1.2 什么是 IoC

                IoC 是 Spring 的核心思想。

                IoC是Inversion of Control的缩写,多数书籍翻译成“控制反转”。1996年,Michael Mattson在一篇有关探讨面向对象框架的文章中,首先提出了IoC这个概念。对于面向对象设计及编程的基本思想,简单来说就是把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实现对外部是透明的,从而降低了解决问题的复杂度,而且可以灵活地被重用和扩展。

                在传统的程序设计中,对象的创建和管理都是由代码直接完成的。而在IoC中,对象的创建和管理权交给了IoC Service Provider(IoC思想的具体实现),我们只需要告诉它需要什么对象,它就会为我们准备好。这种机制的引入,使得应用程序的各个部分之间的依赖关系变得非常清晰,并且可以将各个部分解耦,提高代码的可重用性和可维护性。

                给大家举个例子,传统的汽车开发过程是这样的:

                Spring IoC&DI,在这里插入图片描述,第2张

                用代码体现就是这样的:

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

                如果我们在造车的时候,需要造车的一方指定轮胎大小的话,那么这个生产车的代码进行较大的改动。

                Spring IoC&DI,在这里插入图片描述,第3张

                Spring IoC&DI,在这里插入图片描述,第4张

                Spring IoC&DI,在这里插入图片描述,第5张

                Spring IoC&DI,在这里插入图片描述,第6张

                Spring IoC&DI,在这里插入图片描述,第7张

                可以看到,当需要造车方指定轮胎的大小的时候,基本上所有的零件的代码都需要做出更改,这就叫做 高耦合

                什么叫做高内聚、低耦合呢?

                相比大家经常会听到高内聚、低耦合这句话吧,那么它们到底代表的什么意思呢?

                “高内聚、低耦合”是软件工程中的概念,是判断软件设计好坏的标准,主要用于程序的面向对象的设计,主要看类的内聚性是否高,耦合度是否低。

                • 高内聚:内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。一个模块内各个元素彼此结合的紧密程度高,则内聚性高。所谓高内聚就是一个软件模块是由相关性很强的代码组成,只负责一项任务,也就是常说的单一责任原则。
                • 低耦合:耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。一个程序结构中各模块的内聚程度越高,模块间的耦合程度就越低。

                  上面我们设计的代码的耦合程度就比较高,那么应该如何降低耦合度呢?

                  我们可以将各个零件之间的依赖关系给改变一下。

                  Spring IoC&DI,在这里插入图片描述,第8张

                  我们先根据需要,创造出指定大小的轮胎,然后将造好的轮胎给底盘创造厂,然后再造好底盘,将造好的底盘交给车身制造厂,制造出车身,最后将造好的车身交给汽车制造厂,最终制造出来一个汽车。

                  public class NewCarExample {
                      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("Framework 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(int size) {
                              this.size = size;
                              System.out.println("轮胎尺寸:" + size);
                          }
                      }
                  }
                  

                  通过更改各个类之间的依赖关系,那么就算底层轮胎如何变化,也不会影响整个产业链,这样就实现了代码之间的解耦,从而实现了更加灵活、通用的程序设计了。

                  通过上面的优化,我们发现:类的创建顺序是相反的,之前是 Car 控制并创建了 Framework,Framework 创建并控制创建了 Bottom,Bottom 创建并控制创建了 Tire,改进之后的控制权发生了反转,不再是使用方对象创建并控制依赖对象了,而是把依赖对象注入到当前对象中,依赖对象的控制权不再由当前类控制了。

                  这样,即使依赖对象发生任何变化,当前类都是不受影响的,这就是典型的控制反转,也就是是 IoC 的实现思想。

                  知道了什么是容器以及什么是 IoC 之后我们就知道了什么叫做 IoC 容器了。

                  Spring IoC&DI,在这里插入图片描述,第9张

                  IoC 容器的优点:

                  通过上面的案例我们可以看出来,使用 IoC 容器,资源不再由使用资源的双方管理,而是由不使用资源的第三方进行管理,这样可以带来以下好处:1. 实现资源的集中统一管理;2. 降低了使用资源的双方的依赖程度,也就是耦合程度。

                  1. 资源集中管理:IoC容器会帮我们管理一些资源(对象)等,我们在使用的时候只需要从IoC中去取就可以了。
                  2. 我们在创建实例的时候不需要了解其中的具体细节,降低了使用资源的双方的依赖程度(耦合程度)。

                  2. 什么是DI

                  DI(Dependency Injection)即依赖注入,是面向对象编程中的一种设计模式,用来减少代码之间的耦合度。

                  具体来说,依赖注入将对象的创建和管理权从代码中转移到了外部容器,通过外部容器来创建对象并注入需要的依赖。这种方式可以降低代码的耦合度,提高代码的可重用性和可维护性。

                  在Java中,Spring框架是使用依赖注入最广泛的开源框架之一。通过使用依赖注入,Spring可以将应用程序中的各个组件解耦,使得它们之间的依赖关系变得更加清晰和易于管理。

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

                  从这点来看,依赖注⼊(DI)和控制反转(IoC)是从不同的⻆度的描述的同⼀件事情,就是指通过引⼊ IoC 容器,利⽤依赖关系注⼊的⽅式,实现对象之间的解耦。

                  在造汽车的过程中,将 Tire 这个依赖注入到 Bottom 中造出 Bottom,然后将造好的 Bottom 依赖注入到 Framework 中造出 Framework,最后将造好的 Framework 依赖注入到 Car 中,最终创建出 Car。

                  IoC 是⼀种思想,也是"⽬标",⽽思想只是⼀种指导原则,最终还是要有可⾏的落地⽅案,⽽ DI 就属于具体的实现。所以也可以说, DI 是 IoC 的⼀种实现。

                  IoC & DI 的使用

                  Spring 既然是一个 IoC 容器,那么他肯定具有两个基本的功能:存和取。

                  Spring 容器管理的主要是对象,这些对象我们称之为“Bean”,这个跟我们前面学习的 Bean 不一样。我们把这些 Bean 交给 Spring 进行管理,由 Spring 来负责对象的创建和销毁,我们在写 Spring 代码的时候只需要告诉 Spring,哪些对象是我们要交给 Spring 管理,我们又要取出哪些对象进行使用。

                  那么在 Spring 中,如何存储和取出 Bean 呢?

                  1. 将类存储进 Spring IoC 容器中需要使用 @Component 注解,其实还有很多注解,这里我们先为大家介绍这个注解,本文后面再为大家介绍另外几种存储 Bean 的注解
                  2. 取出依赖对象使用注解 @Autowired
                  package com.example.springiocdi20231209;
                  import org.springframework.stereotype.Component;
                  @Component
                  public class UserComponent {
                      public void sayHi() {
                          System.out.println("hello spring");
                      }
                  }
                  
                  package com.example.springiocdi20231209;
                  import org.springframework.beans.factory.annotation.Autowired;
                  import org.springframework.web.bind.annotation.RequestMapping;
                  import org.springframework.web.bind.annotation.RestController;
                  @RestController
                  @RequestMapping("/component")
                  public class GetMessage {
                      @Autowired
                      private UserComponent userComponent;
                      
                      @RequestMapping("/get")
                      public void get() {
                          userComponent.sayHi();
                      }
                  }
                  

                  Spring IoC&DI,在这里插入图片描述,第10张

                  Spring IoC&DI,在这里插入图片描述,第11张

                  这里显示出了我们想要的结果,就说明我们使用 @Componnet 注解和 @Autowired 注解对 Bean 实现了存储和取出。

                  需要注意的是:当我们在使用 @Autowired 注解的时候,需要保证这个类有 Controller 或者 RestController 注解,因为我们既然要想使用 Spring 的 IoC 容器肯定要保证这个类是被 Spring 管理的。

                  IoC详解

                  上面为大家展示了 IoC 和 DI 的基本使用,接下来将为大家详细的讲解一下 IoC。

                  Bean的存储

                  上面我们存储 Bean 使用的是 @Component 注解,而 Spring 框架为了更好的服务 Web 应用程序,提供了更丰富的注解。

                  1. 类注解:@Controller、@Service、@Repository、@Componet、@Configuration。
                  2. 方法注解:@Bean

                  @Controller注解

                  package com.example.springiocdi20231209.Controller;
                  import org.springframework.stereotype.Controller;
                  @Controller
                  public class UserController {
                      public void sayHi() {
                          System.out.println("hi, spring");
                      }
                  }
                  

                  使用 @Controller 就将这个 Bean 给存储到 IoC 容器中了,那么我们如何获取这个 Bean 呢?

                  如何获取Bean

                  获取 Bean 的方法有很多种,我们只要介绍下面的第1、2、4种。

                  Spring IoC&DI,在这里插入图片描述,第12张

                  1. 根据Bean的名称获取Bean

                  我们可以根据 Bean 的名字来获取到指定的 Bean,但是某个 Bean 的名称是什么,我们该怎么知道呢?我们来看看官方的解释。

                  Spring IoC&DI,在这里插入图片描述,第13张

                  Spring IoC&DI,在这里插入图片描述,第14张

                  Spring IoC&DI,在这里插入图片描述,第15张

                  简单来讲就是当类名中前两个字母中大写字母小于2的时候,那么该类交给 IoC 后就会以第一个字母小写的小驼峰形式命名,当类名的前两个字母都为大写字母的时候,那么该 Bean 名就是原类名。

                  所以要以 Bean 名获取到 UserController 这个 Bean 的话,就需要将 userController 作为参数。

                  package com.example.springiocdi20231209;
                  import com.example.springiocdi20231209.Controller.UserController;
                  import org.springframework.boot.SpringApplication;
                  import org.springframework.boot.autoconfigure.SpringBootApplication;
                  import org.springframework.context.ApplicationContext;
                  @SpringBootApplication
                  public class SpringIoCDi20231209Application {
                      public static void main(String[] args) {
                          ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);
                          UserController userController = (UserController) context.getBean("userController");
                      }
                  }
                  

                  我们这个代码是在 项目名称+Application 这个类中写的,准确来说是在有 @SpringBootApplication 这个注解的类中写的,并且 SpringApplication.run() 方法是可以有返回值也可以没有返回值的,我们可以根据需要使用变量来接收这个方法的返回值。要想获取到 IoC 容器中的 Bean,需要依靠 ApplicationContext 这个类,所以我们就用这个类的变量来接收 run 方法的返回值。

                  Spring IoC&DI,在这里插入图片描述,第16张

                  启动项目的时候,就会发现我们预想中的结果出现在了控制台中,并且这个不需要我们发送什么 Http 请求,而是启动项目就会自动执行这个类当中的代码。并且通过 Bean 名获取到的 Bean 名返回的是一个 Object 类型,所以在拿变量进行接收的时候就需要进行类型的转换。

                  2. 根据Bean类型获取Bean

                  可以通过 Bean 类型来获取到 Bean。

                  package com.example.springiocdi20231209;
                  import com.example.springiocdi20231209.Controller.UserController;
                  import org.springframework.boot.SpringApplication;
                  import org.springframework.boot.autoconfigure.SpringBootApplication;
                  import org.springframework.context.ApplicationContext;
                  @SpringBootApplication
                  public class SpringIoCDi20231209Application {
                      public static void main(String[] args) {
                          ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);
                          //1. 根据Bean名字来获取Bean
                          //UserController userController = (UserController) context.getBean("userController");
                          //2. 根据Bean类型来获取Bean
                          UserController userController = context.getBean(UserController.class);
                          userController.sayHi();
                      }
                  }
                  

                  Spring IoC&DI,在这里插入图片描述,第17张

                  3. 根据Bean名和Bean类型获取Bean

                  通过 Bean 名获取 Bean 需要进行类型的转换,可以在传递参数的时候就指定返回值的类型。

                  package com.example.springiocdi20231209;
                  import com.example.springiocdi20231209.Controller.UserController;
                  import org.springframework.boot.SpringApplication;
                  import org.springframework.boot.autoconfigure.SpringBootApplication;
                  import org.springframework.context.ApplicationContext;
                  @SpringBootApplication
                  public class SpringIoCDi20231209Application {
                      public static void main(String[] args) {
                          ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);
                          //1. 根据Bean名字来获取Bean
                          //UserController userController = (UserController) context.getBean("userController");
                          //2. 根据Bean类型来获取Bean
                          //UserController userController = context.getBean(UserController.class);
                          //3. 根据Bean名和Bean类型获取Bean
                          UserController userController = context.getBean("userController", UserController.class);
                          userController.sayHi();
                      }
                  }
                  

                  Spring IoC&DI,在这里插入图片描述,第18张

                  @Service注解

                  通过这个注解,也可以将类交给 Spring 进行管理。

                  package com.example.springiocdi20231209.Controller;
                  import org.springframework.stereotype.Controller;
                  import org.springframework.stereotype.Service;
                  @Service
                  public class UserController {
                      public void sayHi() {
                          System.out.println("hi, spring");
                      }
                  }
                  

                  Spring IoC&DI,在这里插入图片描述,第19张

                  @Repository注解

                  package com.example.springiocdi20231209.Controller;
                  import org.springframework.stereotype.Controller;
                  import org.springframework.stereotype.Repository;
                  import org.springframework.stereotype.Service;
                  @Repository
                  public class UserController {
                      public void sayHi() {
                          System.out.println("hi, spring");
                      }
                  }
                  

                  Spring IoC&DI,在这里插入图片描述,第20张

                  @Component注解

                  package com.example.springiocdi20231209.Controller;
                  import org.springframework.stereotype.Component;
                  import org.springframework.stereotype.Controller;
                  import org.springframework.stereotype.Repository;
                  import org.springframework.stereotype.Service;
                  @Component
                  public class UserController {
                      public void sayHi() {
                          System.out.println("hi, spring");
                      }
                  }
                  

                  Spring IoC&DI,在这里插入图片描述,第21张

                  @Configuration注解

                  package com.example.springiocdi20231209.Controller;
                  import org.springframework.context.annotation.Configuration;
                  import org.springframework.stereotype.Component;
                  import org.springframework.stereotype.Controller;
                  import org.springframework.stereotype.Repository;
                  import org.springframework.stereotype.Service;
                  @Configuration
                  public class UserController {
                      public void sayHi() {
                          System.out.println("hi, spring");
                      }
                  }
                  

                  Spring IoC&DI,在这里插入图片描述,第22张

                  为什么会有这么多类注解

                  这个也是和咱们前⾯讲的应⽤分层是呼应的.让程序员看到类注解之后,就能直接了解当前类的⽤途。

                  • @Controller:控制层,接收请求,对请求进⾏处理,并进⾏响应
                  • @Servie:业务逻辑层,处理具体的业务逻辑
                  • @Repository:数据访问层,也称为持久层.负责数据访问操作
                  • @Configuration:配置层.处理项⽬中的⼀些配置信息

                    Spring IoC&DI,在这里插入图片描述,第23张

                    并且通过观察这五个注解的源码我们可以发现一些问题。

                    Spring IoC&DI,在这里插入图片描述,第24张

                    Spring IoC&DI,在这里插入图片描述,第25张

                    Spring IoC&DI,在这里插入图片描述,第26张

                    Spring IoC&DI,在这里插入图片描述,第27张

                    Spring IoC&DI,在这里插入图片描述,第28张

                    查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发现:

                    其实这些注解⾥⾯都有⼀个注解 @Component ,说明它们本⾝就是属于@Component 的"⼦类".@Component 是⼀个元注解,也就是说可以注解其他类注解,如 @Controller , @Service ,@Repository 等.这些注解被称为 @Component 的衍⽣注解.

                    @Controller @Service 和 @Repository ⽤于更具体的⽤例(分别在控制层,业务逻辑层,持久化层),在开发过程中,如果你要在业务逻辑层使⽤ @Component 或@Service,显然@Service是更好的选择。

                    方法注解

                    类注解是写在我们项目代码中的类上的,但是存在两个问题:

                    1. 使用外部包里的类,没办法添加类注解
                    2. 一个类,需要多个对象,比如多个数据源

                    上面两个问题是无法使用类注解来解决的。所以也就出现了方法注解 @Bean

                    假设我们这里的 User 类是一个外部包里的类,那么我们就无法在这个类中添加类注解,这是就需要使用到方法注解。

                    package com.example.springiocdi20231209;
                    import org.springframework.context.annotation.Bean;
                    public class BeanConfig {
                        @Bean
                        public User user() {
                            User user = new User();
                            user.setName("zhangsan");
                            user.setAge(18);
                            return user;
                        }
                    }
                    

                    运行 @SpringApplication 注解的代码,看看什么效果。

                    Spring IoC&DI,在这里插入图片描述,第29张

                    这里报错说这个 Bean 没有被定义。其实使用方法注解 @Bean 的时候,需要保证该方法所在的类也有被类注解注释。

                    package com.example.springiocdi20231209;
                    import org.springframework.context.annotation.Bean;
                    import org.springframework.stereotype.Component;
                    @Component
                    public class BeanConfig {
                        @Bean
                        public User user() {
                            User user = new User();
                            user.setName("zhangsan");
                            user.setAge(18);
                            return user;
                        }
                    }
                    
                    package com.example.springiocdi20231209;
                    import com.example.springiocdi20231209.Controller.UserController;
                    import org.springframework.boot.SpringApplication;
                    import org.springframework.boot.autoconfigure.SpringBootApplication;
                    import org.springframework.context.ApplicationContext;
                    @SpringBootApplication
                    public class SpringIoCDi20231209Application {
                        public static void main(String[] args) {
                            ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);
                            User user = context.getBean(User.class);
                            System.out.println(user);
                        }
                    }
                    

                    Spring IoC&DI,在这里插入图片描述,第30张

                    同一个类定义多个对象。

                    package com.example.springiocdi20231209;
                    import org.springframework.context.annotation.Bean;
                    import org.springframework.stereotype.Component;
                    @Component
                    public class BeanConfig {
                        @Bean
                        public User user() {
                            User user = new User();
                            user.setName("zhangsan");
                            user.setAge(18);
                            return user;
                        }
                        @Bean
                        public User user2() {
                            User user = new User();
                            user.setName("liis");
                            user.setAge(20);
                            return user;
                        }
                    }
                    

                    当我们使用方法注解,并且一个类有多个相同类型的 Bean 类型的时候,并且我们通过 Bean 类型获取 Bean 的话就会出错。

                    Spring IoC&DI,在这里插入图片描述,第31张

                    所以这里获取 Bean 的话就需要指定 Bean 名称。

                    User user = context.getBean("user2", User.class);
                    

                    Spring IoC&DI,在这里插入图片描述,第32张

                    @Bean 注解的 Bean,Bean 的,名称就是方法名。

                    重命名Bean

                    前面我们呢说了 Bean 的默认名称,但其实我们可以指定 Bean 的名称。那么如何重命名 Bean 呢?

                    @Bean("beanName")
                    
                    package com.example.springiocdi20231209;
                    import org.springframework.context.annotation.Bean;
                    import org.springframework.stereotype.Component;
                    @Component
                    public class BeanConfig {
                        @Bean("u1")
                        public User user() {
                            User user = new User();
                            user.setName("zhangsan");
                            user.setAge(18);
                            return user;
                        }
                        @Bean("u2")
                        public User user2() {
                            User user = new User();
                            user.setName("liis");
                            user.setAge(20);
                            return user;
                        }
                    }
                    
                    ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);
                    User user = context.getBean("u2", User.class);
                    System.out.println(user);
                    

                    Spring IoC&DI,在这里插入图片描述,第33张

                    可以看到我们通过重命名的名字u2获取到了Bean。

                    不仅如此,通过观察 @Bean 的源码我们可以发现,这里的name参数是一个字符串数组,也就是说一个 Bean 可以有多个名字。

                    Spring IoC&DI,在这里插入图片描述,第34张

                    @Bean({"u2", "s2"})
                    

                    Spring IoC&DI,在这里插入图片描述,第35张

                    同样的类注解也可以重命名,但是类注解只支持一个名字。

                    Spring IoC&DI,在这里插入图片描述,第36张

                    @Configuration("c1")
                    public class UserController {
                        public void sayHi() {
                            System.out.println("hi, spring");
                        }
                    }
                    

                    Spring IoC&DI,在这里插入图片描述,第37张

                    扫描路径

                    其实并不是项目下的所有文件中的加了注解的类都会被 Spring 进行管理,而是需要看扫描路径在哪。假设我们将 @SpringBootApplication 注解所在的类给换个路径。

                    Spring IoC&DI,在这里插入图片描述,第38张

                    Spring IoC&DI,在这里插入图片描述,第39张

                    Spring IoC&DI,在这里插入图片描述,第40张

                    @SpringBootApplication
                    public class SpringIoCDi20231209Application {
                        public static void main(String[] args) {
                            ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);
                            User user = context.getBean("u1", User.class);
                            System.out.println(user);
                        }
                    }
                    

                    Spring IoC&DI,在这里插入图片描述,第41张

                    这里就报错说找不到 u1 这个 Bean,说明这个注解没有被扫描到,那么为什么呢?

                    这其实跟 @ComponentScan 注解配置的扫描路径有关,但是我们 SpringBootApplication 注解的类当中不是没有这个注解吗?其实这个注解继承了 @ComponentScan 注解。

                    Spring IoC&DI,在这里插入图片描述,第42张

                    而如果 @ComponnetScan 没有配置的话,就默认的是当前 @ComponentScan 注解的文件所在的路径。

                    这里 @SpringBootApplication 注解的类所在的路径是这个 package com.example.springiocdi20231209.springiocdi20231209.Controller;

                    而我们的 u1 Bean 所在的路径是 package com.example.springiocdi20231209.springiocdi20231209;,所以这个 Bean 是 Component 无法扫描到的。

                    要想扫描到这个路径,我们可以对 @ComponentScan 注解进行配置。

                    @ComponentScan({"com.example.springiocdi20231209.springiocdi20231209"})
                    @SpringBootApplication
                    public class SpringIoCDi20231209Application {
                        public static void main(String[] args) {
                            ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);
                            User user = context.getBean("u1", User.class);
                            System.out.println(user);
                        }
                    }
                    

                    Spring IoC&DI,在这里插入图片描述,第43张

                    @ComponentScan 也是可以配置多个扫描路径的。

                    DI 详解

                    依赖注⼊是⼀个过程,是指IoC容器在创建Bean时,去提供运⾏时所依赖的资源,⽽资源指的就是对象。在上⾯程序案例中,我们使⽤了 @Autowired 这个注解,完成了依赖注⼊的操作。简单来说,就是把对象取出来放到某个类的属性中。

                    在⼀些⽂章中,依赖注⼊也被称之为"对象注⼊",“属性装配”,具体含义需要结合⽂章的上下⽂来理解。

                    关于依赖注入,Spring 为我们提供了三种方法:

                    1. 属性注入(Filed Injection)
                    2. 构造方法注入(Constructor Injection)
                    3. Setter 注入(Setter Injection)

                    1. 属性注入

                    属性注⼊是使⽤ @Autowired 实现的。

                    package com.example.springiocdi2;
                    import org.springframework.stereotype.Service;
                    @Service
                    public class UserService {
                            public void sayHi() {
                                System.out.println("Hi UserService");
                            }
                    }
                    
                    package com.example.springiocdi2;
                    import org.springframework.beans.factory.annotation.Autowired;
                    import org.springframework.stereotype.Controller;
                    @Controller
                    public class UserController {
                    	//属性注入
                        @Autowired
                        private UserService userService;
                        public void sayHi() {
                            System.out.println("Hi UserController...");
                            userService.sayHi();
                        }
                    }
                    
                    package com.example.springiocdi2;
                    import org.springframework.boot.SpringApplication;
                    import org.springframework.boot.autoconfigure.SpringBootApplication;
                    import org.springframework.context.ApplicationContext;
                    @SpringBootApplication
                    public class SpringIoCDi2Application {
                        public static void main(String[] args) {
                            ApplicationContext context = SpringApplication.run(SpringIoCDi2Application.class, args);
                            UserController userController = context.getBean("userController", UserController.class);
                            userController.sayHi();
                        }
                    }
                    

                    运行结果:

                    Spring IoC&DI,在这里插入图片描述,第44张

                    构造方法注入

                    构造⽅法注⼊是在类的构造⽅法中实现注⼊。

                    package com.example.springiocdi2;
                    import org.springframework.beans.factory.annotation.Autowired;
                    import org.springframework.stereotype.Controller;
                    @Controller
                    public class UserController2 {
                        //构造方法注入
                        private UserService userService;
                        
                        @Autowired
                        public UserController2(UserService userService) {
                            this.userService = userService;
                        }
                        
                        public void sayHi() {
                            System.out.println("Hi UserController2");
                            userService.sayHi();
                        }
                    }
                    

                    Spring IoC&DI,在这里插入图片描述,第45张

                    注意事项:如果类只有⼀个构造⽅法,那么 @Autowired 注解可以省略;如果类中有多个构造⽅法,

                    那么需要添加上 @Autowired 来明确指定到底使⽤哪个构造⽅法。

                    Spring IoC&DI,在这里插入图片描述,第46张

                    Spring IoC&DI,在这里插入图片描述,第47张

                    Setter 注入

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

                    解。

                    package com.example.springiocdi2;
                    import org.springframework.beans.factory.annotation.Autowired;
                    import org.springframework.stereotype.Controller;
                    @Controller
                    public class UserController3 {
                        //setter方法注入
                        private UserService userService;
                        
                        @Autowired
                        public void setUserService(UserService userService) {
                            this.userService = userService;
                        }
                        
                        public void sayHi() {
                            System.out.println("Hi UserController3");
                            userService.sayHi();
                        }
                    }
                    

                    Spring IoC&DI,在这里插入图片描述,第48张

                    如果没加@Autowired 注解,就会报错。

                    Spring IoC&DI,在这里插入图片描述,第49张

                    三种注入的优缺点

                    1. 属性注入
                    • 优点:简洁,使用方便
                    • 缺点:
                      • 只能用于 IoC 容器,如果是非 IoC 容器不可用,并且只有在使用的时候才会出现 NPE(空指针异常)
                        • 不能注入一个 Final 修饰的属性
                          1. 构造函数注入(Spring 4x推荐)
                          • 优点:
                            • 可以注入 Final 修饰的属性
                              • 注入的对象不会被修改
                                • 依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载阶段就会执行的方法
                                  • 通用性好,构造方法是JDK支持的,所以更换任何框架,他都是适用的
                                  • 缺点:
                                    • 注入多个对象的时候,代码会比较繁琐
                                      1. Setter注入(Spring 3x推荐)
                                      • 优点:⽅便在类实例之后,重新对该对象进⾏配置或者注⼊
                                      • 缺点:
                                        • 不能注⼊⼀个Final修饰的属性
                                          • 注⼊对象可能会被改变,因为setter⽅法可能会被多次调⽤,就有被修改的⻛险

                                            属性注入,注入一个final修饰的属性:

                                            Spring IoC&DI,在这里插入图片描述,第50张

                                            构造方法注入,注入一个final修饰的属性:

                                            Spring IoC&DI,在这里插入图片描述,第51张

                                            Setter注入,注入一个final修饰的属性:

                                            Spring IoC&DI,在这里插入图片描述,第52张

                                            为什么有些注入不能注入 final 修饰的属性?

                                            如果一个属性被final关键字修饰,那么这个属性就成为了一个常量,它的值就不能被改变。而依赖注入的属性注入需要动态地修改属性的值,所以不能对被final关键字修饰的属性进行依赖注入。但是,在构造方法中,final属性可以被赋值。这是因为构造方法是在对象创建时执行的,此时final属性还没有被赋值。因此,在构造方法中可以对final属性进行赋值操作。

                                            Autowired 存在的问题

                                            当同一个类类型存在多个 Bean 时,就会出现问题。

                                            package com.example.springiocdi2;
                                            import lombok.Data;
                                            @Data
                                            public class User {
                                                private String name;
                                                private Integer age;
                                            }
                                            
                                            package com.example.springiocdi2;
                                            import org.springframework.context.annotation.Bean;
                                            import org.springframework.stereotype.Component;
                                            @Component
                                            public class BeanConfig {
                                                @Bean("u1")
                                                public User user1() {
                                                    User user = new User();
                                                    user.setName("zhangsan");
                                                    user.setAge(17);
                                                    return user;
                                                }
                                                @Bean("u2")
                                                public User user2() {
                                                    User user = new User();
                                                    user.setName("lisi");
                                                    user.setAge(18);
                                                    return user;
                                                }
                                            }
                                            
                                            package com.example.springiocdi2;
                                            import org.springframework.beans.factory.annotation.Autowired;
                                            import org.springframework.stereotype.Component;
                                            @Component
                                            public class UserController4 {
                                                @Autowired
                                                private User user;
                                                public void sayHi() {
                                                    System.out.println("Hi UserController4");
                                                    System.out.println(user);
                                                }
                                            }
                                            

                                            Spring IoC&DI,在这里插入图片描述,第53张

                                            如何解决这个一个类型有多个 bean 的问题呢?Spring 提供了以下的几种方案:

                                            • @Primary
                                            • @Qualifier
                                            • Resource

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

                                              	@Bean("u1")
                                                  @Primary  //指定该bean为默认实现
                                                  public User user1() {
                                                      User user = new User();
                                                      user.setName("zhangsan");
                                                      user.setAge(17);
                                                      return user;
                                                  }
                                              

                                              Spring IoC&DI,在这里插入图片描述,第54张

                                              使⽤@Qualifier注解:指定当前要注⼊的bean对象。在@Qualifier的value属性中,指定注⼊的bean

                                              的名称。

                                              • @Qualifier注解不能单独使⽤,必须配合@Autowired使⽤
                                                	@Qualifier("u2")
                                                    @Autowired
                                                    private User user;
                                                

                                                Spring IoC&DI,在这里插入图片描述,第55张

                                                使⽤@Resource注解:是按照bean的名称进⾏注⼊。通过name属性指定要注⼊的bean的名称。

                                                	@Resource(name = "u2")
                                                    private User user;
                                                

                                                Spring IoC&DI,在这里插入图片描述,第56张

                                                常见面试题:

                                                @Autowird 与 @Resource的区别:

                                                • @Autowired 是spring框架提供的注解,⽽@Resource是JDK提供的注解
                                                • @Autowired 默认是按照类型注⼊,⽽@Resource是按照名称注⼊。相⽐于 @Autowired 来说,@Resource 支持更多的参数设置,例如 name 设置,根据名称获取 Bean