深入理解 Spring IoC 与 DI:控制反转与依赖注入解析

前言:

       在接触 Spring 框架之前,通常我们会在 main 方法或其他业务逻辑中手动 new 对象,然后调用这些对象的方法来完成任务。手动创建对象的方式意味着我们自己掌握了对象的控制权

      然而,在 Spring 中,我们不再直接在代码中手动创建对象,而是将对象的创建、管理、依赖注入等职责交给了 Spring 容器。Spring 框架通过 IoC(控制反转)和 DI(依赖注入)来实现这一点。

      大家伙,这次封面是我把标题发给ai,让它给我画的。你们觉得怎么样哈哈哈~


目录

一、IoC 控制反转

1.字面意思:

2.含义:

3.举个栗子~

二、依赖注入(DI)

1.定义与概念:

2.依赖注入的具体实现: 

a:构造方法注入

依赖注入的流程

b:Setter 方法注入

Setter 方法注入中的Autowired 是否可以省略?

c:属性注入

三、3种DI方式对比

1. 构造函数注入(Spring4.X推荐)

2. Setter 方法注入(Spring 3.X推荐) 

 3. 属性注入(Field Injection)

四、IOC和DI的关系

五、IoC使用了哪些设计模式?


一、IoC 控制反转

1.字面意思:

Inversion of Control :控制权的反转

2.含义:

       是一种设计原则/思想,其核心是将程序中的对象创建、依赖管理和流程控制从应用程序中剥离出来,交给外部容器(如 Spring 框架)管理。IoC 的目的是减少代码耦合,提高代码的可重用性、可测试性和灵活性。


3.举个栗子~

传统编程模型:

在没有 IoC 的情况下,HelloController 类会自己创建 HelloWorld 的实例,控制着依赖的创建和管理。

//一个用来打招呼的类
class HelloWorld {
    public void sayHello() {
        System.out.println("Hello, World!");
    }

}

public class HelloController {
    private HelloWorld helloWorld;

    public HelloController() {
        this.helloWorld = new HelloWorld(); // 自己创建依赖
    }

    public void sayHello() {
        helloWorld.sayHello();
    }
}

这种情况下,HelloController 完全控制了 HelloWorld 的创建过程,这是一种正向控制。

IoC 编程模型:

通过 Spring IoC 容器,这种控制反转了。HelloController 不再创建 HelloWorld,而是通过依赖注入的方式,由外部(Spring 容器)来注入 HelloWorld 的实例。

@Data
@Component
class HelloWorld {
    public void sayHello() {
        System.out.println("Hello, World!");
    }

}


@Controller
public class HelloController {
    @Autowired
    private HelloWorld helloWorld;

    @PostConstruct
    public void sayHello() {
       helloWorld.sayHello();
    }
  
}

控制权被“反转”到了 Spring 框架,Spring 框架决定何时创建、注入和销毁对象,应用程序代码只是被动接收


二、依赖注入(DI)

1.定义与概念

      依赖注入(DI)是控制反转(IoC)的主要实现方式,它允许将对象的依赖关系从外部传入,而不是在对象内部自行创建。

      通过依赖注入,Spring 容器负责管理和注入对象的依赖,使得对象的创建和依赖关系不再由开发者手动管理,而是交由容器自动处理。

2.依赖注入的具体实现: 

a:构造方法注入

        构造⽅法注⼊是在类的构造方法中实现注⼊,这种方式确保了在对象创建时,所有的依赖都已经满足。

代码示例:

@Controller
public class UserController2 {

    private final UserService userService;

    @Autowired
    public UserController2(UserService userService) {
        this.userService = userService;
    }

    public void sayHi() {
        System.out.println("hi, UserController2...");
        userService.sayHi();
    }

单构造函数@Autowired 是可选的,因为 Spring 会自动使用唯一的构造函数。

多构造函数@Autowired 是必须的,用来明确指定哪个构造函数应该被用来进行依赖注入。


import org.springframework.stereotype.Service;

@Service
public class UserService {
    public void sayHi() {
        System.out.println("hi, UserService...");
    }
}
依赖注入的流程
  1. Spring 启动: Spring 应用启动时,会进行类路径扫描,找到所有标记了 @Component@Service@Repository@Controller 等注解的类。

  2. Bean 的创建: Spring 容器会根据这些注解来创建相应的 Bean。例如,@Service 标注的 UserService 类会被实例化为一个 Bean。

  3. 注入依赖: 当 Spring 容器需要创建 UserController2 的实例时,发现它的构造函数需要一个 UserService 类型的参数。Spring 容器会自动查找已经创建的 UserService Bean,并将其传递给 UserController2 的构造函数,完成依赖注入。

  4. 完成注入: UserController2 被创建后,它内部的 userService 属性就被赋值为由 Spring 管理的 UserService 实例,从而可以在方法中直接使用 userService


b:Setter 方法注入

      通过 Setter 方法来注入依赖,允许在对象创建后,注入依赖。依赖可以在对象创建之后灵活地进行设置,适合需要更改依赖关系的场景。

代码示例:    

@Controller
public class UserController3 {
    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void sayHi(){
        System.out.println("hi, UserController3...");
        userService.sayHi();
    }
}
Setter 方法注入中的Autowired 是否可以省略?

Autowired的作用:

@Autowired 注解明确告诉 Spring 容器,这个 Setter 方法需要注入依赖。

省略 @Autowired 的结果:

       如果省略 @Autowired,Spring 将不会识别这个方法为依赖注入的候选方法,因此不会自动注入依赖。userService 字段将不会被注入任何值,这可能导致 userService 为空,最终引发NullPointerException。

和构造函数注入的对比:

       在构造函数注入中,Spring 可以在类只有一个构造函数时,自动识别该构造函数并注入依赖,因此 @Autowired 是可选的。
       但在 Setter 方法注入中,Spring 并不会自动识别 Setter 方法为依赖注入点,因此 @Autowired 注解是必须的。


c:属性注入

      属性注入是直接在类的字段上使用 @Autowired 注解,将依赖注入到该属性。这是最简洁的一种方式,但在一些场景下可能不如前两种方式灵活。

@Controller
public class UserController {
 //属性注⼊ 
 @Autowired
 private UserService userService;
 public void sayHi(){
 System.out.println("hi,UserController...");
 userService.sayHi();
 }
}

三、3种DI方式对比

1. 构造函数注入(Spring4.X推荐)

优点:

强制依赖:所有依赖在对象创建时必须提供,确保对象总是处于完全初始化的状态。
不可变性:依赖关系在对象创建后通常不可更改,可以注⼊final修饰的属性。
通用性好:构造⽅法是JDK⽀持的,所以更换任何框架,他都是适⽤。

缺点: 

如果类有很多依赖,构造函数可能变得冗长,增加代码复杂性。

2. Setter 方法注入(Spring 3.X推荐) 

优点:

可选依赖:允许在对象创建之后设置或更改依赖,依赖关系可以在运行时动态修改。

缺点: 

1.可能导致对象在依赖注入之前处于不完整的状态,增加了出错的可能性。
2.不能注⼊⼀个Final修饰的属性。

3.注⼊对象可能会被改变,因为setter⽅法可能会被多次调⽤,就有被修改的风险。

 3. 属性注入(Field Injection)

特点:

1.直接在类的字段上注入依赖,代码简洁。
2.隐藏依赖关系,这种做法使得依赖关系不在类的接口(如构造函数或公开的 Setter 方法)中显式展示。

缺点:  

对象可能在未完全初始化时被使用,可能导致 `NullPointerException` 等问题。
不支持不可变性,不能注⼊⼀个Final修饰的属性


四、IOC和DI的关系

1.IoC 是理念,DI 是实现: IoC 是一种更广泛的设计理念,而 DI 是实现 IoC 的一种方式。通过 DI,应用程序将控制反转给了 IoC 容器。

2.DI 是 IoC 的具体体现: 当我们谈论 IoC 时,通常是指通过 DI 来实现的控制反转。通过依赖注入,IoC 的理念在代码中得以实现。


五、IoC使用了哪些设计模式?

工厂模式:

      IoC 容器本质上是一个工厂。它负责创建和管理 Bean 的实例,并根据配置决定如何实例化和初始化这些 Bean。工厂模式帮助将对象的创建逻辑从使用对象的代码中分离出来,使得对象的创建和管理集中在容器中。

依赖注入

      依赖注入是 IoC 的核心实现方式。通过 DI,IoC 容器将依赖关系从对象自身转移到容器中管理,从而实现了控制反转。这种模式允许对象之间的依赖关系在运行时由容器注入,而不是在代码中直接创建依赖对象。

  • 18
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值