Spring Framework 概述
本章定位:在正式学习 IoC 容器之前,先建立对 Spring Framework 的全局认知。理解 Spring 是什么、为什么存在、由哪些模块组成,以及它与 Jakarta EE 生态的关系。
Spring 是什么
Spring Framework 是一个企业级 Java 应用开发框架,诞生于 2003 年,旨在简化早期 J2EE(Java 2 Platform, Enterprise Edition)的复杂性。它提供了从配置模型到依赖注入机制、从数据访问到 Web 开发的全栈支持,同时兼容 Groovy 和 Kotlin 作为 JVM 上的替代语言。
在 Spring 出现之前,开发一个企业级 Java 应用意味着要面对 EJB(Enterprise JavaBeans)的繁重配置、JNDI 查找的繁琐流程,以及应用服务器强绑定的部署模式。Spring 的创始人 Rod Johnson 在《Expert One-on-One J2EE Design and Development》一书中提出了"轻量级容器"的理念,随后 Spring Framework 应运而生。
设计哲学
Spring 的设计哲学可以概括为五点:
| 设计原则 | 含义 |
|---|---|
| 提供选择(Provide choice) | 在每个层面都允许推迟设计决策,例如通过配置切换持久层框架而无需修改代码 |
| 容纳多样视角(Accommodate diverse perspectives) | 拥抱灵活性,不强制规定做事方式,支持不同应用场景 |
| 强向后兼容(Strong backward compatibility) | 版本演进谨慎,尽量减少破坏性变更 |
| 关注 API 设计(Care about API design) | 投入大量精力使 API 直观且经得起多版本考验 |
| 高代码质量标准(High standards for code quality) | 强调有意义的 Javadoc,保持包之间无循环依赖的干净结构 |
这五条原则贯穿 Spring 的每一个版本迭代。例如,Spring Framework 5.x 引入响应式编程模型(WebFlux)时,并没有废弃原有的 Spring MVC,而是让两者并存,这正是"容纳多样视角"的体现。
Spring Framework 模块总览
Spring Framework 采用模块化设计,应用可按需选择模块。核心为 Core Container(核心容器),包括配置模型和依赖注入机制;之外还提供消息、事务数据与持久化、Web 等基础支持。
模块路径说明:Spring Framework 的 JAR 包支持部署到 Java 模块路径(Java Module System),每个 JAR 都带有 Automatic-Module-Name 清单条目,定义稳定的语言级模块名(如 spring.core、spring.context),与 JAR 文件名(spring-core、spring-context)对应。
核心容器(Core Container)
核心容器是 Spring 的根基,包含四个模块:
- spring-core:提供工具类、ASM 字节码操作支持,以及 Spring 的基础抽象
- spring-beans:提供 BeanFactory 和依赖注入机制
- spring-context:在 spring-beans 基础上扩展出 ApplicationContext,支持国际化、事件传播、资源加载
- spring-expression:提供 SpEL(Spring Expression Language)表达式语言支持
数据访问与集成(Data Access/Integration)
- spring-jdbc:简化 JDBC 操作,提供 JdbcTemplate
- spring-tx:提供声明式事务管理抽象
- spring-orm:集成 Hibernate、JPA 等 ORM 框架
- spring-jms:集成消息队列(JMS)
Web 层
- spring-web:提供基础的 Web 工具类(如文件上传、HTTP 客户端抽象)
- spring-webmvc:传统的 Servlet MVC 框架
- spring-webflux:Spring 5 引入的响应式 Web 框架
- spring-websocket:WebSocket 支持
Spring 与 Jakarta EE 的关系
Spring 并非 Jakarta EE 的竞争对手,而是互补关系。Spring 编程模型不拥抱完整的 Jakarta EE 平台规范,而是选择性集成其中的个体规范:
| 规范 | JSR | 在 Spring 中的作用 |
|---|---|---|
| Servlet API | JSR 340 | Spring MVC 的底层基础 |
| WebSocket API | JSR 356 | Spring WebSocket 支持 |
| Concurrency Utilities | JSR 236 | 异步任务调度 |
| JSON Binding API | JSR 367 | JSON 序列化/反序列化 |
| Bean Validation | JSR 303 | 数据校验(如 @Valid) |
| JPA | JSR 338 | ORM 集成(Hibernate 等) |
| JMS | JSR 914 | 消息队列集成 |
| JSR-330 | JSR 330 | 依赖注入标准注解(@Inject 等) |
自 Spring Framework 6.0 起,Spring 已升级至 Jakarta EE 9 级别(如 Servlet 5.0+、JPA 3.0+),使用
jakarta命名空间替代传统的javax包。但在 Spring 5.x / Spring Boot 2.x 中,仍然使用javax命名空间。
完整示例:飞翔科技公司的技术选型
场景简述
广州飞翔科技有限公司成立于 2018 年,技术部负责人大翔决定为公司新开发的"学生管理系统"选择技术栈。架构师白歌建议采用 Spring Framework 5.x 作为基础框架,后端开发小崔负责具体搭建。
操作前:纯 Servlet + JDBC 的原始实现
在没有 Spring 之前,小崔需要手动管理数据库连接、事务和对象依赖:
// 操作前:StudentDao.java —— 纯 JDBC,无框架支持
public class StudentDao {
private DataSource dataSource;
public StudentDao() {
// 手动创建连接池,硬编码配置
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/student_db");
config.setUsername("root");
config.setPassword("secret");
this.dataSource = new HikariDataSource(config);
}
public Student findById(int studentId) throws SQLException {
Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(
"SELECT id, name, age, class_name FROM students WHERE id = ?"
);
ps.setInt(1, studentId);
ResultSet rs = ps.executeQuery();
Student student = null;
if (rs.next()) {
student = new Student();
student.setId(rs.getInt("id"));
student.setName(rs.getString("name"));
student.setAge(rs.getShort("age"));
student.setClassName(rs.getString("class_name"));
}
rs.close();
ps.close();
conn.close(); // 如果前面抛异常,这里可能执行不到,导致连接泄漏
return student;
}
}
// 操作前:StudentService.java —— 手动创建依赖,紧耦合
public class StudentService {
private StudentDao studentDao = new StudentDao(); // 硬编码依赖
private EmailSender emailSender = new EmailSender(); // 硬编码依赖
public void enrollStudent(Student student) {
try {
studentDao.save(student);
emailSender.send("admin@learnto.cn", "新学生入学:" + student.getName());
} catch (SQLException e) {
// 事务?不存在的,只能手动 rollback
throw new RuntimeException("入学失败", e);
}
}
}
痛点分析:
- 连接泄漏:
conn.close()在异常时可能不执行 - 配置硬编码:数据库连接信息写死在代码里
- 依赖紧耦合:
StudentService自己new依赖对象,无法替换实现 - 无事务管理:入学操作涉及"保存学生"和"发送邮件",失败时无法统一回滚
操作后:引入 Spring Framework 的模块化架构
白歌为团队设计了基于 Spring 5.x 的模块依赖图:
// 操作后:AppConfig.java —— Spring 配置类
@Configuration
@ComponentScan("com.feixiang.student")
@PropertySource("classpath:application.properties")
public class AppConfig {
@Bean
public DataSource dataSource(
@Value("${db.url}") String url,
@Value("${db.username}") String username,
@Value("${db.password}") String password) {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
return new HikariDataSource(config);
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
// 操作后:StudentDao.java —— 使用 Spring JDBC
@Repository
public class StudentDao {
private final JdbcTemplate jdbcTemplate;
public StudentDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public Student findById(int studentId) {
return jdbcTemplate.queryForObject(
"SELECT id, name, age, class_name FROM students WHERE id = ?",
new BeanPropertyRowMapper<>(Student.class),
studentId
);
}
public void save(Student student) {
jdbcTemplate.update(
"INSERT INTO students(name, age, class_name) VALUES(?, ?, ?)",
student.getName(), student.getAge(), student.getClassName()
);
}
}
// 操作后:StudentService.java —— 依赖注入 + 声明式事务
@Service
public class StudentService {
private final StudentDao studentDao;
private final EmailSender emailSender;
public StudentService(StudentDao studentDao, EmailSender emailSender) {
this.studentDao = studentDao;
this.emailSender = emailSender;
}
@Transactional
public void enrollStudent(Student student) {
studentDao.save(student);
emailSender.send("admin@learnto.cn", "新学生入学:" + student.getName());
}
}
运行结果及分析
容器启动日志:
[main] INFO o.s.c.a.AnnotationConfigApplicationContext -
Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6d06d69c
[main] INFO o.s.b.f.s.DefaultListableBeanFactory -
Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@5f2108b5:
defining beans [appConfig,studentDao,studentService,emailSender,dataSource,jdbcTemplate];
root of factory hierarchy
[main] DEBUG o.s.jdbc.core.JdbcTemplate -
Executing prepared SQL update [INSERT INTO students...]
[main] DEBUG o.s.j.d.DataSourceTransactionManager -
Initiating transaction commit
改进点总结:
| 维度 | 操作前(纯 JDBC) | 操作后(Spring) |
|---|---|---|
| 连接管理 | 手动获取/释放,易泄漏 | JdbcTemplate 自动管理 |
| 配置管理 | 硬编码在代码中 | @Value + application.properties 外部化 |
| 依赖管理 | new 硬编码,紧耦合 | 构造器注入,松耦合,可测试 |
| 事务管理 | 无,异常时数据不一致 | @Transactional 声明式事务,自动回滚 |
| 异常处理 | 原始 SQLException | Spring 自动转换为 DataAccessException 体系 |
易错场景与面试考点
反例一:混淆 Spring Framework 与 Spring Boot
// ❌ 错误认知:以为 Spring Boot 是 Spring 的替代品
@SpringBootApplication // 这是 Spring Boot 的注解,不是 Spring Framework 原生
public class StudentApplication {
public static void main(String[] args) {
SpringApplication.run(StudentApplication.class, args);
}
}
纠正:Spring Boot 是基于 Spring Framework 的快速启动层,它本身不是替代关系。Spring Boot 的自动配置底层仍然是 Spring Framework 的 ApplicationContext 和 @Configuration 机制。在 985 高校的计算机专业课程中,必须先掌握 Spring Framework 的核心容器原理,再学习 Spring Boot 的自动配置,否则会出现"只会用注解,不懂容器原理"的空中楼阁现象。
反例二:模块依赖缺失导致 ClassNotFoundException
小崔在 Maven 中只引入了 spring-context,却试图使用 @Transactional:
<!-- ❌ 错误:缺少 spring-tx 模块 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.21</version>
</dependency>
运行时报错:
java.lang.ClassNotFoundException: org.springframework.transaction.annotation.Transactional
纠正:@Transactional 属于 spring-tx 模块,必须显式引入:
<!-- ✅ 正确:按需引入事务模块 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.21</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.21</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.21</version>
</dependency>
面试高频题
Q1:Spring Framework 与 Jakarta EE 是什么关系?
Spring 不是 Jakarta EE 的竞争对手,而是互补关系。Spring 选择性集成 Jakarta EE 中的个体规范(如 Servlet、JPA、JMS),提供更轻量、更灵活的编程模型。Spring 5.x 使用
javax命名空间,Spring 6.x 升级至 Jakarta EE 9,使用jakarta命名空间。
Q2:Spring 的核心容器包含哪些模块?
核心容器(Core Container)包含 spring-core、spring-beans、spring-context 和 spring-expression 四个模块。其中 spring-beans 提供 BeanFactory 和依赖注入,spring-context 在此基础上扩展出 ApplicationContext。
Q3:为什么 Spring 被称为"轻量级"框架?
"轻量级"是相对早期 EJB 而言。Spring 不强制要求应用服务器,可以在普通 Servlet 容器甚至独立 Java 应用中运行;同时采用 POJO(Plain Old Java Object)编程模型,不侵入业务代码的类继承结构。