文章目录
- 一、初识 Spring
- 1.1 什么是 Spring
- 1.2 什么是 容器
- 1.3 什么是 IoC
- 二、对 IoC 的深入理解
- 2.1 传统程序开发方式存在的问题
- 2.2 控制反转式程序的开发
- 2.3 对比总结
- 三、对 Spring IoC 的理解
- 四、DI 的概念
- 4.1 什么是 DI
- 4.2 DI 与 IoC的关系
一、初识 Spring
1.1 什么是 Spring
Spring 是一个开源的轻量级 Java 框架,旨在简化 Java 应用程序的开发。它提供了一个全面的编程和配置模型,用于构建各种类型的应用,包括企业级应用和独立的 Java 应用。Spring 的设计理念是基于依赖注入(Dependency Injection)和面向切面编程(Aspect-Oriented Programming),使得应用开发更加模块化、灵活和易于维护。
用一句话概括 Spring 就是:Spring 是一个包含了众多工具的 IoC 容器
。
1.2 什么是 容器
所谓容器就是能够盛放东西的器皿,就好像水杯一样。而 Spring 就是一个容器,它的功能就是负责存储和管理应用中的各个组件(Bean),然后在使用的时候从 Spring 中取出需要的 Bean 对象。
1.3 什么是 IoC
IoC,即控制反转(Inversion of Control),是 Spring 框架的核心理念之一。它将应用程序的控制权从开发人员的手中反转,由 Spring 容器负责管理对象的生命周期以及依赖关系。在传统的程序设计中,开发人员需要负责手动创建和管理对象,但是在 IoC 容器中,开发人员只需要定义组件(Bean)的配置元数据,由容器负责实例化、装配和管理这些组件。
二、对 IoC 的深入理解
如果初次理解 IoC,可能会觉得很困难,可以通过下面的例子帮助我们理解 IoC 的作用。
假如,现在需要使用程序来模拟一个简单制造汽车的过程,其实现思路如下:
- 如果要造汽车Car,首先需要有车身Framework;
- 如果需要造车身Framework,首先就需要有地盘Bottom;
- 如果需要造地盘Bottom,首先就需要有轮子Tire。
上述过程,不难发现存在一个依赖链的关系,首先通过传统程序的开发方式,来感受一下其存在的问题。
2.1 传统程序开发方式存在的问题
下面是用传统方式编写的代码:
class Tire {
private Integer size = 17;
public void init(){
System.out.println("do tire: size = " + size);
}
}
class Bottom {
private Tire tire;
public Bottom(){
this.tire = new Tire();
}
public void init(){
tire.init();
System.out.println("do bottom");
}
}
class Framework {
private Bottom bottom;
public Framework(){
bottom = new Bottom();
}
public void init(){
bottom.init();
System.out.println("do framework");
}
}
class Car {
private Framework framework;
public Car(){
framework = new Framework();
}
public void init(){
framework.init();
System.out.println("do car");
}
}
public class Tradition {
public static void main(String[] args) {
Car car = new Car();
car.init();
}
}
在上面的传统写法中,可以发现每个类之间的耦合度非常高,并且每个类都需要管理它所依赖的对象,即掌握着其依赖对象的控制权。此时如果用户的需求改变了,使用的轮子Tire不再是固定的尺寸,而是需要由用户自己输入尺寸的大小,此时Tire类的代码需要更改为:
class Tire {
private Integer size = 17;
public Tire(int size){
this.size = size;
}
public void init(){
System.out.println("do tire: size = " + size);
}
}
此时发现,不改不要紧,但是一改问题就来了,由于各个类之间的高耦合性,使得后续所依赖前者的代码都需要进行修改。
class Bottom {
private Tire tire;
public Bottom(int size){
this.tire = new Tire(size);
}
public void init(){
tire.init();
System.out.println("do bottom");
}
}
class Framework {
private Bottom bottom;
public Framework(int size){
bottom = new Bottom(size);
}
public void init(){
bottom.init();
System.out.println("do framework");
}
}
class Car {
private Framework framework;
public Car(int size){
framework = new Framework(size);
}
public void init(){
framework.init();
System.out.println("do car");
}
}
如果要后续要继续增加 Tire 类的属性,例如color,此时就还要需要从头来进行修改。那么如何解决这个缺陷呢?
上次代码出现这个缺陷的根本原因就在于:当前类将自己所依赖的类的控制权掌握在了自己的手中,即在自己类的内部调用的所依赖类的构造方法。因此解决这个问题的方法也非常简单,那就是不在自己的内部代码中创建所依赖的对象,而是通过参数传递的方式获取这个依赖对象,这就是控制权转移的思想,即 IoC。
2.2 控制反转式程序的开发
下面是转移控制权的代码写法,即将当前类所依赖的对象通过参数的方式进行传入:
class Tire {
private Integer size = 17;
public Tire(int size) {
this.size = size;
}
public void init() {
System.out.println("do tire: size = " + size);
}
}
class Bottom {
private Tire tire;
public Bottom(Tire tire) {
this.tire = tire;
}
public void init() {
tire.init();
System.out.println("do bottom");
}
}
class Framework {
private Bottom bottom;
public Framework(Bottom bottom) {
this.bottom = bottom;
}
public void init() {
bottom.init();
System.out.println("do framework");
}
}
class Car {
private Framework framework;
public Car(Framework framework) {
this.framework = framework;
}
public void init() {
framework.init();
System.out.println("do car");
}
}
public class IoC {
public static void main(String[] args) {
Tire tire = new Tire(18, "red");
Bottom bottom = new Bottom(tire);
Framework framework = new Framework(bottom);
Car car = new Car(framework);
car.init();
}
}
此时,如果用户需求又发生了改变,要求轮子 Tire 的颜色也要自己来挑选,此时更改的代码如下:
class Tire {
private Integer size = 17;
private String color;
public Tire(int size, String color) {
this.size = size;
this.color = color;
}
public void init() {
System.out.println("do tire: size = " + size + ", color = " + color);
}
}
此时,只需要修改 Tire 类和创建 Tire 对象的代码,而不需要对其他代码进行修改。可以发现,即使底层类的改变也不会影响到整个调用链的改变,这样就实现了代码的解耦,从而实现了更加灵活、通用的程序设计方式。
2.3 对比总结
以下是传统代码和 IoC 形式代码的调用过程:
通过对比两种代码的实现方式不难发现:
- 两种代码中类的创建顺序是相反的:
- 传统代码的创建顺序是由顶层 Car 类创建到底层 Tire 类,即:
Car -> Framework -> Bottom -> Tire
; - 而 IoC 形式代码的创建顺序则是由底层 Tire 类创建到顶层 Car 类,即:
Tire -> Bottom -> Frame -> Car
。
- 传统代码的创建顺序是由顶层 Car 类创建到底层 Tire 类,即:
- 传统代码中,当前类掌握着自己所依赖对象的控制权,耦合度非常高,一旦底层代码修改了,整个调用链的代码都会修改。
- IoC 形式的代码中,当前类通过参数传递的方式,将对所依赖对象的控制权进行反转,即不再有自己掌控,此时无论底层代码如何修改,也不会对整个调用链上的代码造成影响。
三、对 Spring IoC 的理解
“Spring 是一个 包含众多工具的 IoC 容器” 这句话的理解非常重要,因为它揭示了 Spring 框架最核心的功能和优势。在这里,我们可以把 Spring IoC 容器比喻成一个大大的容器,用来存放应用程序中的各种对象(Bean)。这个容器不仅负责对象生命周期的管理,还实现了对象之间的依赖注入(DI),从而实现了控制反转。
既然 Spring 是容器,那么它的核心功能就可以分为两个:
1. 将对象存入容器
- 程序员首先需要创建应用程序中的各种对象(Bean),这些对象代表应用程序中的不同组件和功能模块。
- 然后通过配置文件(如XML、注解或者 Java 配置类)告诉 Spring IoC 容器如何创建这些对象,以及它们之间的依赖关系。
- Spring IoC 容器会根据配置信息,在应用程序启动的时候负责实例化这些对象并存放到容器中,从而完成对象的创建和组装过程。
2. 从容器中取出对象
- 一旦对象被存入了 Spring IoC 容器中,它们就可以在应用程序的其他部分被获取和使用。程序员不需要显示地创建对象,而是从 Spring 容器中直接获取已经创建好的对象来使用。
- 通过依赖注入的方式,Spring IoC 容器会在合适的时候将依赖的对象自动注入到需要它们的地方。
总的来说,Spring IoC 容器负责管理对象的创建、组装和依赖关系,开发人员只需关注对象的定义和配置。通过将对象存入容器并从容器中获取对象,Spring 实现了对象的控制反转,使得应用程序的开发更加简洁、灵活和易于维护。这也是 Spring 框架的核心之一,为开发者提供了一个强大且高度可定制的开发平台。
四、DI 的概念
4.1 什么是 DI
依赖注入(Dependency Injection,DI)是一种实现 IoC 的具体技术,它是 IoC 容器的一种表现形式。在 DI 中,对象之间的依赖关系不再有类自己创建和管理,而是有外部容器(如 Spring)来负责注入依赖的对象。简单来说,DI 就是将一个对象的依赖关系交给外部容器来处理,从而实现对象之间的解耦。
在 DI 中,通常由三种注入方式:
- 构造方法注入(Constructor Injection):通过构造函数接收依赖对象,这是最常见的注入方式。
- Setter 方法注入(Setter Injection):通过Setter方法设置依赖对象。
- 属性注入(Field Injection):通过属性的方式注入依赖对象。
DI 使得对象的依赖关系从代码中移出,变得可配置和灵活。通过使用DI,我们可以在应用程序的不同部分之间实现松耦合,提高代码的可测试性、可维护性和可扩展性。
4.2 DI 与 IoC的关系
DI(依赖注入)和 IoC(控制反转)是紧密相关的概念,通常同时被提及,它们之间的关系可以概括为:
- IoC 是一种设计思想,它将应用程序的控制权从代码内部转移到外部容器。通过 IoC,对象的创建和依赖关系的管理被反转,交由容器来负责管理,而不是由代码显示的控制。
- DI 是实现 IoC 的一种具体技术手段,它是 IoC 的一种表现形式。通过 DI,对象之间的依赖关系由外部容器来注入,而不是由对象自己来创建和管理依赖。
因此,DI 是 IoC 的一部分,它是实现 IoC 的重要手段。Spring 框架正是以 IoC 和 DI 为核心,提供了强大的 IoC 容器和依赖注入机制,从而实现了各种功能,如依赖管理、AOP、事务管理等。通过 IoC 和 DI,Spring 框架实现了松耦合、可配置和可扩展的应用程序开发。