• 【Spring】Spring IOC & DI


    IOC DI入门

    什么是Spring

    Spring是一个开源的轻量级Java开发框架,它提供了全面的基础设施支持和广泛的应用程序级特性,使得Java开发变得更加简单和高效。 Spring框架的核心特点包括依赖注入(Dependency Injection)、面向切面编程(AOP)、容器、事务管理等。

    依赖注入允许开发人员将组件之间的依赖关系外部化,这样可以降低类之间的耦合度,使得代码更容易维护和测试。面向切面编程则允许开发人员定义横切关注点,比如日志、事务管理等,从而实现模块化的开发。

    Spring框架还提供了一个轻量级的IoC容器,用于管理JavaBean的生命周期和配置。此外,Spring还支持各种各样的应用程序级特性,包括Web开发、数据访问、安全性、远程调用等。

    总的来说,Spring框架在Java企业应用开发中扮演着重要的角色,它提供了丰富的功能和灵活的设计理念,使得开发人员能够更加高效地编写可维护、可扩展的应用程序。

    什么是容器

    容器是⽤来容纳某种物品的(基本)装置。
    ⽣活中的⽔杯, 垃圾桶, 冰箱等等这些都是容器.

    • List/Map -> 数据存储容器
    • Tomcat -> Web 容器

    什么是IOC

    IOC(Inverse of Control,控制反转)是面向对象编程中的一种设计原则,它是软件开发中依赖注入(DI)的一种实现方式。在IOC中,控制权从传统的应用程序代码中转移到了外部容器或框架,使得对象之间的依赖关系由外部来管理和注入。

    在传统的编程模式中,一个对象通常会直接创建和管理它所依赖的其他对象。而在IOC容器中,对象的依赖关系由容器在创建对象时进行注入,而不是由对象自身负责。这种控制权的转移使得对象之间的耦合度降低,也更有利于代码的可测试性和可维护性。

    Spring框架就是一个典型的IOC容器,它通过依赖注入的方式来管理对象之间的依赖关系。开发人员只需要描述组件之间的依赖关系,比如通过配置文件或注解,然后由Spring容器负责实例化对象并注入它们之间的依赖关系。

    总的来说,IOC通过将对象之间的依赖关系外部化,使得系统更加灵活、可扩展,并且有利于降低代码之间的耦合度。

    IOC介绍

    接下来我们通过案例来了解⼀下什么是IoC
    需求: 造⼀辆⻋

    传统程序开发

    我们的实现思路是这样的:
    先设计轮⼦(Tire),然后根据轮⼦的⼤⼩设计底盘(Bottom),接着根据底盘设计⻋⾝(Framework),最后根据⻋⾝设计好整个汽⻋(Car)。这⾥就出现了⼀个"依赖"关系:汽⻋依赖⻋⾝,⻋⾝依赖底盘,底盘依赖轮⼦.

    在这里插入图片描述
    这样的设计看起来没有问题,但是可维护性却很低

    例如我们要修改轮胎的尺寸 因为底盘是根据轮胎制造的 所以底盘的尺寸也需要修改 因为车身是根据底盘制造的 车身也需要修改 因为整个车是根据车身来制造的 所以整个车也需要修改 此时我们发现 有一处小修改则整体要受到很大的改变 可维护性很低

    解决方案

    在上⾯的程序中, 我们是根据轮⼦的尺⼨设计的底盘,轮⼦的尺⼨⼀改,底盘的设计就得修改. 同样因为我们是根据底盘设计的⻋⾝,那么⻋⾝也得改,同理汽⻋设计也得改, 也就是整个设计⼏乎都得改~

    我们尝试换⼀种思路, 我们先设计汽⻋的⼤概样⼦,然后根据汽⻋的样⼦来设计⻋⾝,根据⻋⾝来设计底盘,最后根据底盘来设计轮⼦. 这时候,依赖关系就倒置过来了:轮⼦依赖底盘, 底盘依赖⻋⾝,⻋⾝依赖汽⻋
    在这里插入图片描述
    如何来实现呢:

    我们可以尝试不在每个类中⾃⼰创建下级类,如果⾃⼰创建下级类就会出现当下级类发⽣改变操作,⾃⼰也要跟着修改.此时,我们只需要将原来由⾃⼰创建的下级类,改为传递的⽅式(也就是注⼊的⽅式),因为我们不需要在当前类中创建下级类了,所以下级类即使发⽣变化(创建或减少参数),当前类本⾝也⽆需修改任何代码,这样就完成了程序的解耦.

    例如这种方式的开发 使程序之间实现解耦 就叫做IOC程序开发

    IOC容器
    在这里插入图片描述
    从上⾯也可以看出来, IoC容器具备以下优点:
    资源不由使⽤资源的双⽅管理,⽽由不使⽤资源的第三⽅管理,这可以带来很多好处。第⼀,资源集中管理,实现资源的可配置和易管理。第⼆,降低了使⽤资源双⽅的依赖程度,也就是我们说的耦合度。

    1. 资源集中管理: IoC容器会帮我们管理⼀些资源(对象等), 我们需要使⽤时, 只需要从IoC容器中去取
      就可以了
    2. 我们在创建实例的时候不需要了解其中的细节, 降低了使⽤资源双⽅的依赖程度, 也就是耦合度.
      Spring 就是⼀种IoC容器, 帮助我们来做了这些资源管理.

    DI

    DI: Dependency Injection(依赖注⼊)
    容器在运⾏期间, 动态的为应⽤程序提供运⾏时所依赖的资源,称之为依赖注⼊

    程序运行时需要某个资源,此时容器就为其提供这个资源
    从这点来看,依赖注入和控制反转是从不同角度描述同一件事情,就是通过引入IOC容器,利用依赖注入的方式,实现对对象的解耦
    ioc容器中存放着对象,而我们通过di的方式,来从容器中取出这个对象来使用

    IOC详解

    Spring 容器 管理的主要是对象, 这些对象, 我们称之为"Bean". 我们把这些对象交由Spring管理, 由Spring来负责对象的创建和销毁. 我们程序只需要告诉Spring, 哪些需要存, 以及如何从Spring中取出对象

    通过上⾯的案例, 我们已经知道了Spring IoC 和DI的基本操作, 接下来我们来系统的学习Spring IoC和DI
    的操作.前⾯我们提到IoC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对
    象。也就是bean的存储.

    Bean的存储

    在之前的⼊⻔案例中,要把某个对象交给IOC容器管理,需要在类上添加⼀个注解:@Component
    ⽽Spring框架为了更好的服务web应⽤程序, 提供了更丰富的注解

    共有两种注解可以实现

    1. 类注解: @Controller @Service @Repository @Component @Configuration
    2. 方法注解: @Bean

    @Controller(控制器存储)

    import org.springframework.stereotype.Controller;
    
    @Controller
    public class UserController {
        public void sayHi(){
            System.out.println("hi UserController");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如何观察这个对象已经存在于Spring容器中了呢?
    接下来学习如何从Spring容器中获取对象

    @SpringBootApplication
    public class DemoApplication {
    
        public static void main(String[] args) {
            //获取Spring上下文对象
            ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
            //从Spring上下文中获取对象
            UserController userController = context.getBean(UserController.class);
            //使用对象
            userController.sayHi();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    上述代码是根据类型来查找对象, 如果Spring容器中, 同⼀个类型存在多个bean的话, 怎么来获取呢?
    ApplicationContext 也提供了其他获取bean的⽅式, ApplicationContext 获取bean对象的功能, 是⽗类BeanFactory提供的功能
    在这里插入图片描述
    常用的是第一种 第二种 第五种

    第一种是根据bean名获取bean
    第二种是根据bean名和类型获取bean
    第五种是按照bean类型和构造函数参数动态创建bean 只适用于具有原型作用域的bean

    但是其中涉及到的bean 的名称 那么bean的名称是什么呢?

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

    1. 命名约定使用Java标准约定作为实例字段名,也就是说,bean的名称以小写字母开头,然后使用驼峰式大小写
      比如类名UserController Bean的 名称为userController
    2. 当有多个字符并且第一个和第二个字符都是大写时,将保留原始的大小写
      比如类名UController Bean 的名称为UController

    ApplicationContext 和 BeanFactory的对比

    1. 从继承关系和功能方面来说 Spring容器有两个顶级的接口 BeanFactory和ApplicationContext 其中BeanFactory提供基础的访问容器的能力 而ApplicationContext属于BeanFactory的子类,他除了继承BeanFactory的所有功能之外,还具有独特的特性,还添加了对国际化支持,资源访问支持,以及事件传播等方面的支持
    2. 从性能方面来说: ApplicationContext是一次性加载并初始化所有的Bean对象,而BeanFactory是需要哪个才去加载哪个,因此更加轻量

    @Service(服务存储)

    @Service
    public class UserService {
    
        public void sayHi(){
            System.out.println("hi UserService");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述
    在这里插入图片描述

    @Repository(仓库存储)

    @Repository
    public class UserRepository {
        public void sayHi(){
            System.out.println("hi UserRepository");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述
    在这里插入图片描述

    @Component(组件存储)

    @Component
    public class UserComponent {
        public void sayHi(){
            System.out.println("hi UserComponent");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述
    在这里插入图片描述

    @Configuration(配置存储)

    @Configuration
    public class UserConfiguration {
        public void sayHi(){
            System.out.println("hi UserConfiguration");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述
    在这里插入图片描述

    为什么需要这么多类注解

    这里的类注解和应用分层是呼应的,程序员在看到类注解之后,就能够直接了解当前类的用途

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

    在这里插入图片描述

    类注解之间的关系

    查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发现:其实这些注解里面都有一个注解@Component 说明他们本身是基于@Component的子类

    @Component 是⼀个元注解,也就是说可以注解其他类注解,如 @Controller , @Service ,@Repository 等. 这些注解被称为 @Component 的衍⽣注解.

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

    方法注解@Bean

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

    1. 使⽤外部包⾥的类, 没办法添加类注解
    2. ⼀个类, 需要多个对象, ⽐如多个数据源
      这种场景, 我们就需要使⽤⽅法注解 @Bean
    @Component
    public class BeanConfig {
        @Bean
        public User user(){
            User user = new User();
            user.setName("zhangsan");
            user.setAge(18);
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述
    在这里插入图片描述
    方法注解要配合类注解使用

    定义多个对象

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

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

    @Component
    public class BeanConfig {
        @Bean
        public User user1(){
            User user = new User();
            user.setName("zhangsan");
            user.setAge(18);
            return user;
        }
        
        @Bean
        public User user2(){
            User user = new User();
            user.setName("lisi");
            user.setAge(20);
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    当我们定义了多个对象时,我们根据类型获取对象,获取到的是哪个对象呢?
    在这里插入图片描述

    Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.demo.Model.User' available: expected single matching bean but found 2: user1,user2
    	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1273)
    	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:494)
    	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349)
    	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
    	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1178)
    	at com.example.demo.DemoApplication.main(DemoApplication.java:35)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    我们发现此时代码报错了,根据报错信息,期望只有一个匹配,结果发现了两个对象 user1和user2

    从报错信息中可以看出来,@Bean注解的Bean,Bean的名称就是他的方法名
    我们可以通过名称来获取Bean对象

    User user1 = (User) context.getBean("user1");
    User user2 = (User) context.getBean("user2");
    System.out.println(user1);
    System.out.println(user2);
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    重命名Bean

    可以通过设置name属性给Bean对象进行重命名操作
    在这里插入图片描述
    此时我们既可以通过user1这个名称获取User对象 也可以通过u1这个名称获取到User对象了

    name=可以省略
    在这里插入图片描述
    只有一个名称时 括号也可以省略
    在这里插入图片描述

    扫描路径

    使用四个注解声明的bean一定会生效吗? 答案是不一定 因为bean要想生效 还需要被Spring扫描

    使用五大注解声明的bean,要想生效,还需要配置扫描路径 让Spring扫描到这些注解

    也就是使用@ComponentScan来配置扫描路径

    为什么我们在之前没有配置扫描路径也可以扫描到bean呢?
    我们虽然没有显示的配置@ComponentScan,但是在启动类的注解上已经包含了这个注解了
    在这里插入图片描述
    默认的扫描范围是SpringBoot启动类所在包及其子包

    DI详解

    上面我们详解了控制反转IoC的细节,下面我们介绍DI的细节

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

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

    关于依赖注入,Spring也给我们提供了三种方式

    1. 属性注入
    2. 构造方法注入
    3. Setter注入

    属性注入

    属性注⼊是使⽤ @Autowired 实现的,将 Service 类注⼊到 Controller 类中。
    Service 类的实现代码如下:

    @Service
    public class UserService {
    
        public void sayHi(){
            System.out.println("hi UserService");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Controller类实现

    @Controller
    public class UserController {
    
        @Autowired
        //将UserService注入到Usercontroller中
        private UserService userService;
        public void sayHi(){
            System.out.println("hi UserController");
            userService.sayHi();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述
    在这里插入图片描述

    构造方法注入

    @Controller
    public class UserController {
        //方式二:构造方法注入
        //将UserService注入到Usercontroller中
        private UserService userService;
        @Autowired
        public UserController(UserService userService) {
            this.userService = userService;
        }
        
        public void sayHi(){
            System.out.println("hi UserController");
            userService.sayHi();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    注意事项:如果类只有⼀个构造⽅法,那么 @Autowired 注解可以省略;如果类中有多个构造⽅法,那么需要添加上 @Autowired 来明确指定到底使⽤哪个构造⽅法

    Setter注入

    注意事项:如果类只有⼀个构造⽅法,那么 @Autowired 注解可以省略;如果类中有多个构造⽅法,那么需要添加上 @Autowired 来明确指定到底使⽤哪个构造⽅法

    @Controller
    public class UserController {
        //方式三:Setter注入
        //将UserService注入到Usercontroller中
        private UserService userService;
    
        @Autowired
        public void setUserService(UserService userService) {
            this.userService = userService;
        }
        
        public void sayHi() {
            System.out.println("hi UserController");
            userService.sayHi();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    三种注入优缺点分析

    构造方法注入:

    优点:构造方法注入可以保证对象的完整性,因为它要求在创建对象时必须提供所需的属性值。这样可以避免对象处于不完整或不一致的状态。同时,构造方法注入也使得对象在创建后就可以是不可变的。
    缺点:构造方法注入在参数较多的情况下会显得冗长,特别是当属性较多、属性之间有依赖关系时。此外,对于每个依赖项,都需要提供一个相应的构造方法,这可能会导致类的构造方法过于臃肿。

    Setter方法注入:

    优点:Setter方法注入相对于构造方法注入,使用起来更加灵活,不需要在创建对象时传递所有依赖项。可以通过调用各个Setter方法来逐个注入属性。同时,Setter方法注入也便于后续修改或替换属性的值。
    缺点:Setter方法注入破坏了对象的完整性,因为依赖项可以在对象创建后的任何时候进行注入。这可能导致对象在某些操作中处于不一致的状态。此外,它也增加了类的可变性,可能导致对象的不可变性和线程安全性问题。

    字段注入:

    优点:字段注入简单直观,没有构造方法或Setter方法的冗长代码。对于小型的应用或快速原型开发,字段注入可能更加方便。
    缺点:字段注入破坏了封装性,将依赖项直接暴露为公共字段。这违反了面向对象编程的原则,可能导致代码可维护性和测试性的问题。此外,字段注入也不利于单元测试和模拟依赖项。

    @Autowired存在问题

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

    @Controller
    public class UserController {
        @Autowired
        private UserService userService;
    
        @Autowired
        private User user;
    
        public void sayHi() {
            System.out.println("hi UserController");
            userService.sayHi();
            System.out.println(user);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    报错的原因是,非唯一的Bean对象
    那么如何解决上述问题呢? Spring提供了以下几种解决方案

    @Primary

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

    @Component
    public class BeanConfig {
        @Primary
        @Bean("user1")
        public User user1(){
            User user = new User();
            user.setName("zhangsan");
            user.setAge(18);
            return user;
        }
    
        @Bean
        public User user2(){
            User user = new User();
            user.setName("lisi");
            user.setAge(20);
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    @Qualifier

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

    @Controller
    public class UserController {
        @Autowired
        private UserService userService;
    
        @Autowired
        @Qualifier("user2")
        private User user;
    
        public void sayHi() {
            System.out.println("hi UserController");
            userService.sayHi();
            System.out.println(user);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

    @Resource

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

    @Controller
    public class UserController {
        @Autowired
        private UserService userService;
    
        @Resource(name = "user2")
        private User user;
    
        public void sayHi() {
            System.out.println("hi UserController");
            userService.sayHi();
            System.out.println(user);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    @Autowird 与 @Resource的区别
    @Autowired 是spring框架提供的注解,⽽@Resource是JDK提供的注解
    @Autowired 默认是按照类型注⼊,⽽@Resource是按照名称注⼊

  • 相关阅读:
    django开发: 富文本编辑器TinyMCE的默认字体大小及一键排版功能
    span标签和p标签的区别
    JS——循环结构经典例题解析与分享
    vue组件通信6种方式总结(常问知识点)
    「运维有小邓」审核并分析文件和文件夹访问权限
    听GPT 讲Rust源代码--library/std(2)
    汽车电子电气TARA分析从入门到放弃
    java毕业设计调酒互动交流平台Mybatis+系统+数据库+调试部署
    js如何操作video标签
    【多线程与高并发】- 线程基础与状态
  • 原文地址:https://blog.csdn.net/m0_72670269/article/details/134296281