一、Spring 框架概述:为什么它成为 Java 开发的事实标准?
Spring 框架自 2003 年首次发布以来,已经成为 Java 企业级开发的基石。它以 "轻量级"、"非侵入式" 的设计理念,解决了传统 Java 开发中组件耦合紧密、配置繁琐等问题,极大地提高了开发效率和系统可维护性。
Spring 的核心价值体现在以下几个方面:
- 控制反转(IoC):将对象的创建和依赖管理交给容器,降低组件间耦合
- 面向切面编程(AOP):分离业务逻辑和横切关注点(如日志、事务)
- 一站式解决方案:提供数据访问、Web 开发、安全等全方位功能
- 生态系统丰富:与 Spring Boot、Spring Cloud 等项目无缝衔接
- 开放性:可以与其他框架(如 MyBatis、Hibernate)轻松集成
本文将从 Spring 最核心的 IoC 容器和依赖注入讲起,帮助你打下坚实的 Spring 基础。
二、控制反转(IoC):Spring 的灵魂所在
2.1 什么是控制反转?
控制反转(Inversion of Control)是一种设计思想,它颠覆了传统的程序设计模式。在传统开发中,对象的创建和依赖关系的维护完全由开发者在代码中控制;而在 IoC 模式下,这些责任被转移到了容器(在 Spring 中就是 IoC 容器)。
传统方式:
java
运行
// 手动创建对象
UserService userService = new UserServiceImpl();
// 手动设置依赖
userService.setUserDao(new UserDaoImpl());
IoC 方式:
java
运行
// 从容器获取对象,无需关心创建过程和依赖
UserService userService = context.getBean(UserService.class);
这种 "控制权" 的转移带来了诸多好处:
- 降低组件间的耦合度
- 提高代码的可测试性
- 简化对象的创建和管理
- 便于实现组件的复用和替换
2.2 IoC 容器的工作原理
Spring 的 IoC 容器负责对象的创建、配置和管理,其核心实现是ApplicationContext。容器的工作流程可分为三个阶段:
-
初始化阶段:
- 加载配置元数据(XML、注解或 Java 配置)
- 解析配置信息,生成 Bean 定义
- 注册 Bean 定义到容器中
-
Bean 创建阶段:
- 根据 Bean 定义创建对象(实例化)
- 为对象设置属性(依赖注入)
- 调用初始化方法
-
使用与销毁阶段:
- 应用程序从容器获取 Bean 并使用
- 容器关闭时,调用 Bean 的销毁方法
2.3 核心容器接口
Spring 提供了多个容器接口,形成了层次化的容器体系:
- BeanFactory:最基础的容器接口,提供基本的 Bean 管理功能
- ApplicationContext:继承自 BeanFactory,提供更丰富的功能(事件发布、国际化等)
- ConfigurableApplicationContext:提供容器配置和生命周期管理的方法
常用的 ApplicationContext 实现类:
ClassPathXmlApplicationContext:从类路径加载 XML 配置FileSystemXmlApplicationContext:从文件系统加载 XML 配置AnnotationConfigApplicationContext:基于注解的配置WebApplicationContext:专门用于 Web 应用的容器
三、依赖注入(DI):实现松耦合的关键
依赖注入(Dependency Injection)是 IoC 思想的具体实现方式,指容器在创建对象时自动将其依赖的其他对象注入进来。
3.1 依赖注入的三种方式
3.1.1 构造器注入
通过构造方法传递依赖,确保对象在创建时就处于可用状态。
Service 类:
java
运行
public class UserService {
private UserDao userDao;
// 构造器注入
public UserService(UserDao userDao) {
this.userDao = userDao;
}
// 业务方法
public void addUser() {
userDao.insert();
}
}
XML 配置:
xml
<bean id="userDao" class="com.example.dao.UserDaoImpl"/>
<bean id="userService" class="com.example.service.UserService">
<!-- 构造器参数注入 -->
<constructor-arg ref="userDao"/>
</bean>
注解配置:
java
运行
@Repository
public class UserDaoImpl implements UserDao { ... }
@Service
public class UserService {
private final UserDao userDao;
// 构造器注入(Spring 4.3+可省略@Autowired)
@Autowired
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
3.1.2 Setter 方法注入
通过 Setter 方法设置依赖,灵活性更高,允许在对象创建后修改依赖。
Service 类:
java
运行
public class UserService {
private UserDao userDao;
// Setter方法
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
// 业务方法
public void addUser() {
userDao.insert();
}
}
XML 配置:
xml
<bean id="userDao" class="com.example.dao.UserDaoImpl"/>
<bean id="userService" class="com.example.service.UserService">
<!-- Setter注入 -->
<property name="userDao" ref="userDao"/>
</bean>
注解配置:
java
运行
@Service
public class UserService {
private UserDao userDao;
// Setter注入
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
3.1.3 字段注入
直接在字段上使用注解注入,代码最简洁,但测试时难以替换依赖。
java
运行
@Service
public class UserService {
// 字段注入
@Autowired
private UserDao userDao;
public void addUser() {
userDao.insert();
}
}
3.2 三种注入方式的对比与选择
| 注入方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 构造器注入 | 确保依赖不可变、对象创建即完整 | 多个依赖时构造器参数过长 | 必需依赖、核心依赖 |
| Setter 注入 | 灵活性高、支持可选依赖 | 依赖可被多次修改、对象可能处于不完整状态 | 可选依赖、需要动态修改的依赖 |
| 字段注入 | 代码简洁、开发效率高 | 耦合框架、测试困难 | 快速开发、简单场景 |
最佳实践:
- 优先使用构造器注入核心依赖
- 对于可选依赖使用 Setter 注入
- 谨慎使用字段注入,尤其是在需要频繁测试的场景
四、Bean 的生命周期:从创建到销毁
理解 Bean 的生命周期有助于更好地控制对象的创建和资源管理。Spring 中 Bean 的完整生命周期包括以下阶段:
- 实例化(Instantiation):创建 Bean 的实例
- 属性赋值(Populate):为 Bean 的属性设置值
- 初始化(Initialization):
- 调用
BeanNameAware的setBeanName() - 调用
BeanFactoryAware的setBeanFactory() - 调用
ApplicationContextAware的setApplicationContext() - 调用
BeanPostProcessor的postProcessBeforeInitialization() - 调用
InitializingBean的afterPropertiesSet() - 调用自定义的初始化方法(
init-method) - 调用
BeanPostProcessor的postProcessAfterInitialization()
- 调用
- 使用(In Use):Bean 可以被应用程序使用
- 销毁(Destruction):
- 调用
DisposableBean的destroy() - 调用自定义的销毁方法(
destroy-method)
- 调用
4.1 自定义初始化和销毁方法
XML 配置方式:
xml
<bean id="userService" class="com.example.service.UserService"
init-method="init" destroy-method="cleanup">
<property name="userDao" ref="userDao"/>
</bean>
注解方式:
java
运行
@Service
public class UserService {
private UserDao userDao;
@Autowired
public UserService(UserDao userDao) {
this.userDao = userDao;
}
// 自定义初始化方法
@PostConstruct
public void init() {
System.out.println("UserService初始化...");
}
// 自定义销毁方法
@PreDestroy
public void cleanup() {
System.out.println("UserService销毁...");
}
}
4.2 Bean 的作用域
Spring 定义了多种 Bean 的作用域,控制 Bean 的创建方式和生命周期:
- singleton(默认):整个应用中只有一个 Bean 实例
- prototype:每次请求都创建新的 Bean 实例
- request:每个 HTTP 请求创建一个实例(Web 环境)
- session:每个会话创建一个实例(Web 环境)
- application:整个 Web 应用共享一个实例(Web 环境)
- websocket:每个 WebSocket 会话创建一个实例
配置方式:
xml
<!-- XML配置 -->
<bean id="userService" class="com.example.service.UserService" scope="prototype"/>
java
运行
// 注解配置
@Service
@Scope("prototype")
public class UserService { ... }
五、Spring 配置方式:从 XML 到注解再到 JavaConfig
Spring 支持多种配置方式,从早期的 XML 配置到现在主流的注解和 Java 配置,体现了 "约定优于配置" 的发展趋势。
5.1 XML 配置
XML 配置是 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">
<!-- 定义Bean -->
<bean id="userDao" class="com.example.dao.UserDaoImpl"/>
<bean id="userService" class="com.example.service.UserService">
<property name="userDao" ref="userDao"/>
</bean>
<!-- 引入其他配置文件 -->
<import resource="dataSource.xml"/>
</beans>
5.2 注解配置
注解配置简化了配置工作,将配置信息与代码结合,提高开发效率。
常用注解:
@Component:通用组件注解@Service:服务层组件@Repository:数据访问层组件@Controller:Web 层组件@Autowired:自动注入依赖@Qualifier:指定注入 Bean 的名称@Value:注入基本类型和字符串
配置类:
java
运行
// 开启组件扫描
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
// 手动定义Bean(当无法使用注解时)
@Bean
public UserDao userDao() {
return new UserDaoImpl();
}
}
组件类:
java
运行
@Repository
public class UserDaoImpl implements UserDao { ... }
@Service
public class UserService {
@Autowired
private UserDao userDao;
// ...
}
5.3 JavaConfig 配置
JavaConfig 是基于 Java 类的配置方式,类型安全且易于重构,是 Spring Boot 推荐的配置方式。
java
运行
@Configuration
public class AppConfig {
// 数据源配置
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
// JdbcTemplate配置
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
5.4 混合配置策略
在实际项目中,通常会根据场景选择合适的配置方式:
- 核心业务组件:使用注解配置(@Service、@Repository 等)
- 基础设施配置:使用 JavaConfig(数据源、事务管理器等)
- 复杂的第三方组件:可保留 XML 配置,通过 @ImportResource 引入
java
运行
@Configuration
@ComponentScan("com.example")
@ImportResource("classpath:legacy-config.xml") // 引入XML配置
public class MainConfig {
// 新配置...
}
六、实战案例:构建一个简单的 Spring 应用
下面通过一个完整的案例,展示如何使用 Spring 框架构建一个简单的用户管理应用。
6.1 项目结构
plaintext
src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ ├── App.java // 应用入口
│ │ ├── config/
│ │ │ └── AppConfig.java // 配置类
│ │ ├── dao/
│ │ │ ├── UserDao.java // 数据访问接口
│ │ │ └── UserDaoImpl.java // 数据访问实现
│ │ ├── pojo/
│ │ │ └── User.java // 实体类
│ │ └── service/
│ │ ├── UserService.java // 服务接口
│ │ └── UserServiceImpl.java // 服务实现
│ └── resources/
└── test/
6.2 代码实现
实体类:
java
运行
package com.example.pojo;
public class User {
private Integer id;
private String username;
private String email;
// 构造器、Getter和Setter
public User() {}
public User(Integer id, String username, String email) {
this.id = id;
this.username = username;
this.email = email;
}
// Getter和Setter方法省略
}
数据访问接口:
java
运行
package com.example.dao;
import com.example.pojo.User;
import java.util.List;
public interface UserDao {
void addUser(User user);
User getUserById(Integer id);
List<User> getAllUsers();
}
数据访问实现:
java
运行
package com.example.dao;
import com.example.pojo.User;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Repository
public class UserDaoImpl implements UserDao {
// 模拟数据库
private static final Map<Integer, User> USER_MAP = new HashMap<>();
private static int nextId = 1;
@Override
public void addUser(User user) {
if (user.getId() == null) {
user.setId(nextId++);
}
USER_MAP.put(user.getId(), user);
}
@Override
public User getUserById(Integer id) {
return USER_MAP.get(id);
}
@Override
public List<User> getAllUsers() {
return new ArrayList<>(USER_MAP.values());
}
}
服务接口:
java
运行
package com.example.service;
import com.example.pojo.User;
import java.util.List;
public interface UserService {
void registerUser(User user);
User getUserById(Integer id);
List<User> getAllUsers();
}
服务实现:
java
运行
package com.example.service;
import com.example.dao.UserDao;
import com.example.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
private final UserDao userDao;
@Autowired
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void registerUser(User user) {
// 简单的业务逻辑
if (user.getUsername() == null || user.getUsername().trim().isEmpty()) {
throw new IllegalArgumentException("用户名不能为空");
}
userDao.addUser(user);
}
@Override
public User getUserById(Integer id) {
return userDao.getUserById(id);
}
@Override
public List<User> getAllUsers() {
return userDao.getAllUsers();
}
}
配置类:
java
运行
package com.example.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
// 组件扫描会自动发现并注册Bean
}
应用入口:
java
运行
package com.example;
import com.example.config.AppConfig;
import com.example.pojo.User;
import com.example.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class App {
public static void main(String[] args) {
// 初始化Spring容器
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 获取服务Bean
UserService userService = context.getBean(UserService.class);
// 测试功能
User user1 = new User(null, "张三", "zhangsan@example.com");
User user2 = new User(null, "李四", "lisi@example.com");
userService.registerUser(user1);
userService.registerUser(user2);
System.out.println("所有用户:");
userService.getAllUsers().forEach(user ->
System.out.println(user.getId() + ": " + user.getUsername() + " - " + user.getEmail()));
User user = userService.getUserById(1);
System.out.println("ID为1的用户:" + user.getUsername());
}
}
6.3 运行结果
plaintext
所有用户:
1: 张三 - zhangsan@example.com
2: 李四 - lisi@example.com
ID为1的用户:张三
七、常见问题与解决方案
-
Bean 注入失败(NoSuchBeanDefinitionException):
- 检查类是否添加了 @Component、@Service 等注解
- 确认 @ComponentScan 扫描的包路径是否正确
- 检查依赖是否存在循环依赖问题
-
循环依赖问题:
- 尽量避免循环依赖设计
- 使用 @Lazy 注解延迟加载其中一个依赖
- 将构造器注入改为 Setter 注入
-
Bean 的作用域使用不当:
- 理解 singleton 和 prototype 的区别
- 在 Web 环境正确使用 request/session 作用域
- 避免在 singleton Bean 中依赖 prototype Bean(会导致只创建一次)
-
@Autowired 注入多个候选 Bean:
- 使用 @Qualifier 指定 Bean 名称
- 使用 @Primary 指定首选 Bean
- 确保 Bean 名称唯一
总结
本文深入讲解了 Spring 框架的核心概念 ——IoC 容器和依赖注入,从理论到实践,全面介绍了 Spring 的工作原理和使用方法。通过理解控制反转思想,掌握依赖注入的三种方式,以及 Bean 的生命周期和配置方式,你已经具备了使用 Spring 框架开发应用的基础能力。
Spring 的 IoC 容器通过接管对象的创建和依赖管理,极大地降低了组件间的耦合度,使代码更加灵活、可维护和可测试。在实际开发中,应根据项目需求选择合适的配置方式和注入策略,遵循 Spring 的设计原则,充分发挥其优势。
下一篇博客将介绍 Spring 的另一个核心特性 —— 面向切面编程(AOP),它与 IoC 一起构成了 Spring 框架的基石,为企业级应用开发提供了强大的支持。
1139

被折叠的 条评论
为什么被折叠?



