Spring IOC 详解
IOC(Inversion of Control,控制反转)是Spring框架的核心概念,它通过依赖注入(DI)实现了对象创建和依赖关系的管理,让开发者专注于业务逻辑的实现。
IOC = 控制反转 + 依赖注入 + 对象管理 + 松耦合设计
- 🔄 控制反转:将对象的创建和管理权交给容器
- 💉 依赖注入:通过构造函数、setter或字段自动注入依赖
- 🧩 对象管理:统一管理Bean的生命周期和作用域
- 🔗 松耦合设计:降低组件间的耦合度,提高可维护性
1. IOC基础概念
1.1 什么是IOC?
IOC是一种设计模式,它将对象的创建和依赖关系的管理从代码中分离出来,交给容器来处理。这样可以降低代码的耦合度,提高代码的可维护性和可测试性。
传统方式 vs IOC方式
- 传统方式
- IOC方式
1// 传统方式 - 紧耦合2public class UserService {3 private UserRepository userRepository;4 5 public UserService() {6 // 直接创建依赖对象7 this.userRepository = new UserRepositoryImpl();8 }9 10 public void createUser(User user) {11 userRepository.save(user);12 }13}1// IOC方式 - 松耦合2@Service3public class UserService {4 @Autowired5 private UserRepository userRepository;6 7 public void createUser(User user) {8 userRepository.save(user);9 }10}1112@Repository13public class UserRepositoryImpl implements UserRepository {14 public void save(User user) {15 // 保存用户逻辑16 }17}1.2 IOC容器
Spring IOC容器负责管理对象的生命周期和依赖关系。Spring提供了两种容器类型:BeanFactory和ApplicationContext。
| 特性 | BeanFactory | ApplicationContext |
|---|---|---|
| 加载方式 | 懒加载 | 预加载 |
| 国际化 | 不支持 | 支持 |
| 事件发布 | 不支持 | 支持 |
| 资源访问 | 有限支持 | 完全支持 |
| 适用场景 | 资源受限环境 | 大多数应用 |
1// 创建IOC容器2ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);34// 从容器获取Bean5UserService userService = context.getBean(UserService.class);67// 使用Bean8userService.createUser(new User("张三"));2. 依赖注入方式
Spring提供多种依赖注入方式,每种方式都有其适用场景。
2.1 构造器注入
- 代码实现
- 优缺点
1@Service2public class UserService {3 private final UserRepository userRepository;4 private final EmailService emailService;5 6 // 构造器注入7 public UserService(UserRepository userRepository, EmailService emailService) {8 this.userRepository = userRepository;9 this.emailService = emailService;10 }11 12 public void createUser(User user) {13 userRepository.save(user);14 emailService.sendWelcomeEmail(user.getEmail());15 }16}构造器注入优缺点
优点:
- 保证依赖不可变
- 确保必需的依赖在对象创建时可用
- 支持final字段,避免后续修改
- 便于单元测试
缺点:
- 当依赖过多时,构造函数参数列表可能过长
- 可能导致循环依赖问题
2.2 Setter注入
- 代码实现
- 优缺点
1@Service2public class UserService {3 private UserRepository userRepository;4 private EmailService emailService;5 6 // Setter注入7 @Autowired8 public void setUserRepository(UserRepository userRepository) {9 this.userRepository = userRepository;10 }11 12 @Autowired13 public void setEmailService(EmailService emailService) {14 this.emailService = emailService;15 }16}Setter注入优缺点
优点:
- 适用于可选依赖
- 可以在运行时更改依赖
- 避免循环依赖问题
缺点:
- 不能确保依赖不为null
- 不能使用final字段
- 需要更多的代码
2.3 字段注入
- 代码实现
- 优缺点
1@Service2public class UserService {3 @Autowired4 private UserRepository userRepository;5 6 @Autowired7 private EmailService emailService;8 9 public void createUser(User user) {10 userRepository.save(user);11 emailService.sendWelcomeEmail(user.getEmail());12 }13}字段注入优缺点
优点:
- 代码简洁
- 减少样板代码
缺点:
- 不利于单元测试
- 不能使用final字段
- 容易导致过度依赖
- 隐藏依赖关系
- 构造器注入:推荐使用,可以确保依赖不为null,支持不可变对象
- Setter注入:适用于可选依赖,当依赖有合理默认值时
- 字段注入:简单但不推荐,不利于测试和不可变性,隐藏依赖关系
3. Bean生命周期
3.1 Bean生命周期阶段
Spring Bean的生命周期包含多个阶段,从实例化到销毁,容器会在各个阶段提供回调机制。
完整生命周期示例代码
1@Component2public class UserService implements InitializingBean, DisposableBean {3 4 public UserService() {5 System.out.println("1. 构造函数 - 实例化");6 }7 8 @Autowired9 public void setUserRepository(UserRepository userRepository) {10 System.out.println("2. 设置属性 - 依赖注入");11 }12 13 @PostConstruct14 public void init() {15 System.out.println("3. @PostConstruct - 初始化");16 }17 18 @Override19 public void afterPropertiesSet() throws Exception {20 System.out.println("4. InitializingBean.afterPropertiesSet()");21 }22 23 public void customInit() {24 System.out.println("5. 自定义初始化方法");25 }26 27 public void businessMethod() {28 System.out.println("6. 业务方法 - 使用Bean");29 }30 31 @PreDestroy32 public void preDestroy() {33 System.out.println("7. @PreDestroy - 销毁前");34 }35 36 @Override37 public void destroy() throws Exception {38 System.out.println("8. DisposableBean.destroy()");39 }40 41 public void customDestroy() {42 System.out.println("9. 自定义销毁方法");43 }44}3.2 生命周期回调
| 阶段 | 回调方法 | 说明 |
|---|---|---|
| 实例化 | 构造器 | 创建Bean实例 |
| 属性注入 | setter方法 | 注入依赖属性 |
| 初始化前 | BeanPostProcessor | 前置处理器 |
| 初始化 | @PostConstruct | 注解方式初始化 |
| 初始化 | InitializingBean | 接口方式初始化 |
| 初始化 | 自定义init方法 | XML或注解配置 |
| 初始化后 | BeanPostProcessor | 后置处理器 |
| 使用 | 业务方法 | Bean正常使用 |
| 销毁前 | @PreDestroy | 注解方式销毁前 |
| 销毁 | DisposableBean | 接口方式销毁 |
| 销毁 | 自定义destroy方法 | XML或注解配置 |
4. Bean作用域
4.1 作用域类型
Spring提供了多种Bean作用域,用于控制Bean实例的生命周期和可见性。
- 单例
- 原型
- Web作用域
1@Component2@Scope("singleton") // 默认作用域,可以省略3public class SingletonService {4 // 整个容器只有一个实例5 6 public SingletonService() {7 System.out.println("创建SingletonService");8 }9 10 public void doSomething() {11 System.out.println("SingletonService doing something");12 }13}1@Component2@Scope("prototype")3public class PrototypeService {4 // 每次获取都创建新实例5 6 public PrototypeService() {7 System.out.println("创建PrototypeService");8 }9 10 public void doSomething() {11 System.out.println("PrototypeService doing something");12 }13}1@Component2@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)3public class SessionService {4 // 每个会话一个实例5 6 public SessionService() {7 System.out.println("创建SessionService");8 }9}1011@Component12@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)13public class RequestService {14 // 每个请求一个实例15 16 public RequestService() {17 System.out.println("创建RequestService");18 }19}4.2 作用域特点
| 作用域 | 说明 | 特点 | 使用场景 |
|---|---|---|---|
| singleton | 单例 | 整个容器只有一个实例 | 无状态服务、配置类 |
| prototype | 原型 | 每次获取都创建新实例 | 有状态对象、线程安全 |
| request | 请求 | 每个HTTP请求一个实例 | Web请求处理器 |
| session | 会话 | 每个HTTP会话一个实例 | 用户会话状态 |
| application | 应用 | 每个ServletContext一个实例 | 全局Web资源 |
| websocket | 连接 | 每个WebSocket连接一个实例 | WebSocket处理器 |
当单例Bean依赖非单例Bean时,需要使用代理模式,否则非单例Bean将失去其作用域特性。使用proxyMode = ScopedProxyMode.TARGET_CLASS来解决此问题。
5. 配置方式
Spring提供了三种主要的Bean配置方式,可以根据需求选择合适的方式。
5.1 注解配置
- 组件扫描
- @Bean方法
1@Configuration2@ComponentScan("com.example")3public class AppConfig {4 // 配置类5}67@Service8public class UserService {9 @Autowired10 private UserRepository userRepository;11}1213@Repository14public class UserRepositoryImpl implements UserRepository {15 // 实现16}1@Configuration2public class AppConfig {3 4 @Bean5 public UserRepository userRepository() {6 return new UserRepositoryImpl();7 }8 9 @Bean10 public EmailService emailService() {11 return new EmailServiceImpl();12 }13 14 @Bean15 public UserService userService() {16 return new UserService(userRepository(), emailService());17 }18}5.2 XML配置
XML配置示例
1<?xml version="1.0" encoding="UTF-8"?>2<beans xmlns="http://www.springframework.org/schema/beans"3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"4 xsi:schemaLocation="http://www.springframework.org/schema/beans5 http://www.springframework.org/schema/beans/spring-beans.xsd">6 7 <bean id="userRepository" class="com.example.repository.UserRepositoryImpl"/>8 9 <bean id="emailService" class="com.example.service.EmailServiceImpl"/>10 11 <bean id="userService" class="com.example.service.UserService">12 <constructor-arg ref="userRepository"/>13 <constructor-arg ref="emailService"/>14 </bean>15 16</beans>5.3 Java配置
1@Configuration2public class AppConfig {3 4 @Bean5 public UserRepository userRepository() {6 return new UserRepositoryImpl();7 }8 9 @Bean10 public EmailService emailService() {11 return new EmailServiceImpl();12 }13 14 @Bean15 public UserService userService(UserRepository userRepository, EmailService emailService) {16 return new UserService(userRepository, emailService);17 }18}6. 高级特性
6.1 条件化Bean
Spring提供了条件化配置机制,允许根据特定条件创建Bean。
条件化Bean示例
1@Configuration2public class AppConfig {3 4 @Bean5 @ConditionalOnProperty(name = "database.type", havingValue = "mysql")6 public DataSource mysqlDataSource() {7 return new MysqlDataSource();8 }9 10 @Bean11 @ConditionalOnProperty(name = "database.type", havingValue = "postgresql")12 public DataSource postgresqlDataSource() {13 return new PostgresqlDataSource();14 }15 16 @Bean17 @ConditionalOnClass(name = "com.mysql.jdbc.Driver")18 public JdbcTemplate jdbcTemplate(DataSource dataSource) {19 return new JdbcTemplate(dataSource);20 }21}常用条件注解
| 条件注解 | 说明 | 用途 |
|---|---|---|
@ConditionalOnBean | 当存在指定Bean时 | 依赖其他Bean |
@ConditionalOnMissingBean | 当不存在指定Bean时 | 默认配置 |
@ConditionalOnClass | 当存在指定类时 | 依赖外部库 |
@ConditionalOnMissingClass | 当不存在指定类时 | 兼容性配置 |
@ConditionalOnProperty | 当存在指定属性时 | 配置驱动 |
@ConditionalOnExpression | 当表达式为true时 | 复杂条件判断 |
6.2 Bean后处理器
Bean后处理器允许在Bean初始化前后进行自定义处理。
1@Component2public class CustomBeanPostProcessor implements BeanPostProcessor {3 4 @Override5 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {6 System.out.println("Bean初始化前: " + beanName);7 return bean;8 }9 10 @Override11 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {12 System.out.println("Bean初始化后: " + beanName);13 return bean;14 }15}6.3 工厂Bean
工厂Bean用于创建复杂的Bean实例,隐藏实例化细节。
1@Component2public class UserServiceFactoryBean implements FactoryBean<UserService> {3 4 @Override5 public UserService getObject() throws Exception {6 // 创建复杂的UserService实例7 UserService userService = new UserService();8 // 进行一些复杂的初始化9 return userService;10 }11 12 @Override13 public Class<?> getObjectType() {14 return UserService.class;15 }16 17 @Override18 public boolean isSingleton() {19 return true;20 }21}6.4 循环依赖
Spring通过三级缓存解决单例Bean的循环依赖问题。
循环依赖示例与解决过程
1@Service2public class ServiceA {3 @Autowired4 private ServiceB serviceB;5 6 public void methodA() {7 serviceB.methodB();8 }9}1011@Service12public class ServiceB {13 @Autowired14 private ServiceA serviceA;15 16 public void methodB() {17 serviceA.methodA();18 }19}解决过程:
- 创建ServiceA实例
- 发现依赖ServiceB,将ServiceA放入三级缓存
- 创建ServiceB实例
- 发现依赖ServiceA,尝试从缓存获取
- 从三级缓存获取ServiceA的早期引用
- 完成ServiceB的依赖注入
- 完成ServiceA的依赖注入
7. 面试题精选
7.1 基础概念题
- IOC概念
- IOC容器
Q: 什么是IOC?它的优势是什么?
A: IOC(控制反转)是一种设计模式,它将对象的创建和依赖关系的管理从代码中分离出来。优势包括:
- 降低耦合度:对象之间的依赖关系由容器管理
- 提高可维护性:修改依赖关系不需要修改业务代码
- 提高可测试性:可以轻松替换依赖进行测试
- 简化代码:专注于业务逻辑实现
Q: Spring IOC容器的核心接口有哪些?
A: Spring IOC容器的核心接口包括:
- BeanFactory:基础容器接口,提供基本的IOC功能
- ApplicationContext:BeanFactory的子接口,提供更多企业级功能
- ConfigurableApplicationContext:可配置的应用上下文
- WebApplicationContext:Web应用的应用上下文
- AnnotationConfigApplicationContext:基于注解的配置上下文
7.2 实践题
- 依赖注入
- Bean作用域
Q: Spring中有哪些依赖注入方式?
A: Spring提供三种依赖注入方式:
- 构造器注入:通过构造器注入依赖,推荐使用
- Setter注入:通过setter方法注入依赖
- 字段注入:直接在字段上使用@Autowired注解
Q: Bean的作用域有哪些?
A: Spring Bean的作用域包括:
- singleton:单例(默认),整个容器只有一个实例
- prototype:原型,每次获取都创建新实例
- request:请求作用域,每个HTTP请求一个实例
- session:会话作用域,每个HTTP会话一个实例
- application:应用作用域,每个ServletContext一个实例
- websocket:WebSocket作用域,每个WebSocket连接一个实例
7.3 高级题
- 生命周期
- 循环依赖
Q: Spring Bean的生命周期是怎样的?
A: Spring Bean的生命周期包括:
- 实例化:创建Bean实例
- 属性注入:注入依赖属性
- BeanPostProcessor前置处理:在初始化前进行处理
- 初始化回调:执行初始化回调方法(@PostConstruct、InitializingBean等)
- BeanPostProcessor后置处理:在初始化后进行处理
- 使用:Bean正常使用
- 销毁回调:执行销毁回调方法(@PreDestroy、DisposableBean等)
Q: 如何解决循环依赖问题?
A: Spring通过三级缓存解决循环依赖:
- 一级缓存:singletonObjects,存放完全初始化好的Bean
- 二级缓存:earlySingletonObjects,存放早期暴露的Bean(未完全初始化)
- 三级缓存:singletonFactories,存放Bean的工厂对象
通过提前暴露Bean的引用解决循环依赖。注意,这种机制只能解决单例Bean的setter注入循环依赖,不能解决构造器注入的循环依赖和原型Bean的循环依赖。
- 理解核心概念:掌握IOC、DI、Bean等基本概念
- 熟悉注入方式:学会构造器、Setter、字段注入
- 掌握生命周期:了解Bean的创建、初始化、销毁过程
- 学会配置方式:掌握注解、XML、Java配置
- 了解高级特性:学会条件化Bean、后处理器等
通过本章的学习,你应该已经掌握了Spring IOC的核心概念、依赖注入方式和Bean生命周期管理。IOC是Spring框架的基础,掌握IOC对于理解整个Spring生态系统至关重要。在实际项目中,合理使用IOC可以构建出松耦合、易维护的应用程序。
评论