Elon Musk 讲到如何快速学习新知识,假如要学习火箭发动机,传统的理念是,先学习有关火箭的所有知识,比如有哪些型号的螺丝刀,最后从0-1造火箭发动机。而效率更高的学习方式,是拿过来一个火箭发动机,把他拆解开,看看有哪些组成部分。怎么拆?需要A型号的螺丝刀来拆开发动机。Opps!A型号的螺丝刀有这个作用。
P.S. 我将以这种方法向大家讲解java的常用框架,以及设计模式,有兴趣的可以点个关注,会持续不定期更新,本系列举例由chatGPT生成
相比传统的学习方式,大多的学习Spring的blog都从DI和AOP的概念讲起,哪怕举例一些例子,依然觉得有些抽象。
现在我从问题出发,倒着学习spring
Question 1:有DI和没有DI对我写代码有什么影响(我们先不说DI具体的概念)
a、没有DI
假设有一个订单服务(OrderService),它需要使用用户服务(UserService)来获取用户信息。在没有使用 Spring 框架的情况下,我们可能会这样实现:
public class OrderService {
private UserService userService;
public OrderService() {
this.userService = new UserService(); // 手动创建 UserService 实例
}
public void processOrder(int userId, String productId) {
// 根据用户ID获取用户信息
User user = userService.getUserById(userId);
// 处理订单逻辑
// ...
}
}
在上面的例子中,OrderService 类在构造函数中手动创建了 UserService 实例,并使用该实例来获取用户信息。这种方式存在一些问题,比如:
- OrderService 类与 UserService 类之间的耦合度较高,不利于代码的维护和扩展。
- 如果 UserService 类的实现发生变化,需要修改 OrderService 类的构造函数,违反了开闭原则。
- 单元测试时难以进行模块的替换和测试。
b、引入DI
public class OrderService {
private UserService userService;
// 使用构造函数注入依赖对象
public OrderService(UserService userService) {
this.userService = userService;
}
public void processOrder(int userId, String productId) {
// 根据用户ID获取用户信息
User user = userService.getUserById(userId);
// 处理订单逻辑
// ...
}
}
在上面的例子中,OrderService 类的构造函数接受一个 UserService 类的实例作为参数,并将该实例赋值给类的成员变量。这样,Spring 框架在创建 OrderService 实例时会自动注入 UserService 实例,实现了依赖注入。这种方式可以减少类之间的耦合度,提高代码的可维护性和可测试性。
这时,就算我不说DI是什么,大家也知道DI有什么作用了,保险起见,还是写一下概念
依赖注入(Dependency Injection): Spring 框架引入了依赖注入(DI)机制,通过将组件之间的依赖关系委托给容器管理,实现了松耦合和可测试性。开发人员不再需要手动管理对象之间的依赖关系,而是通过配置文件或注解告诉 Spring 框架如何注入依赖,从而简化了代码和提高了灵活性。
Question 2:这时我们应该继续发问,为什么spring框架可以实现DI?
我们首先将每个类想象成高达的组件,有的是小拇指,有的是手掌,拼高达时,我们要把小拇指拼到手掌上。说明手掌需要小拇指,手掌是主组件,小拇指是普通组件。
举个新的例子,UserController类调用了UserService,UserController是主组件,UserService是普通组件,我们用@Controller和@Component来表示主组件和普通组件。对了,这些组件有个统一的名字,Bean
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
private UserService userService;
// 构造函数注入
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
public String displayUserInfo() {
return userService.getUserInfo();
}
}
public class UserService {
public String getUserInfo() {
return "User information from UserService";
}
}
贴上八股:在实际开发中,@Component
和 @Controller
注解都可以用来标识组件,它们的作用是相同的,都能够让 Spring 框架自动扫描并注册为 Bean。但是,为了更好地表达代码的用途和意图,建议按照它们的语义规范来使用,即 @Component
用于普通的组件类,@Controller
用于标识控制器类。这样可以使代码更加清晰易懂,符合开发规范
回到问题,spring框架把每个类型变成一个组件,相互依赖时,只要把两块组件拼起来就行。
贴上八股
Spring 框架通过控制反转(IoC)实现了对象的创建、管理和依赖注入。以下是 Spring 实现 IoC 的主要步骤和原理:
-
配置元数据: 在 Spring 中,通常通过 XML 文件、Java 注解或者 Java 代码来配置应用程序的组件和它们之间的依赖关系。这些配置信息称为配置元数据。
-
Bean 定义: 在配置元数据中,需要明确指定哪些类是需要由 Spring 管理的 Bean,并且提供了 Bean 的属性、构造函数参数、依赖关系等信息。在 Spring 中,可以使用
<bean>
标签(XML 配置)、@Component
注解(注解配置)或者@Bean
注解(Java 代码配置)来定义 Bean。 -
Bean 工厂: Spring 框架会读取配置元数据,并根据配置信息来创建和管理 Bean。Spring 的 Bean 工厂负责实例化 Bean,并将它们放入容器中。
-
依赖注入: 当 Bean 被创建并放入容器中后,Spring 框架会根据配置信息自动注入 Bean 之间的依赖关系。依赖注入的方式可以通过构造函数注入、Setter 方法注入、字段注入等方式来实现。
-
生命周期管理: Spring 容器管理 Bean 的生命周期,包括初始化、销毁等阶段。可以通过配置初始化方法和销毁方法来管理 Bean 的生命周期。
总的来说,Spring 实现 IoC 的关键在于它通过配置元数据和 Bean 工厂来管理应用程序的组件,并根据依赖关系自动注入 Bean,从而实现了对象的解耦合、灵活性和可测试性。通过控制反转,Spring 将对象的创建和依赖关系的管理从应用程序中解耦出来,交给了 Spring 容器来管理,从而降低了应用程序的耦合度,提高了代码的可维护性和可测试性。