前言:
在接触 Spring 框架之前,通常我们会在
main
方法或其他业务逻辑中手动new
对象,然后调用这些对象的方法来完成任务。手动创建对象的方式意味着我们自己掌握了对象的控制权。然而,在 Spring 中,我们不再直接在代码中手动创建对象,而是将对象的创建、管理、依赖注入等职责交给了 Spring 容器。Spring 框架通过 IoC(控制反转)和 DI(依赖注入)来实现这一点。
大家伙,这次封面是我把标题发给ai,让它给我画的。你们觉得怎么样哈哈哈~
目录
Setter 方法注入中的Autowired 是否可以省略?
一、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..."); } }
依赖注入的流程
Spring 启动: Spring 应用启动时,会进行类路径扫描,找到所有标记了
@Component
、@Service
、@Repository
、@Controller
等注解的类。Bean 的创建: Spring 容器会根据这些注解来创建相应的 Bean。例如,
@Service
标注的UserService
类会被实例化为一个 Bean。注入依赖: 当 Spring 容器需要创建
UserController2
的实例时,发现它的构造函数需要一个UserService
类型的参数。Spring 容器会自动查找已经创建的UserService
Bean,并将其传递给UserController2
的构造函数,完成依赖注入。完成注入:
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 容器将依赖关系从对象自身转移到容器中管理,从而实现了控制反转。这种模式允许对象之间的依赖关系在运行时由容器注入,而不是在代码中直接创建依赖对象。