Spring之核心容器IoC/DI/基本操作详解
Spring框架的核心是IoC容器,它通过控制反转(IoC)和依赖注入(DI)实现对象的管理与依赖解耦,是Spring所有功能的基础。
一、核心概念:IoC与DI的本质
1.1 IoC(Inversion of Control,控制反转)
IoC是一种设计思想,核心是将对象的创建权由开发者转移给容器,实现“谁用谁创建”到“容器创建后注入”的转变。
传统开发模式(无IoC)
// 传统方式:开发者手动创建对象
public class UserService {
// 依赖UserDao,手动创建
private UserDao userDao = new UserDaoImpl();
public void addUser() {
userDao.insert(); // 调用依赖对象的方法
}
}
问题:
- 依赖硬编码(
new UserDaoImpl()
),若更换实现类(如UserDaoMybatisImpl
),需修改UserService
源码; - 对象创建与业务逻辑耦合,难以测试和扩展。
IoC模式(Spring容器管理)
// IoC方式:容器创建对象,开发者仅声明依赖
public class UserService {
// 依赖UserDao,由容器注入(无需手动new)
@Autowired
private UserDao userDao;
public void addUser() {
userDao.insert();
}
}
核心变化:
- 对象创建权转移:
UserDao
的实例由Spring容器创建,而非UserService
手动创建; - 依赖解耦:
UserService
仅依赖UserDao
接口,不依赖具体实现,更换实现类无需修改源码。
1.2 DI(Dependency Injection,依赖注入)
DI是IoC的具体实现方式,指容器在创建对象时,自动将依赖的对象注入到当前对象中。简单说:IoC是思想,DI是手段。
DI的三种实现方式
- 构造器注入:通过构造方法传入依赖对象;
- Setter注入:通过Setter方法设置依赖对象;
- 字段注入:通过注解直接标记字段(如
@Autowired
)。
后续会通过代码示例详细讲解这三种方式。
1.3 IoC容器的核心作用
Spring的IoC容器(如ApplicationContext
)本质是一个“对象工厂”,核心功能:
- 对象管理:创建、存储、销毁Bean(Spring对对象的称呼);
- 依赖注入:自动将依赖的Bean注入到目标对象;
- 生命周期管理:控制Bean的初始化、销毁等生命周期节点;
- 配置解析:读取XML、注解等配置,解析Bean的定义。
二、Spring容器的核心接口与实现类
Spring提供了两套核心容器接口:BeanFactory
和ApplicationContext
,后者是前者的增强版,实际开发中优先使用ApplicationContext
。
2.1 核心接口关系
BeanFactory(基础容器)
└── ApplicationContext(高级容器,继承BeanFactory)
├── ClassPathXmlApplicationContext(XML配置,类路径加载)
├── FileSystemXmlApplicationContext(XML配置,文件系统加载)
├── AnnotationConfigApplicationContext(注解配置)
└── WebApplicationContext(Web环境专用)
2.2 常用容器实现类
容器实现类 | 特点 | 适用场景 |
---|---|---|
ClassPathXmlApplicationContext |
从类路径加载XML配置文件 | 非Web项目,配置文件在src/main/resources |
AnnotationConfigApplicationContext |
基于注解配置(如@Configuration ) |
注解驱动开发,无XML配置 |
三、Bean的定义与依赖注入(DI)实战
3.1 环境准备
创建Maven项目,添加Spring核心依赖:
<dependencies>
<!-- Spring核心容器 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
</dependency>
</dependencies>
3.2 基于XML的Bean定义与注入
3.2.1 定义Bean(XML配置)
创建src/main/resources/spring.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">
<!-- 定义UserDao的Bean(id:唯一标识,class:全类名) -->
<bean id="userDao" class="com.example.dao.UserDaoImpl"/>
<!-- 定义UserService的Bean,并注入UserDao -->
<bean id="userService" class="com.example.service.UserService">
<!-- Setter注入:通过setUserDao方法注入userDao -->
<property name="userDao" ref="userDao"/>
</bean>
</beans>
3.2.2 目标类(UserDao、UserService)
// UserDao接口
public interface UserDao {
void insert();
}
// UserDao实现类
public class UserDaoImpl implements UserDao {
@Override
public void insert() {
System.out.println("UserDaoImpl:插入用户");
}
}
// UserService(需要注入UserDao)
public class UserService {
private UserDao userDao;
// Setter方法(用于Setter注入,方法名需对应XML中的property name)
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void addUser() {
userDao.insert(); // 调用注入的UserDao
}
}
3.2.3 启动容器并使用Bean
public class Main {
public static void main(String[] args) {
// 1. 加载Spring配置文件,创建容器(ApplicationContext是IoC容器的核心接口)
ApplicationContext context =
new ClassPathXmlApplicationContext("spring.xml");
// 2. 从容器中获取UserService(参数为XML中定义的id)
UserService userService = context.getBean("userService", UserService.class);
// 3. 调用方法(依赖的UserDao已被容器注入)
userService.addUser(); // 输出:UserDaoImpl:插入用户
}
}
3.3 基于注解的Bean定义与注入(推荐)
注解配置比XML更简洁,是现代Spring开发的主流方式。
3.3.1 核心注解
注解 | 作用 |
---|---|
@Component |
标记类为Bean(通用注解) |
@Repository |
标记DAO层Bean(@Component 的特例) |
@Service |
标记Service层Bean(@Component 的特例) |
@Controller |
标记Controller层Bean(Web环境) |
@Autowired |
自动注入依赖(默认按类型匹配) |
@Configuration |
标记配置类(替代XML配置文件) |
@ComponentScan |
扫描指定包下的注解Bean |
3.3.2 注解配置实战
// 1. 配置类(替代XML,扫描com.example包下的注解Bean)
@Configuration
@ComponentScan("com.example")
public class SpringConfig {
// 无需手动定义Bean,通过@Component等注解自动扫描
}
// 2. UserDaoImpl(用@Repository标记为Bean)
@Repository // 等价于<bean id="userDaoImpl" class="..."/>
public class UserDaoImpl implements UserDao {
@Override
public void insert() {
System.out.println("UserDaoImpl:插入用户");
}
}
// 3. UserService(用@Service标记,并通过@Autowired注入UserDao)
@Service // 等价于<bean id="userService" class="..."/>
public class UserService {
// 字段注入:直接在字段上标记@Autowired(无需Setter或构造器)
@Autowired
private UserDao userDao;
public void addUser() {
userDao.insert();
}
}
3.3.3 启动容器(基于注解配置)
public class Main {
public static void main(String[] args) {
// 加载注解配置类,创建容器
ApplicationContext context =
new AnnotationConfigApplicationContext(SpringConfig.class);
// 获取UserService(Bean id默认是类名首字母小写:userService)
UserService userService = context.getBean("userService", UserService.class);
userService.addUser(); // 输出:UserDaoImpl:插入用户
}
}
3.4 三种依赖注入方式对比
3.4.1 构造器注入(推荐)
通过构造方法注入依赖,确保对象创建时依赖已初始化:
@Service
public class UserService {
private final UserDao userDao;
// 构造器注入(@Autowired可省略,Spring 4.3+支持单构造器自动注入)
@Autowired
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
优势:
- 依赖不可变(
final
修饰),避免后续被修改; - 强制初始化依赖,防止
null
异常。
3.4.2 Setter注入
通过Setter方法注入,灵活性高(可在对象创建后修改依赖):
@Service
public class UserService {
private UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
优势:适合可选依赖(可设置默认值)。
3.4.3 字段注入(简洁但不推荐)
直接在字段上注入,代码简洁但存在缺陷:
@Service
public class UserService {
@Autowired
private UserDao userDao; // 字段注入
}
缺陷:
- 无法注入
final
字段(构造器注入可以); - 依赖隐藏在字段中,不通过构造器或方法暴露,可读性差;
- 不利于单元测试(难以手动注入模拟对象)。
四、Spring容器的基本操作
4.1 容器的创建与关闭
创建容器
// 1. 基于XML(类路径)
ApplicationContext context =
new ClassPathXmlApplicationContext("spring.xml");
// 2. 基于XML(文件系统路径)
ApplicationContext context =
new FileSystemXmlApplicationContext("D:/spring.xml");
// 3. 基于注解配置类
ApplicationContext context =
new AnnotationConfigApplicationContext(SpringConfig.class);
关闭容器
ApplicationContext
无直接关闭方法,需通过ConfigurableApplicationContext
:
ConfigurableApplicationContext context =
new ClassPathXmlApplicationContext("spring.xml");
// 关闭容器(触发Bean的销毁方法)
context.close();
4.2 获取Bean的三种方式
// 1. 通过id获取(返回Object,需强转)
UserService userService1 = (UserService) context.getBean("userService");
// 2. 通过id+类型获取(推荐,无需强转)
UserService userService2 = context.getBean("userService", UserService.class);
// 3. 通过类型获取(适合单实例Bean,存在多个同类型Bean时报错)
UserService userService3 = context.getBean(UserService.class);
4.3 Bean的作用域(Scope)
Spring默认创建的Bean是单实例(singleton),可通过@Scope
指定作用域:
@Service
@Scope("prototype") // 多实例:每次获取Bean时创建新对象
public class UserService { ... }
常用作用域:
作用域 | 说明 | 适用场景 |
---|---|---|
singleton |
单实例(默认),容器启动时创建 | 无状态Bean(如Service、Dao) |
prototype |
多实例,每次获取时创建 | 有状态Bean(如Model、View) |
request |
每个HTTP请求创建一个实例(Web环境) | Web应用请求相关Bean |
session |
每个会话创建一个实例(Web环境) | Web应用会话相关Bean |
4.4 Bean的生命周期
Spring容器管理Bean的完整生命周期:
- 实例化:创建Bean对象(调用构造方法);
- 属性注入:注入依赖的Bean;
- 初始化:执行初始化方法(如
@PostConstruct
); - 使用:Bean可被容器获取并使用;
- 销毁:容器关闭时执行销毁方法(如
@PreDestroy
)。
生命周期示例
@Service
public class UserService {
// 1. 实例化(构造方法)
public UserService() {
System.out.println("UserService:构造方法(实例化)");
}
// 2. 属性注入(@Autowired)
@Autowired
private UserDao userDao;
// 3. 初始化方法(@PostConstruct标记)
@PostConstruct
public void init() {
System.out.println("UserService:初始化");
}
// 5. 销毁方法(@PreDestroy标记)
@PreDestroy
public void destroy() {
System.out.println("UserService:销毁");
}
}
执行结果:
UserService:构造方法(实例化)
UserService:初始化 // 容器启动时执行
// 使用Bean...
UserService:销毁 // 容器关闭时执行
五、常见问题与避坑指南
5.1 Bean的命名冲突
当容器中存在多个同类型Bean时,注入会报错NoUniqueBeanDefinitionException
:
// 两个UserDao实现类
@Repository
public class UserDaoImpl1 implements UserDao { ... }
@Repository
public class UserDaoImpl2 implements UserDao { ... }
// 注入时冲突
@Service
public class UserService {
@Autowired // 报错:存在两个UserDao Bean
private UserDao userDao;
}
解决方案:
- 用
@Qualifier
指定Bean的id:
@Autowired
@Qualifier("userDaoImpl1") // 指定注入id为userDaoImpl1的Bean
private UserDao userDao;
- 用
@Primary
标记优先注入的Bean:
@Repository
@Primary // 优先注入
public class UserDaoImpl1 implements UserDao { ... }
5.2 循环依赖问题
两个Bean互相依赖(A依赖B,B依赖A)会导致循环依赖:
@Service
public class AService {
@Autowired
private BService bService;
}
@Service
public class BService {
@Autowired
private AService aService;
}
解决方案:
- 用
@Lazy
延迟注入(打破即时依赖):
@Service
public class AService {
@Autowired
@Lazy // 延迟注入BService
private BService bService;
}
- 改用Setter注入(构造器注入无法解决循环依赖)。
5.3 单实例Bean的线程安全问题
单实例Bean(默认)在多线程环境下,若存在共享状态(如成员变量),会有线程安全问题:
@Service
public class UserService {
// 共享状态(多线程访问会冲突)
private int count = 0;
public void increment() {
count++; // 线程不安全操作
}
}
解决方案:
- 避免共享状态(推荐):单实例Bean设计为无状态(不定义成员变量);
- 改用
prototype
作用域(不推荐,性能差); - 使用线程安全容器(如
ThreadLocal
)。
总结:Spring核心容器通过IoC和DI实现了对象的“按需创建”和“自动注入”:
- 依赖解耦:对象之间仅依赖接口,不依赖具体实现,降低耦合度;
- 简化开发:开发者无需关注对象创建和依赖管理,专注业务逻辑;
- 可扩展性:通过配置或注解轻松更换Bean实现,无需修改业务代码;
- 生命周期管理:容器统一管理Bean的创建、初始化、销毁,便于资源控制。
掌握Spring容器的核心是理解“容器是对象的管理者”:它创建对象、注入依赖、控制生命周期,是整个Spring生态的基础。后续学习Spring的AOP、事务等功能,都需要以容器为基础。
若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ