本文收录于「Java 学习日记」专栏,聚焦 Spring 框架的两大核心 ------IOC(控制反转)与 DI(依赖注入),从底层原理拆解到实战落地,帮你打通 Spring 框架的入门关键一步~
一、为什么要学 Spring 框架?
在上一篇手动实现 MVC 框架的过程中,我们遇到了一个核心痛点:对象依赖管理混乱。
- 手动
new UserServiceImpl()创建对象,组件之间耦合度极高(修改实现类需改代码); - 每个 Controller 都要手动创建 Service 对象,重复代码多,且对象生命周期无法统一管理;
- 业务扩展时,对象依赖关系越复杂,手动维护成本越高。
而Spring 框架的核心价值就是解决这些问题:
- IOC(控制反转):将对象的创建权从程序员手中 "反转" 给 Spring 容器,由容器统一管理对象的生命周期;
- DI(依赖注入):Spring 容器自动将对象的依赖(如 Service 注入到 Controller)注入到目标对象中,无需手动 new;
- 核心目标:降低代码耦合度,实现组件解耦、可复用、可扩展,是 Java EE 开发的事实标准。
今天这篇日记,我们从 IOC/DI 的核心原理入手,手把手完成 Spring 框架的入门实战,彻底搞懂 "容器如何管理对象"。
二、核心概念:IOC 与 DI 的本质
1. 什么是 IOC(控制反转)?
IOC(Inversion of Control)------ 控制反转,是一种设计思想,核心是 **"对象的创建权反转"**:
- 传统开发 :程序员主动
new创建对象(控制权在程序员); - Spring 开发:程序员只定义对象,由 Spring 容器负责创建、管理、销毁对象(控制权在容器)。
用通俗的例子理解:
- 传统方式:你(程序员)亲自去菜市场买菜(new 对象),还要自己洗、切(管理对象);
- Spring 方式:你告诉餐厅(Spring 容器)要吃什么(定义对象),餐厅帮你准备好所有食材(创建 / 管理对象),你只需直接烹饪(使用对象)。
2. 什么是 DI(依赖注入)?
DI(Dependency Injection)------ 依赖注入,是 IOC 思想的具体实现方式:
- 当一个对象(如 UserController)依赖另一个对象(如 UserService)时,Spring 容器会自动将依赖的对象 "注入" 到目标对象中,无需手动
new; - 核心:"谁需要,容器就给谁",彻底消除对象之间的手动依赖。
3. IOC/DI 的核心优势
| 特性 | 传统开发 | Spring 开发(IOC+DI) |
|---|---|---|
| 对象创建 | 手动 new,耦合高 | 容器创建,耦合低 |
| 依赖管理 | 手动维护,易出错 | 容器自动注入,无需关心细节 |
| 生命周期 | 手动控制,重复代码多 | 容器统一管理(初始化 / 销毁) |
| 扩展性 | 修改实现类需改代码 | 配置文件 / 注解修改,无需改代码 |
三、Spring 入门实战:环境准备与核心配置
1. 开发环境准备
- JDK:8 及以上(推荐 JDK 8);
- 构建工具:Maven(推荐)或 Gradle;
- IDE:IDEA(推荐)或 Eclipse;
- Spring 版本:5.3.x(稳定版)。
2. Maven 依赖配置(核心)
新建 Maven 项目,在pom.xml中添加 Spring 核心依赖:
xml
XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>spring-ioc-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 依赖版本管理 -->
<properties>
<spring.version>5.3.29</spring.version>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<!-- Spring核心依赖 -->
<dependencies>
<!-- Spring Context(包含IOC容器核心功能) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Spring Beans(对象管理核心) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Spring Core(核心工具类) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Spring Expression Language(EL表达式) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</project>
3. 编写核心组件(Service 层)
3.1 业务接口:UserService
java
运行
java
// src/main/java/com/example/service/UserService.java
package com.example.service;
/**
* 业务接口(面向接口编程,降低耦合)
*/
public interface UserService {
void sayHello(String username);
}
3.2 业务实现类:UserServiceImpl
java
运行
java
// src/main/java/com/example/service/impl/UserServiceImpl.java
package com.example.service.impl;
import com.example.service.UserService;
/**
* 业务实现类(由Spring容器管理)
*/
public class UserServiceImpl implements UserService {
// 无参构造(Spring创建对象默认调用无参构造)
public UserServiceImpl() {
System.out.println("UserServiceImpl被创建了!");
}
@Override
public void sayHello(String username) {
System.out.println("Hello " + username + "!这是Spring IOC容器管理的对象");
}
}
4. Spring 配置文件(核心)
在src/main/resources目录下创建 Spring 核心配置文件applicationContext.xml,用于定义需要由容器管理的对象(Bean):
xml
XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定义Bean:让Spring容器管理UserServiceImpl对象 -->
<!-- id:Bean的唯一标识,class:Bean的全类名 -->
<bean id="userService" class="com.example.service.impl.UserServiceImpl"></bean>
</beans>
5. 测试 IOC 容器获取对象
编写测试类,通过 Spring 容器获取对象,而非手动 new:
java
运行
java
// src/main/java/com/example/test/SpringIocTest.java
package com.example.test;
import com.example.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringIocTest {
public static void main(String[] args) {
// 1. 加载Spring配置文件,创建IOC容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2. 从容器中获取Bean对象(两种方式)
// 方式1:根据id获取(需手动强转)
UserService userService1 = (UserService) context.getBean("userService");
// 方式2:根据id+类型获取(无需强转,推荐)
UserService userService2 = context.getBean("userService", UserService.class);
// 3. 使用对象
userService1.sayHello("Java日记");
userService2.sayHello("Spring学习者");
// 4. 验证:容器中是单例对象(默认)
System.out.println(userService1 == userService2); // 输出true
}
}
6. 运行结果与解析
运行测试类,控制台输出:
plaintext
UserServiceImpl被创建了!
Hello Java日记!这是Spring IOC容器管理的对象
Hello Spring学习者!这是Spring IOC容器管理的对象
true
核心解析:
UserServiceImpl被创建了!:Spring 容器启动时,自动创建了userService对象(默认单例);userService1 == userService2为 true:Spring 默认创建单例 Bean,多次获取的是同一个对象;- 全程没有
new UserServiceImpl():对象创建权完全由 Spring 容器掌控,实现了 IOC。
四、DI 依赖注入实战:自动注入依赖对象
1. 场景准备:Controller 依赖 Service
新建UserController,依赖UserService,我们通过 DI 让 Spring 自动注入UserService,而非手动 new:
java
运行
java
// src/main/java/com/example/controller/UserController.java
package com.example.controller;
import com.example.service.UserService;
public class UserController {
// 依赖的UserService(由Spring注入)
private UserService userService;
// 提供setter方法(用于Set注入)
public void setUserService(UserService userService) {
this.userService = userService;
}
// 业务方法
public void hello(String username) {
userService.sayHello(username);
}
}
2. 配置 DI(Set 注入)
修改applicationContext.xml,配置UserController的 Bean,并注入userService依赖:
xml
XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 1. 定义Service Bean -->
<bean id="userService" class="com.example.service.impl.UserServiceImpl"></bean>
<!-- 2. 定义Controller Bean,并注入Service依赖(Set注入) -->
<bean id="userController" class="com.example.controller.UserController">
<!-- property:指定要注入的属性名,name=属性名,ref=依赖的Bean id -->
<property name="userService" ref="userService"></property>
</bean>
</beans>
3. 测试 DI 注入效果
修改测试类,获取UserController并调用方法:
java
运行
java
// src/main/java/com/example/test/SpringDiTest.java
package com.example.test;
import com.example.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringDiTest {
public static void main(String[] args) {
// 1. 加载配置文件,创建容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2. 获取Controller对象(依赖已自动注入)
UserController controller = context.getBean("userController", UserController.class);
// 3. 调用方法(无需手动注入Service)
controller.hello("DI依赖注入");
}
}
运行结果:
plaintext
UserServiceImpl被创建了!
Hello DI依赖注入!这是Spring IOC容器管理的对象
核心解析:
UserController的userService属性由 Spring 自动注入,无需手动new UserServiceImpl();- 只需在配置文件中通过
<property>标签声明依赖关系,Spring 容器会自动完成注入,实现了解耦。
4. 常用 DI 注入方式(扩展)
除了 Set 注入,Spring 还支持构造器注入、注解注入(后续重点讲),这里补充构造器注入:
4.1 修改 UserController(添加有参构造)
java
运行
java
public class UserController {
private UserService userService;
// 构造器注入:通过构造方法注入依赖
public UserController(UserService userService) {
this.userService = userService;
}
public void hello(String username) {
userService.sayHello(username);
}
}
4.2 配置构造器注入
xml
<bean id="userController" class="com.example.controller.UserController">
<!-- constructor-arg:构造器参数,ref=依赖的Bean id -->
<constructor-arg ref="userService"></constructor-arg>
</bean>
五、注解开发:简化 IOC/DI 配置(推荐)
XML 配置方式需要编写大量标签,Spring 提供了注解方式,大幅简化配置:
1. 开启注解扫描
修改applicationContext.xml,添加注解扫描配置(扫描指定包下的注解):
xml
XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启注解扫描:扫描com.example包下的所有注解 -->
<context:component-scan base-package="com.example"></context:component-scan>
</beans>
2. 使用注解定义 Bean
2.1 Service 层添加@Service注解
java
运行
java
import org.springframework.stereotype.Service;
// @Service:标记为Service层Bean,等价于XML中的<bean>标签
// 不指定value时,Bean id默认为类名首字母小写(userServiceImpl)
@Service("userService")
public class UserServiceImpl implements UserService {
@Override
public void sayHello(String username) {
System.out.println("注解版:Hello " + username);
}
}
2.2 Controller 层添加@Controller+@Autowired注解
java
运行
java
import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
// @Controller:标记为Controller层Bean
@Controller("userController")
public class UserController {
// @Autowired:自动注入依赖(按类型匹配),无需setter/构造器
@Autowired
private UserService userService;
public void hello(String username) {
userService.sayHello(username);
}
}
3. 测试注解开发效果
测试类代码不变,运行后输出:
plaintext
UserServiceImpl被创建了!
注解版:Hello 注解开发
核心解析:
@Service/@Controller:替代 XML 的<bean>标签,告诉 Spring 容器管理该对象;@Autowired:替代 XML 的<property>/<constructor-arg>,自动注入依赖,无需手动配置;- 注解开发大幅简化配置,是实际开发中的主流方式。
六、IOC/DI 核心细节与避坑指南
1. Bean 的作用域(Scope)
Spring 默认创建单例 Bean(singleton),可通过@Scope指定作用域:
java
运行
java
// 原型模式:每次获取都创建新对象
@Service
@Scope("prototype")
public class UserServiceImpl implements UserService {
// ...
}
常用作用域:
| 作用域 | 说明 |
|---|---|
| singleton | 单例(默认),容器启动时创建,全局唯一 |
| prototype | 原型,每次 getBean () 创建新对象 |
| request | Web 环境,每个请求创建一个 Bean |
| session | Web 环境,每个会话创建一个 Bean |
2. 常见错误与解决方案
| 错误场景 | 原因 | 解决方案 |
|---|---|---|
| NoSuchBeanDefinitionException | 找不到指定 id 的 Bean | 检查 Bean id 是否正确,包是否扫描到 |
| NoUniqueBeanDefinitionException | 同类型 Bean 有多个,无法自动注入 | 使用@Qualifier("beanId")指定 Bean |
| NullPointerException | 依赖未注入,调用时为 null | 确保@Autowired注解生效,包扫描正确 |
3. 关键注意点
@Autowired默认按类型 注入,若同类型有多个 Bean,需配合@Qualifier("beanId")按名称注入;- Spring 创建 Bean 默认调用无参构造 ,若只有有参构造,需添加
@Autowired或配置构造器注入; - 注解扫描必须配置
context:component-scan,否则注解无效; - 单例 Bean 是线程不安全的,不要在单例 Bean 中定义成员变量(如用户信息)。
七、今日实战小任务
- 基于注解开发,新增
UserDao层,让UserService依赖UserDao,通过@Autowired注入; - 测试 Bean 的
singleton和prototype作用域,验证单例 / 原型的区别; - 尝试使用
@Resource注解(JDK 注解)替代@Autowired,对比两者的区别。
总结
- IOC(控制反转)是将对象创建权交给 Spring 容器,替代手动 new,核心是 "容器管理对象";
- DI(依赖注入)是 IOC 的实现方式,Spring 容器自动将依赖对象注入到目标对象中,实现解耦;
- Spring 开发有 XML 和注解两种方式,注解(
@Service/@Controller/@Autowired)是主流,大幅简化配置; - Bean 默认单例,可通过
@Scope指定作用域,实际开发中需注意线程安全问题。
下一篇【Day39】预告:SpringMVC 核心详解:DispatcherServlet、@RequestMapping 与请求参数处理,关注专栏从 Spring 核心过渡到 Web 框架~若本文对你有帮助,欢迎点赞 + 收藏 + 关注,你的支持是我更新的最大动力💖!