Spring IoC:彻底搞懂控制反转
控制反转(IoC)。这是一个听起来很高大上、很抽象,但实际上非常朴实和重要的概念。
一、核心思想:一句话概括
控制反转(IoC)是一种软件设计原则,其核心思想是:将对象的创建、依赖装配和生命周期管理的控制权,从应用程序代码中“反转”到一个外部容器(例如 Spring 框架)来负责。
简单说,就是 “别new了,我来给你”。
二、通过一个比喻来理解:传统方式 vs. IoC方式
想象一下你去餐厅吃饭:
1. 传统开发方式(控制正转):
- 你(应用程序代码) 说:“我想吃牛排”。
- 然后你自己 去菜市场买牛肉、买调料、生火、自己煎牛排、自己摆盘。
- 你控制了整个过程, tightly coupled(紧耦合),非常麻烦,而且一旦想吃猪排,所有流程都得重来。
// 类似于传统代码:自己在内部创建所有依赖
public class MyService {// 自己创建依赖对象private MyRepository myRepository = new MyRepository();public void doBusiness() {myRepository.queryData();}
}
问题:MyService
牢牢控制着 MyRepository
的创建。如果想换一个 NewRepositoryImpl
,就必须修改 MyService
的源代码。这违反了“开闭原则”。
2. IoC方式:
- 你(应用程序代码) 说:“我想吃牛排”。
- 服务员(IoC容器) 听到后,直接通知后厨做好一份标准的牛排,然后端给你(注入给你)。
- 你不需要关心牛排是怎么来的,你只负责吃(使用)就行了。控制权从“你”反转到了“餐厅系统”。
// 使用IoC的代码:依赖由外部容器提供(注入)
public class MyService {// 不自己创建,只声明一个依赖private MyRepository myRepository;// 容器通过这个构造方法将MyRepository“注入”进来public MyService(MyRepository myRepository) {this.myRepository = myRepository;}public void doBusiness() {myRepository.queryData();}
}
好处:MyService
不再关心 MyRepository
是谁实现的、怎么创建的。它只依赖于 MyRepository
这个抽象(接口)。之后你想换一种 MyRepository
的实现,根本不需要改动 MyService
的代码,只需要告诉容器(通过配置)用什么实现就行了。这就是 “松耦合”。
三、IoC 和 DI(依赖注入)是什么关系?
这是一个非常常见的疑问。
-
IoC(控制反转):是一个广义的概念,一种设计思想。它描述了“控制权被反转”这一现象。实现IoC思想有很多种方式,例如:
- 模板方法模式(Template Method)
- 服务定位器模式(Service Locator)
- 依赖注入(Dependency Injection)
-
DI(依赖注入):是实现IoC最常用、最主流的一种技术手段。它特指“通过外部将依赖对象注入到组件中”的这种具体做法。
所以,可以这样理解:
IoC 是目的(目标),DI 是手段(实现方式)。
Spring 框架通过 DI 这种方式来实现 IoC 思想。因此,我们平时在 Spring 中所说的 IoC,实际上指的就是 DI。
四、依赖注入(DI)的几种方式
Spring 容器负责创建 Bean(对象),并注入它们的依赖。主要有三种注入方式:
1. 构造器注入(推荐的方式)
@Component
public class MyService {private final MyRepository myRepository;// 容器会自动找到MyRepository的实例,并通过构造器注入进来@Autowired // Spring 4.3 后,如果只有一个构造器,@Autowired可省略public MyService(MyRepository myRepository) {this.myRepository = myRepository;}
}
优点:
- 保证依赖不可变(
final
关键字)。 - 保证依赖不为空(
null
)。 - 完全初始化的状态,更安全。
2. Setter 方法注入
@Component
public class MyService {private MyRepository myRepository;// 容器会通过调用这个setter方法进行注入@Autowiredpublic void setMyRepository(MyRepository myRepository) {this.myRepository = myRepository;}
}
优点:可选依赖或需要重新配置依赖时比较灵活。
3. 字段注入(不推荐用于主要业务代码)
@Component
public class MyService {@Autowired // 直接注入到字段上private MyRepository myRepository;
}
缺点:
- 绕过构造方法,可能导致依赖不为空(
null
)的假设失效。 - 让代码与Spring框架紧密耦合(因为直接用了
@Autowired
注解)。 - 无法设置
final
字段,对象状态可能可变。 - 不利于单元测试(你必须通过反射来注入依赖)。
现代最佳实践是使用构造器注入。
五、Spring IoC 容器是什么?
Spring IoC 容器就是这个“餐厅系统”/“服务员”。它的核心是 ApplicationContext
接口。
它的工作流程:
- 配置元数据:你通过 XML、Java 配置或注解(如
@Component
,@Service
,@Repository
)告诉容器:“请管理这些类”。 - 启动容器:容器启动后,会根据配置信息创建并组装好所有对象(这些对象被称为 Bean),放在一个“池子”里。
- 等待索取/注入:当你的应用程序需要某个 Bean 时(比如通过
@Autowired
),容器会自动将它已经创建好的那个对象“注入”到需要的地方。
总结:为什么使用IoC?(优点)
- 降低耦合度:组件不关心依赖的具体实现,只关心接口。代码变得灵活,易于维护和扩展。
- 提高可测试性:可以轻松地将真实依赖替换为 Mock对象 进行单元测试。例如,给
MyService
注入一个假的MyRepository
来测试业务逻辑。 - 管理生命周期:容器可以统一管理对象的生命周期(如初始化、销毁)。
- 集中配置:所有对象的创建和组装逻辑都集中在容器中,而不是散落在代码的各个角落,更易于管理。
总而言之,IoC 是一种让代码变得更灵活、更松散耦合、更易于测试的强大设计思想,而 Spring 框架通过依赖注入(DI)完美地实现了它。 你不再是自己“new”对象的苦力,而是变成一个“提出需求”,由容器(Spring)来满足的架构师。