Spring 框架全家桶详解:从入门到实战 🌱

Spring 是 Java 领域最强大的企业级应用开发框架,它的出现彻底改变了 Java 企业级开发的格局。从最初的 Spring Framework 到现在的 Spring Boot、Spring Cloud,Spring 生态已经成为 Java 开发者必须掌握的核心技术。本文将带你全面理解 Spring 的核心概念、核心功能以及实战应用!💪


📚 目录导航


一、Spring 概述:为什么选择 Spring?

1.1 Spring 的诞生背景

2002 年,Rod Johnson 在其著作《Expert One-on-One J2EE Design and Development》中首次提出了 Spring 的概念。此时的 Java 企业级开发主要依赖 EJB(Enterprise JavaBeans)框架,但 EJB 存在以下问题:

EJB 的痛点:

  • 📦 重量级:每个 Bean 都需要实现特定的接口(如 SessionBean、EntityBean)
  • 🔧 配置复杂:大量的 XML 配置,部署描述符(DD)冗长
  • 🐢 性能低下:EJB 容器启动慢,Bean 实例化消耗资源多
  • 🧪 难以测试:业务逻辑与容器紧耦合,单元测试困难

Spring 的创新:
Rod Johnson 认为,我们可以采用简单的 JavaBean(Plain Old Java Object,简称 POJO)来代替 EJB,实现轻量级的企业级开发。Spring 通过依赖注入(DI)和面向切面编程(AOP)等核心功能,让 Java 开发变得简单、灵活、可测试。

1.2 什么是 Spring?

简单来说,Spring 是一个轻量级的 Java 开发框架,它提供了从基础配置到企业级应用的完整解决方案。

我们可以把 Spring 比喻成一个大型工具箱

  • 🔨 它提供了各种工具(模块),你可以根据需要选择使用
  • 📦 这些工具都是精心设计、互相配合的
  • 🔧 你不需要自己制造轮子,Spring 已经帮你做好了一切准备

1.3 Spring 的核心优势

让我详细解释 Spring 的四大核心优势:

1️⃣ 低侵入性 —— 保持代码纯净

传统的 EJB 框架要求开发者实现特定的接口(如 SessionBean),这会让你的业务代码充满框架特定的代码。而 Spring 不要求你实现任何特定接口,你的类可以保持 Plain Old Java Object(POJO)的形态。这意味着:

  • 你的代码更容易理解
  • 你的代码更容易在其他项目中使用
  • 你可以在任何时候去掉 Spring,而不改变业务代码

2️⃣ 组件化设计 —— 按需引入

Spring 采用了模块化的设计理念,主要模块包括:

  • spring-core:核心工具类
  • spring-beans:Bean 的创建和管理
  • spring-context:应用上下文
  • spring-aop:面向切面编程
  • spring-web:Web 开发支持
  • spring-tx:事务管理

你可以根据项目需要,只引入需要的模块,而不需要加载整个框架。

3️⃣ 强大生态 —— 一站式解决方案

Spring 生态非常丰富,包括:

  • Spring Boot:简化 Spring 应用开发
  • Spring Cloud:微服务架构解决方案
  • Spring Security:安全认证授权
  • Spring Data:数据访问统一抽象
  • Spring Batch:批处理框架
  • Spring Integration:消息集成

4️⃣ 易于测试 —— 可维护性高

Spring 的依赖注入让对象之间的依赖关系变得清晰,你可以轻松使用 Mock 对象进行单元测试,而不需要启动整个容器。

1.4 Spring 框架模块全景

各模块详细介绍:

模块 功能 核心类/接口
Spring Core 核心容器,提供 IoC 和 DI 功能 BeanFactory, ApplicationContext
Spring AOP 面向切面编程,支持事务、日志等功能 Aspect, JoinPoint, Advice
Spring DAO 数据访问,简化 JDBC 操作 JdbcTemplate, TransactionManager
Spring Web Web 开发支持 DispatcherServlet, Controller
Spring Test 单元测试支持 SpringRunner, MockMvc

1.5 Spring vs Java EE 对比

对比维度 Spring Java EE (Jakarta EE)
学习曲线 平缓,容易上手 陡峭,需要学习大量规范
配置方式 灵活(注解/Java Config/XML) 规范但复杂
侵入性 低侵入,保持 POJO 较高侵入
轻量级 ✅ 轻量级容器 重量级容器
灵活性 高度灵活 规范性更强
生态 极其丰富 逐渐追赶
社区活跃度 非常活跃 一般

二、Spring IoC 容器与依赖注入

2.1 什么是 IoC?

IoC(Inversion of Control)控制反转,是 Spring 的核心设计思想,也是面向对象设计原则中依赖倒置原则的具体实现。

要理解 IoC,我们需要先了解传统编程方式的问题

传统方式的问题举例:

假设我们有一个 UserService 类需要使用 UserDao 来操作数据库:

1
2
3
4
5
6
7
8
// 传统方式:UserService 自己创建 UserDao
public class UserService {
private UserDao userDao = new UserDao(); // ❌ 主动创建依赖

public void save(User user) {
userDao.save(user);
}
}

这种方式的问题是:

  1. UserServiceUserDao 紧耦合,无法替换成其他实现
  2. 如果 UserDao 的构造函数变了(比如需要参数),UserService 也需要改
  3. 单元测试时,无法用 Mock 对象替换真实的 UserDao

IoC 的解决思路:

IoC 的核心思想是**”不要让类自己创建依赖,而是让外部(容器)把依赖传进来”**。

打个比方:

  • 🎬 传统方式:你(在餐厅)自己做饭
  • 🍽️ IoC 方式:你(在餐厅)点菜,由厨房(容器)做好端给你

2.2 什么是 DI?

DI(Dependency Injection)依赖注入,是 IoC 的一种具体实现方式。容器在创建 Bean 时,自动将依赖的对象注入到目标 Bean 中。

我们可以把 DI 想象成医院的输液系统

  • 💉 病人(目标对象)需要药物(依赖)
  • 💉 不是病人自己去找药,而是护士(容器)把药接上
  • 💉 药瓶空了?护士会自动换一个新的(容器管理对象生命周期)

2.3 Spring IoC 容器详解

Spring IoC 容器的核心接口:

  1. BeanFactory:最基础的 IoC 容器接口

    • 采用延迟加载策略,只有当调用 getBean() 时才创建 Bean
    • 适合资源敏感的场景
  2. ApplicationContext:功能更强大的高级容器

    • 启动时即加载所有 Bean
    • 提供国际化(i18n)支持
    • 提供事件发布机制
    • 是我们在实际开发中最常用的接口

ApplicationContext 的三个实现类:

实现类 适用场景 配置文件位置
ClassPathXmlApplicationContext XML 配置方式 类路径下(src)
FileSystemXmlApplicationContext XML 配置方式 文件系统路径
AnnotationConfigApplicationContext 注解配置方式 Java 代码中指定

2.4 三种依赖注入方式详解

方式一:构造器注入(推荐使用)

构造器注入是 Spring 官方推荐的注入方式,特别适合处理必选依赖

优点:

  • ✅ 依赖对象在构造时就完全初始化,不会出现空指针
  • ✅ 构造器参数可以声明为 final,保证不可变性
  • ✅ 通过构造器可以明确看出这个类需要哪些依赖
  • ✅ 有利于单元测试,直接传 Mock 对象即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 构造器注入示例
*/
@Component
public class UserService {

private final UserDao userDao; // 声明为 final,保证不可变
private final EmailService emailService;

// 使用 @Autowired 标记构造器,Spring 会自动注入参数
@Autowired
public UserService(UserDao userDao, EmailService emailService) {
this.userDao = userDao;
this.emailService = emailService;
}

public void register(User user) {
userDao.save(user);
emailService.sendWelcomeEmail(user.getEmail());
}
}

方式二:Setter 注入

Setter 注入通过 setter 方法注入依赖,适合处理可选依赖

使用场景:

  • 依赖可能在后续操作中才设置
  • 需要在运行时更换依赖实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* Setter 注入示例
*/
@Component
public class UserService {

private UserDao userDao; // 非 final,可以后续修改

private NotificationService notificationService; // 可选依赖

@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

@Autowired
public void setNotificationService(NotificationService notificationService) {
this.notificationService = notificationService;
}

public void notifyUser(Long userId, String message) {
if (notificationService != null) { // 检查是否为 null
notificationService.send(userId, message);
}
}
}

方式三:字段注入(不推荐)

字段注入直接在字段上加 @Autowired,看起来最简洁,但不推荐使用。

为什么不推荐?

  • ❌ 依赖隐蔽,构造器中看不到有哪些依赖
  • ❌ 难以进行单元测试,无法手动传入 Mock 对象
  • ❌ 容易导致空指针异常(忘记注入)
  • ❌ 违反单一职责原则,类职责不清晰
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 字段注入(不推荐,但很常见)
*/
@Component
public class UserService {

@Autowired // ❌ 不推荐
private UserDao userDao;

@Autowired
private EmailService emailService;
}

2.5 @Autowired 注解详解

@Autowired 是 Spring 中最常用的依赖注入注解,它有几个关键用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* @Autowired 注解的作用域详解
*/

// 场景一:构造器注入(Spring 5.0+,单构造器时可省略 @Autowired)
@Component
public class UserService {
private UserDao userDao;

// 只有一个构造器时,@Autowired 可以省略
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}

// 场景二:Setter 方法注入
@Component
public class UserService {
private UserDao userDao;

@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}

// 场景三:字段注入(不推荐)
@Component
public class UserService {
@Autowired
private UserDao userDao;
}

// 场景四:当存在多个同类型 Bean 时,使用 @Qualifier 指定
@Component
public class UserService {

// 指定使用 "mysqlUserDao" 这个 Bean
@Autowired
@Qualifier("mysqlUserDao")
private UserDao userDao;
}

// 场景五:使用 @Primary 指定默认实现
@Repository
@Primary // 默认优先使用这个实现
public class MysqlUserDao implements UserDao {
// 这是默认实现
}

@Repository
public class OracleUserDao implements UserDao {
// 如果没有明确指定,使用带 @Primary 的
}

// 场景六:@Resource 注解(Java 标准注解,不仅仅是 Spring)
// @Resource(name = "mysqlUserDao") // 等同于 @Qualifier

三、Spring Bean 管理详解

3.1 Bean 的作用域

Spring 根据作用域将 Bean 分为以下几类:

详解五种作用域:

1️⃣ singleton(单例模式)—— 默认作用域

在 Spring IoC 容器中,每个 Bean 只会有一个共享实例。无论你获取多少次这个 Bean,得到的都是同一个对象。

使用场景:

  • 无状态的 Bean(不保存实例状态,如 Service、Repository)
  • 性能优化,减少对象创建开销
1
2
3
4
5
6
7
8
9
@Component
@Scope("singleton") // 可以省略,默认就是 singleton
public class UserService {
private int requestCount = 0; // ⚠️ 所有请求共享这个变量!

public void addRequest() {
requestCount++; // 线程安全问题!
}
}

2️⃣ prototype(原型模式)

每次从容器中获取 Bean 时,都会创建一个新的实例

使用场景:

  • 有状态的 Bean,需要保持每次请求的独立性
  • 对象创建成本较高,且不需要共享
1
2
3
4
5
6
7
8
9
@Component
@Scope("prototype")
public class OrderService {
private List<OrderItem> items = new ArrayList<>(); // 每个实例独立

public void addItem(OrderItem item) {
items.add(item); // 每个实例有自己独立的 items
}
}

3️⃣ request(请求级别)—— Web 应用专用

同一个 HTTP 请求内,所有获取的 Bean 都是同一个实例;不同请求则创建不同实例。

1
2
3
4
5
6
7
8
9
10
@Component
@Scope("request")
public class RequestScopeService {
private String requestId; // 每个请求有独立的 requestId

@PostConstruct
public void init() {
this.requestId = UUID.randomUUID().toString();
}
}

4️⃣ session(会话级别)—— Web 应用专用

同一用户会话内获取的 Bean 是同一个实例;不同用户会话则创建不同实例。

1
2
3
4
5
6
7
8
9
@Component
@Scope("session")
public class UserPreferencesService {
private UserPreferences preferences; // 每个用户有独立的偏好设置

public void setTheme(String theme) {
preferences.setTheme(theme);
}
}

5️⃣ application(应用级别)

整个 Web 应用只有一个实例,类似于单例但属于 ServletContext 范围。

1
2
3
4
5
6
7
8
9
@Component
@Scope("application")
public class AppCounterService {
private int totalUsers; // 整个应用的计数器

public void increment() {
totalUsers++;
}
}

3.2 Bean 的生命周期详解

理解 Bean 的生命周期,有助于我们在正确的时机执行初始化和清理逻辑:

每个阶段详解:

阶段 说明 可实现接口/注解
1. 实例化 调用构造器创建 Bean 实例
2. 属性填充 Spring 注入配置的属性和依赖 @Autowired, @Value
3. BeanNameAware 将 Bean 的名称注入到 Bean 中 BeanNameAware
4. BeanFactoryAware 将 BeanFactory 注入到 Bean 中 BeanFactoryAware
5. ApplicationContextAware 将 ApplicationContext 注入 ApplicationContextAware
6. BeanPostProcessor 前置 对 Bean 进行预处理 BeanPostProcessor
7. InitializingBean 初始化逻辑 InitializingBean.afterPropertiesSet()
8. 自定义 init-method 自定义初始化方法 @PostConstruct 或 @Bean(initMethod)
9. BeanPostProcessor 后置 对 Bean 进行后处理 BeanPostProcessor
10. Bean 就绪 Bean 可以正常使用了 -
11. 销毁 清理资源,关闭连接 DisposableBean.destroy()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* 完整的 Bean 生命周期演示
*/
@Component
public class UserService implements InitializingBean, DisposableBean {

private String name;
private UserDao userDao;

// 1. 构造器(阶段一)
public UserService() {
System.out.println("1️⃣ UserService 构造器被调用");
}

// 2. 属性注入(阶段二)
@Autowired
public void setName(String name) {
System.out.println("2️⃣ 属性注入:" + name);
this.name = name;
}

// 3. BeanNameAware(阶段三)
@Override
public void setBeanName(String name) {
System.out.println("3️⃣ Bean 名称:" + name);
}

// 4. 初始化方法(阶段七/八)
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("7️⃣ InitializingBean.afterPropertiesSet() 被调用");
// 在这里可以执行初始化逻辑,如校验参数、建立连接等
}

// 5. 自定义初始化方法(阶段八)
@PostConstruct
public void init() {
System.out.println("8️⃣ @PostConstruct 初始化方法被调用");
}

// 6. 销毁方法(阶段十一)
@Override
public void destroy() throws Exception {
System.out.println("🛑 DisposableBean.destroy() 被调用");
// 在这里可以执行清理逻辑,如关闭连接、释放资源等
}

// 7. 自定义销毁方法(阶段十一)
@PreDestroy
public void cleanup() {
System.out.println("🗑️ @PreDestroy 销毁方法被调用");
}
}

3.3 组件扫描与注册

Spring 通过组件扫描(Component Scanning)自动发现和注册带有 @Component 及其衍生注解(如 @Service@Repository@Controller)的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* 组件扫描配置详解
*/

// 方式一:@ComponentScan 基本用法
@Configuration
@ComponentScan(basePackages = "com.example") // 扫描 com.example 包及其子包
public class AppConfig {
}

// 方式二:排除特定组件
@Configuration
@ComponentScan(
basePackages = "com.example",
excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Dao")
)
public class AppConfig {
}

// 方式三:只包含特定组件
@Configuration
@ComponentScan(
basePackages = "com.example",
includeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = {Controller.class, Service.class}
)
)
public class AppConfig {
}

// 方式四:排除特定类型的组件
@Configuration
@ComponentScan(
basePackages = "com.example",
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Controller.class // 排除所有 Controller
)
)
public class AppConfig {
}

3.4 @Conditional 条件注册

@Conditional 注解允许我们根据特定条件决定是否注册某个 Bean,这在多环境配置和功能开关场景中非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* 条件注册 Bean 示例
*/

// 场景一:根据类是否存在条件注册
@Configuration
public class AppConfig {

@Bean
@ConditionalOnClass(name = "com.example.thirdparty.ThirdPartyService")
public ThirdPartyService thirdPartyService() {
return new ThirdPartyService();
}
}

// 场景二:根据配置文件条件注册
@Configuration
@ConditionalOnProperty(name = "feature.enabled", havingValue = "true")
public class FeatureConfig {

@Bean
public FeatureService featureService() {
return new FeatureService();
}
}

// 场景三:根据环境条件注册
@Configuration
@Profile("prod") // 只在生产环境生效
public class ProdConfig {

@Bean
public AlertService alertService() {
return new EmailAlertService(); // 生产环境发邮件告警
}
}

@Configuration
@Profile("dev") // 只在开发环境生效
public class DevConfig {

@Bean
public AlertService alertService() {
return new ConsoleAlertService(); // 开发环境打印到控制台
}
}

四、Spring AOP 面向切面编程

4.1 为什么需要 AOP?

在软件开发中,有一些横切关注点(Cross-cutting Concerns)会分散在多个模块中,例如:

  • 📝 日志记录:在每个方法开始和结束时记录日志
  • 🔒 安全控制:在方法执行前检查用户权限
  • 🔄 事务管理:在方法开始时开启事务,结束时提交/回滚
  • 📊 性能监控:记录每个方法的执行时间

这些功能如果每个方法都手动编写,会导致:

  1. 代码重复:同样的逻辑在多个地方重复
  2. 业务逻辑不清晰:核心业务被横切逻辑污染
  3. 修改成本高:横切逻辑改变需要修改所有模块

AOP 的解决方案是:将这些横切关注点封装成独立的模块(切面),然后声明式地将它们”织入”到需要的方法中。

4.2 AOP 核心概念详解

核心概念详解:

🎯 Aspect(切面):
切面是横切关注点的模块化封装。你可以把切面想象成一个”插件”,它定义了在哪些地方(切入点)做什么(通知)。

📍 Pointcut(切入点):
切入点用于定义哪些连接点需要被拦截。你可以理解为”在哪些方法上动手脚”的规则。

➡️ Join Point(连接点):
连接点是程序执行的某个位置,可以被切面拦截。在 Spring AOP 中,连接点特指方法调用

🔔 Advice(通知/增强):
通知是拦截到连接点后执行的逻辑,分为五种类型:

  • @Before:前置通知,在方法执行前执行
  • @After:后置通知,在方法执行后执行(无论成功或失败)
  • @AfterReturning:返回通知,在方法成功返回后执行
  • @AfterThrowing:异常通知,在方法抛出异常时执行
  • @Around:环绕通知,可以控制方法是否执行以及执行时机

🔄 Weaving(织入):
织入是把切面应用到目标对象的过程。Spring AOP 默认采用动态代理方式进行织入。

4.3 AOP 工作流程图解

4.4 AOP 示例代码详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/**
* 定义切面类
* 切面 = 切入点表达式 + 通知
*/
@Aspect // 标注为切面类(注意:需要同时标注 @Component 才能被扫描)
@Component // 加入 Spring 容器管理
public class LoggingAspect {

// ==========================================
// 切入点表达式定义
// ==========================================

// 切入点:拦截 com.example.service 包下所有类的所有方法
// 语法:execution([返回值类型] [包名.类名.方法名](参数))
// * 表示任意返回值类型
// *.* 表示任意包下的任意类
// *(..) 表示任意方法,任意参数
@Pointcut("execution(* com.example.service.*.*(..))")
public void servicePointcut() {}

// 切入点:拦截标注 @Loggable 注解的方法
@Pointcut("@annotation(com.example.annotation.Loggable)")
public void loggablePointcut() {}

// ==========================================
// 通知方法定义
// ==========================================

// 前置通知:在目标方法执行前执行
@Before("servicePointcut()")
public void before(JoinPoint joinPoint) {
// JoinPoint 封装了被拦截方法的信息
String methodName = joinPoint.getSignature().toShortString(); // 方法签名
Object[] args = joinPoint.getArgs(); // 方法参数

System.out.println("🔔 [前置通知] 方法 " + methodName + " 即将执行");
System.out.println(" 参数:" + Arrays.toString(args));
}

// 后置通知:在目标方法执行后执行(无论成功或失败)
@After("servicePointcut()")
public void after(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().toShortString();
System.out.println("🔔 [后置通知] 方法 " + methodName + " 执行完成");
}

// 返回通知:在目标方法成功返回后执行
@AfterReturning(pointcut = "servicePointcut()", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().toShortString();
System.out.println("✅ [返回通知] 方法 " + methodName + " 返回结果:" + result);
}

// 异常通知:在目标方法抛出异常时执行
@AfterThrowing(pointcut = "servicePointcut()", throwing = "exception")
public void afterThrowing(JoinPoint joinPoint, Throwable exception) {
String methodName = joinPoint.getSignature().toShortString();
System.out.println("❌ [异常通知] 方法 " + methodName + " 抛出异常:" + exception.getMessage());
}

// 环绕通知:最强大的通知类型
// 可以在方法执行前后执行自定义逻辑,甚至决定是否执行方法
@Around("servicePointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().toShortString();
long startTime = System.currentTimeMillis();

System.out.println("⏰ [环绕通知] 方法 " + methodName + " 开始执行");

try {
// proceed() 方法执行目标方法
// 如果不调用 proceed(),目标方法不会执行
Object result = joinPoint.proceed();

long duration = System.currentTimeMillis() - startTime;
System.out.println("⏱️ [环绕通知] 方法 " + methodName + " 执行成功,耗时:" + duration + "ms");

return result;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
System.out.println("❌ [环绕通知] 方法 " + methodName + " 执行失败,耗时:" + duration + "ms");
throw e; // 重新抛出异常,让调用者处理
}
}
}

4.5 常用切入点表达式详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
* 切入点表达式语法详解
*
* execution([修饰符] 返回值类型 [包名.类名.方法名](参数))
*
* 特殊符号:
* - * :匹配任意字符
* - .. :匹配任意参数或任意层级包
* - + :匹配子类
*/

@Aspect
@Component
public class PointcutExpressions {

// 示例一:拦截特定类的方法
// 拦截 UserService 类的所有方法
@Pointcut("execution(* com.example.service.UserService.*(..))")
public void userServicePointcut() {}

// 示例二:拦截特定方法(精确到方法名和参数类型)
// 拦截 findById(Long) 方法
@Pointcut("execution(* com.example.service.UserService.findById(Long))")
public void findByIdPointcut() {}

// 示例三:拦截返回值为特定类型的方法
// 返回 User 类型的所有方法
@Pointcut("execution(com.example.entity.User com.example.service.*.*(..))")
public void userReturnPointcut() {}

// 示例四:拦截特定包下所有子包的方法
// com.example.service.. 表示 service 包及其所有子包
@Pointcut("execution(* com.example.service..*.*(..))")
public void servicePackagePointcut() {}

// 示例五:拦截所有 public 方法
@Pointcut("execution(public * *(..))")
public void publicMethodPointcut() {}

// 示例六:拦截所有带 @Transactional 注解的方法
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalPointcut() {}

// 示例七:组合切入点(使用 && || !)
// 拦截 service 包的方法,但排除 save 开头的方法
@Pointcut("execution(* com.example.service..*.*(..)) && !execution(* com.example.service..save*(..))")
public void serviceExceptSavePointcut() {}

// 示例八:within —— 拦截特定类/包的所有方法
// 拦截 UserService 类中的所有方法
@Pointcut("within(com.example.service.UserService)")
public void withinPointcut() {}

// 示例九:this —— 拦截代理对象是特定类型的方法
// 当代理对象是 UserService 类型时拦截
@Pointcut("this(com.example.service.UserService)")
public void thisPointcut() {}

// 示例十:args —— 拦截参数类型匹配的方法
// 拦截参数第一个是 Long 类型的方法
@Pointcut("execution(* com.example.service.UserService.findById(Long))")
public void argsPointcut() {}
}

4.6 自定义注解 + AOP 实战

通过结合自定义注解和 AOP,我们可以实现功能更明确、使用更便捷的切面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**
* 第一步:定义注解
*/
@Target(ElementType.METHOD) // 注解可以标注在方法上
@Retention(RetentionPolicy.RUNTIME) // 注解在运行时可见
@Documented
public @interface Loggable {
String value() default ""; // 注解属性:日志描述
LogLevel level() default LogLevel.INFO; // 注解属性:日志级别
}

enum LogLevel {
DEBUG, INFO, WARN, ERROR
}

/**
* 第二步:定义切面处理注解
*/
@Aspect
@Component
public class LoggableAspect {

// 拦截所有标注 @Loggable 注解的方法
@Around("@annotation(Loggable)")
public Object around(ProceedingJoinPoint joinPoint,
Loggable loggable) throws Throwable {
String methodName = joinPoint.getSignature().toShortString();
String annotationValue = loggable.value();
LogLevel level = loggable.level();

// 根据日志级别输出日志
log(level, "📝 [" + level + "] 方法 " + methodName + " 注解值:" + annotationValue);

long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - startTime;
log(level, "✅ 方法执行成功,耗时:" + duration + "ms");
return result;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
log(LogLevel.ERROR, "❌ 方法执行异常:" + e.getMessage());
throw e;
}
}

private void log(LogLevel level, String message) {
switch (level) {
case DEBUG: System.out.println("[DEBUG] " + message); break;
case INFO: System.out.println("[INFO] " + message); break;
case WARN: System.out.println("[WARN] " + message); break;
case ERROR: System.err.println("[ERROR] " + message); break;
}
}
}

/**
* 第三步:在需要记录日志的方法上使用注解
*/
@Service
public class UserService {

@Loggable(value = "查询用户详情", level = LogLevel.INFO)
public User findById(Long id) {
return userDao.findById(id);
}

@Loggable(value = "保存用户", level = LogLevel.WARN)
public User save(User user) {
return userDao.save(user);
}
}

五、Spring 事务管理

5.1 为什么需要事务?

事务是一组操作,要么全部成功,要么全部失败的机制。典型场景是银行转账:

假设 A 账户给 B 账户转账 1000 元,操作包括:

  1. 从 A 账户扣除 1000 元
  2. 向 B 账户增加 1000 元

如果第一步成功但第二步失败(比如数据库崩溃),A 账户的钱就莫名其妙少了!这就需要事务来保证:

ACID 特性详解:

A - Atomicity(原子性):
事务中的所有操作要么全部完成,要么全部不执行。如果事务中途失败,已经执行的操作会被撤销。

C - Consistency(一致性):
事务执行前后,数据库的状态保持一致。例如转账前后,总金额不变。

I - Isolation(隔离性):
并发执行的事务之间互不干扰。数据库通过隔离级别来控制并发程度。

D - Durability(持久性):
事务一旦提交,其对数据库的修改就是永久性的,即使系统崩溃也不会丢失。

5.2 Spring 事务管理方式

Spring 提供两种事务管理方式:

方式一:编程式事务(了解即可,不推荐使用)

通过编写代码手动管理事务,可以精确控制,但代码侵入性高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 编程式事务管理示例
* 这种方式代码侵入性高,不推荐在日常开发中使用
*/
@Service
public class UserService {

@Autowired
private PlatformTransactionManager transactionManager;

public void transfer(Long fromId, Long toId, BigDecimal amount) {
// 1. 定义事务属性
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);

// 2. 获取事务状态
TransactionStatus status = transactionManager.getTransaction(definition);

try {
// 3. 执行业务操作
accountDao.deduct(fromId, amount);
accountDao.add(toId, amount);

// 4. 提交事务
transactionManager.commit(status);
} catch (Exception e) {
// 5. 发生异常,回滚事务
transactionManager.rollback(status);
throw e;
}
}
}

方式二:声明式事务(推荐使用)

通过 @Transactional 注解声明式地管理事务,代码侵入性低,是实际开发中的主要方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 声明式事务管理示例
* 只需在方法或类上加 @Transactional 注解即可
*/
@Service
public class UserService {

// 在方法上加 @Transactional,方法内的所有操作都在一个事务中
@Transactional(rollbackFor = Exception.class)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
accountDao.deduct(fromId, amount);
accountDao.add(toId, amount);
// 如果发生任何异常,Spring 会自动回滚事务
}

// 在类上加 @Transactional,类中所有方法都会在事务中执行
@Transactional
public class UserService {
public void saveUser(User user) { /* ... */ }
public void updateUser(User user) { /* ... */ }
public void deleteUser(Long id) { /* ... */ }
}
}

5.3 @Transactional 注解详解

@Transactional 注解有多个参数,每个参数控制事务的不同方面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
* @Transactional 注解参数详解
*/

@Service
public class UserService {

// 1. rollbackFor:指定触发回滚的异常类型
// 默认只对 RuntimeException 和 Error 回滚
// 如果想让所有异常都回滚,指定为 Exception.class
@Transactional(rollbackFor = Exception.class)
public void method1() {
// 任何异常都会回滚,包括受检异常
}

// 2. noRollbackFor:指定不触发回滚的异常类型
// 业务异常通常不希望回滚,而是返回错误码
@Transactional(noRollbackFor = BusinessException.class)
public void method2() {
// BusinessException 不会触发回滚
}

// 3. propagation:事务传播行为
// 控制当一个事务方法被另一个事务方法调用时,事务如何传播
@Transactional(propagation = Propagation.REQUIRED)
public void method3() {
/*
* REQUIRED:加入已存在的事务,或创建新事务(默认)
* REQUIRES_NEW:挂起当前事务,创建新事务
* NESTED:如果当前有事务则嵌套执行,否则创建新事务
* SUPPORTS:如果当前有事务则加入,否则非事务执行
* MANDATORY:必须在事务中执行,否则抛异常
* NEVER:必须在非事务中执行,否则抛异常
* NOT_SUPPORTED:挂起当前事务,非事务执行
*/
}

// 4. isolation:事务隔离级别
// 控制并发事务之间的相互影响程度
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void method4() {
/*
* READ_UNCOMMITTED:最低隔离级别,可能产生脏读
* READ_COMMITTED:防止脏读,但可能产生不可重复读
* REPEATABLE_READ:防止脏读和不可重复读,但可能产生幻读
* SERIALIZABLE:最高隔离级别,完全串行执行
*/
}

// 5. timeout:超时时间(秒)
// 如果事务执行超过指定时间,自动回滚
@Transactional(timeout = 30)
public void method5() {
// 超过 30 秒自动回滚
}

// 6. readOnly:只读事务
// 提示数据库优化,适合只有查询的操作
@Transactional(readOnly = true)
public void method6() {
// 只读事务,数据库可能会进行优化
}
}

5.4 事务传播行为详解

事务传播行为定义了当一个事务方法被另一个事务方法调用时,事务如何处理

常见传播行为详解:

传播行为 说明 适用场景
REQUIRED 如果当前有事务,加入;否则创建新事务 大多数业务操作
REQUIRES_NEW 挂起当前事务,创建全新事务 独立记录日志
NESTED 嵌套事务(使用保存点) 部分操作需要独立回滚
SUPPORTS 如果当前有事务加入;否则非事务执行 查询为主的操作
MANDATORY 必须在事务中执行 强制要求事务的方法
NOT_SUPPORTED 挂起事务,非事务执行 不需要事务的操作

5.5 Spring 事务原理

Spring 事务的原理基于 AOP 动态代理。当你使用 @Transactional 注解时:

⚠️ 重要注意事项:@Transactional 在内部方法调用时不生效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* ❌ 错误示例:内部方法调用,事务不生效
*/
@Service
public class UserService {

@Transactional
public void outerMethod() {
innerMethod(); // ❌ 这里调用的是 this.innerMethod()
// 因为 this 不是代理对象,所以事务不生效
}

@Transactional
public void innerMethod() {
// 这里的代码实际上没有事务!
}
}

/**
* ✅ 正确解决方案:注入自身代理对象
*/
@Service
public class UserService {

// 注入 Spring 容器中的代理对象(self 实际上是代理对象)
@Autowired
private UserService self;

@Transactional
public void outerMethod() {
self.innerMethod(); // ✅ 通过代理对象调用,事务生效
}

@Transactional
public void innerMethod() {
// 现在有事务了!
}
}

六、Spring MVC 开发 Web 应用

6.1 Spring MVC 概述

Spring MVC 是 Spring 框架的 Web 模块,专门用于开发 Web 应用。它遵循 MVC(Model-View-Controller) 设计模式,将数据处理、页面展示、业务控制分离。

MVC 职责分工:

角色 职责 Spring MVC 对应
Model(模型) 业务数据处理 Service、Repository、Entity
View(视图) 页面展示 JSP、Thymeleaf、JSON
Controller(控制器) 接收请求、调用服务、返回响应 @Controller、@RestController

6.2 Spring MVC 工作流程

流程详解:

  1. DispatchServlet 接收请求:作为前端控制器,所有请求都先到达这里
  2. HandlerMapping 查找 Controller:根据 URL 找到对应的处理方法
  3. HandlerAdapter 执行 Controller:调用具体的处理方法
  4. Controller 返回 ModelAndView:包含模型数据和视图名
  5. ViewResolver 解析视图:根据视图名找到实际的视图文件
  6. View 渲染视图:将数据填充到模板中
  7. 返回响应:生成 HTML 或 JSON 返回给浏览器

6.3 @Controller vs @RestController

这两个注解用于不同的场景:

@Controller:返回视图(JSP、Thymeleaf 等模板)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Controller
@RequestMapping("/users")
public class UserController {

// 返回视图页面
@RequestMapping("/list")
public String list(Model model) {
// 查询数据
List<User> users = userService.findAll();
// 将数据存入模型
model.addAttribute("users", users);
// 返回视图名(Spring Boot 会自动拼接模板路径)
return "user/list"; // -> templates/user/list.html
}

// 返回视图页面(REST 风格)
@RequestMapping("/detail/{id}")
public String detail(@PathVariable Long id, Model model) {
User user = userService.findById(id);
model.addAttribute("user", user);
return "user/detail";
}
}

@RestController:返回 JSON 数据(前后端分离)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@RestController  // = @Controller + @ResponseBody
@RequestMapping("/api/users")
public class UserApiController {

@Autowired
private UserService userService;

// GET /api/users
@GetMapping
public List<User> list() {
return userService.findAll();
}

// GET /api/users/1
@GetMapping("/{id}")
public User getById(@PathVariable Long id) {
return userService.findById(id);
}

// POST /api/users
@PostMapping
public Result<User> create(@RequestBody @Valid User user) {
User saved = userService.save(user);
return Result.success(saved);
}

// PUT /api/users/1
@PutMapping("/{id}")
public Result<Void> update(@PathVariable Long id,
@RequestBody @Valid User user) {
user.setId(id);
userService.update(user);
return Result.success();
}

// DELETE /api/users/1
@DeleteMapping("/{id}")
public Result<Void> delete(@PathVariable Long id) {
userService.deleteById(id);
return Result.success();
}
}

6.4 请求参数绑定详解

Spring MVC 提供了丰富的参数绑定方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**
* 请求参数绑定示例
*/
@RestController
@RequestMapping("/api/products")
public class ProductController {

// ==========================================
// 1. 路径变量 @PathVariable
// 用于绑定 URL 路径中的变量
// ==========================================
@GetMapping("/{category}/{id}")
public Product getById(@PathVariable Long id,
@PathVariable String category) {
// GET /api/products/electronics/100
// category = "electronics", id = 100
return productService.findById(id);
}

// ==========================================
// 2. 查询参数 @RequestParam
// 用于绑定 URL ?key=value 形式的参数
// ==========================================
@GetMapping("/list")
public PageResult<Product> list(
@RequestParam(defaultValue = "1") int page, // 默认值
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword, // 可选参数
@RequestParam(required = false) String sort) {
// GET /api/products/list?page=2&size=20&keyword=手机&sort=price
return productService.findByPage(page, size, keyword, sort);
}

// ==========================================
// 3. 请求体 @RequestBody(JSON)
// 用于接收 JSON 格式的请求体
// ==========================================
@PostMapping
public Result<Product> create(@RequestBody @Valid Product product) {
// POST /api/products Body: {"name":"手机","price":2999.00}
return Result.success(productService.save(product));
}

// ==========================================
// 4. 表单参数 @RequestParam(form-data)
// 用于接收传统表单提交
// ==========================================
@PostMapping("/form")
public Result<Void> submitForm(@RequestParam String name,
@RequestParam String email) {
// POST /api/products/form Body: name=手机&email=xxx
return Result.success();
}

// ==========================================
// 5. 请求头 @RequestHeader
// 用于获取 HTTP 请求头中的值
// ==========================================
@GetMapping("/header")
public String getHeader(
@RequestHeader("Authorization") String auth,
@RequestHeader(value = "X-Custom-Header", required = false) String custom) {
return "Auth: " + auth + ", Custom: " + custom;
}

// ==========================================
// 6. Cookie @CookieValue
// 用于获取 Cookie 中的值
// ==========================================
@GetMapping("/cookie")
public String getCookie(@CookieValue("JSESSIONID") String sessionId) {
return "Session ID: " + sessionId;
}

// ==========================================
// 7. 实体类自动映射
// Spring 会自动将请求参数映射到对象的同名属性
// ==========================================
@GetMapping("/search")
public List<Product> search(ProductQuery query) {
// GET /api/products/search?keyword=手机&category=electronics&minPrice=1000
return productService.search(query);
}

// ==========================================
// 8. Servlet 原生对象
// 直接注入 HttpServletRequest、HttpServletResponse 等
// ==========================================
@GetMapping("/raw")
public String raw(HttpServletRequest request,
HttpServletResponse response) {
String method = request.getMethod();
String remoteAddr = request.getRemoteAddr();
return "Method: " + method + ", IP: " + remoteAddr;
}
}

/**
* 查询参数实体类
*/
@Data
public class ProductQuery {
private String keyword;
private String category;
private BigDecimal minPrice;
private BigDecimal maxPrice;
@Min(1)
private Integer page = 1;
@Max(100)
private Integer size = 10;
}

6.5 响应处理与数据封装

为了保持 API 响应格式的一致性,我们通常会定义统一的响应结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
* 统一响应结果封装
* 所有 API 响应都使用这个结构
*/
@Data
public class Result<T> {

private int code; // 状态码:200=成功,其他=失败
private String message; // 消息:成功/失败的描述
private T data; // 数据:响应的数据

// 成功响应(带数据)
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("success");
result.setData(data);
return result;
}

// 成功响应(无数据)
public static <T> Result<T> success() {
return success(null);
}

// 失败响应
public static <T> Result<T> error(String message) {
return error(500, message); // 默认 500 错误
}

// 带状态码的失败响应
public static <T> Result<T> error(int code, String message) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
return result;
}
}

/**
* 分页结果封装
*/
@Data
public class PageResult<T> {

private long total; // 总记录数
private int page; // 当前页
private int size; // 每页大小
private int totalPages; // 总页数
private List<T> data; // 数据列表

// 从 Spring Data 的 Page 对象转换
public static <T> PageResult<T> of(Page<T> page) {
PageResult<T> result = new PageResult<>();
result.setTotal(page.getTotalElements());
result.setPage(page.getNumber() + 1); // Spring Data 页码从 0 开始
result.setSize(page.getSize());
result.setTotalPages(page.getTotalPages());
result.setData(page.getContent());
return result;
}
}

6.6 统一异常处理

为了给用户友好的错误提示,我们需要统一处理各种异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
* 全局异常处理器
* 统一处理所有 Controller 抛出的异常
*/
@RestControllerAdvice // = @ControllerAdvice + @ResponseBody
public class GlobalExceptionHandler {

// 处理业务异常
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e) {
System.out.println("业务异常:" + e.getMessage());
return Result.error(400, e.getMessage());
}

// 处理参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> handleValidationException(
MethodArgumentNotValidException e) {
// 获取第一个校验错误的信息
String message = e.getBindingResult().getFieldError()
.getDefaultMessage();
return Result.error(400, "参数校验失败:" + message);
}

// 处理资源不存在异常
@ExceptionHandler(EntityNotFoundException.class)
public Result<Void> handleNotFound(EntityNotFoundException e) {
return Result.error(404, e.getMessage());
}

// 处理数据库唯一约束异常
@ExceptionHandler(DataIntegrityViolationException.class)
public Result<Void> handleDataIntegrityViolation(
DataIntegrityViolationException e) {
return Result.error(400, "数据重复,请检查后重试");
}

// 处理其他所有异常
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
e.printStackTrace(); // 打印堆栈,方便排查
return Result.error(500, "系统异常,请稍后重试");
}
}

/**
* 自定义业务异常
*/
public class BusinessException extends RuntimeException {
public BusinessException(String message) {
super(message);
}
}

/**
* 实体未找到异常
*/
public class EntityNotFoundException extends RuntimeException {
public EntityNotFoundException(String entity, Long id) {
super(entity + " 不存在,ID:" + id);
}
}

七、Spring Boot 快速开发

7.1 什么是 Spring Boot?

Spring Boot 是 Spring 的子项目,它的目标是简化 Spring 应用的创建和开发过程。你可以把 Spring Boot 理解为”Spring 的一键启动器”。

Spring Boot 的核心特性:

1️⃣ 开箱即用:
Spring Boot 提供了大量的”起步依赖”(Starter Dependencies),你只需要引入一个依赖,就能获得完整的模块功能。

2️⃣ 习惯优于配置:
Spring Boot 会根据你引入的依赖和类路径,自动配置应用程序。你不需要手动配置每一个 Bean。

3️⃣ 内嵌服务器:
Spring Boot 内嵌了 Tomcat、Jetty 等 Web 服务器,可以直接打包成可执行的 JAR 文件运行,无需部署到外部服务器。

4️⃣ 自动配置:
Spring Boot 会根据你的环境自动配置 Bean,例如:

  • 如果类路径中有 MySQL 驱动,自动配置数据源
  • 如果类路径中有 Spring MVC,自动配置 DispatcherServlet

5️⃣ 内置测试:
Spring Boot 提供了完整的测试支持,可以方便地编写集成测试和单元测试。

7.2 Spring Boot 项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
my-spring-boot-project/
├── src/
│ ├── main/
│ │ ├── java/com/example/
│ │ │ ├── Application.java # 启动类
│ │ │ ├── controller/ # 控制器层(接收请求)
│ │ │ ├── service/ # 服务层(业务逻辑)
│ │ │ ├── repository/ # 数据访问层(操作数据库)
│ │ │ ├── entity/ # 实体类(数据库表映射)
│ │ │ ├── dto/ # 数据传输对象
│ │ │ ├── config/ # 配置类
│ │ │ └── exception/ # 异常处理
│ │ └── resources/
│ │ ├── application.yml # 配置文件
│ │ ├── static/ # 静态资源(CSS/JS/图片)
│ │ └── templates/ # 模板文件
│ └── test/ # 测试代码
├── pom.xml # Maven 配置
└── README.md # 项目说明

分层职责说明:

层级 职责 包含内容
Controller 接收请求、参数校验、调用服务 @RestController、@GetMapping 等
Service 业务逻辑处理、事务管理 @Service、@Transactional
Repository 数据访问、操作数据库 JpaRepository、MyBatis Mapper
Entity 数据模型、ORM 映射 @Entity、@Table、@Column

7.3 Spring Boot 启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* Spring Boot 启动类
*
* @SpringBootApplication 是一个组合注解,包含:
* - @Configuration:标记为配置类
* - @EnableAutoConfiguration:启用自动配置
* - @ComponentScan:扫描当前包及子包的组件
*/
@SpringBootApplication
public class Application {

public static void main(String[] args) {
// 启动 Spring Boot 应用
SpringApplication.run(Application.class, args);
}
}

/**
* 自定义 SpringApplication 配置
*/
public class CustomApplication {

public static void main(String[] args) {
// 创建 SpringApplication 实例
SpringApplication app = new SpringApplication(Application.class);

// 关闭 Banner(启动时的 ASCII 艺术字)
app.setBannerMode(Banner.Mode.OFF);

// 设置自定义 Banner
app.setBanner(new Banner() {
@Override
public void printBanner(Environment environment,
Class<?> sourceClass,
PrintStream out) {
out.println("========================================");
out.println(" 🚀 我的 Spring Boot 应用 🚀");
out.println("========================================");
}
});

// 运行应用
app.run(args);
}
}

7.4 Spring Boot Maven 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>

<!--
parent 元素指定父 POM
Spring Boot 会统一管理所有依赖的版本
我们只需要指定版本号,不需要写版本号(除非跟父 POM 不一致)
-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>

<groupId>com.example</groupId>
<artifactId>my-spring-boot-demo</artifactId>
<version>1.0.0</version>

<properties>
<java.version>17</java.version>
</properties>

<dependencies>
<!--
1. Web 开发起步依赖
包含:Spring MVC、Tomcat、Jackson JSON 等
一行依赖搞定所有 Web 开发需要的东西
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--
2. Spring Data JPA 起步依赖
包含:Hibernate、Spring ORM、Spring Data JPA
简化数据访问层开发
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!--
3. MySQL 驱动
scope=runtime:运行时需要,编译时不需要
-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>

<!-- 4. Redis 缓存 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- 5. 模板引擎 Thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!--
6. 参数校验起步依赖
包含:Hibernate Validator
支持 @Valid、@NotNull 等校验注解
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<!--
7. Lombok
自动生成 getter/setter/toString 等方法
大幅减少样板代码
-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<!-- 8. 测试起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!--
9. 开发工具
支持热部署(修改代码后自动重启)
显著提升开发效率
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>

<build>
<plugins>
<!-- Spring Boot Maven 插件:打包成可执行 JAR -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- 打包时排除 Lombok,因为运行时不需要 -->
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

八、Spring Boot 核心配置

8.1 application.yml 配置详解

Spring Boot 使用 application.yml(或 application.properties)作为配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# ==========================================
# 服务器配置
# ==========================================
server:
port: 8080 # 服务端口,默认 8080
servlet:
context-path: /api # 上下文路径,即 URL 前缀
tomcat:
threads:
max: 200 # 最大线程数
min-spare: 10 # 最小空闲线程
connection-timeout: 20000 # 连接超时时间(毫秒)

# ==========================================
# Spring 配置
# ==========================================
spring:
application:
name: my-spring-boot-demo # 应用名称,用于服务注册等

# 数据源配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: your_password
hikari:
maximum-pool-size: 20 # 连接池最大连接数
minimum-idle: 5 # 连接池最小空闲连接
connection-timeout: 30000 # 获取连接超时(毫秒)
idle-timeout: 600000 # 空闲连接超时
max-lifetime: 1800000 # 连接最大生命周期

# JPA 配置(Java Persistence API)
jpa:
hibernate:
ddl-auto: update # 表结构自动更新:
# update:自动更新(推荐开发)
# create:每次删除并重新创建
# none:不自动更新
# validate:校验表结构
show-sql: true # 显示 SQL 语句(方便调试)
properties:
hibernate:
format_sql: true # 格式化 SQL 输出

# Redis 配置
data:
redis:
host: localhost # Redis 主机地址
port: 6379 # Redis 端口
password: your_password # Redis 密码(如果没有可省略)
lettuce:
pool:
max-active: 8 # 最大连接数
max-idle: 8 # 最大空闲连接
min-idle: 0 # 最小空闲连接

# 日志配置
logging:
level:
root: INFO # 根日志级别
com.example: DEBUG # 指定包的日志级别
org.hibernate.SQL: DEBUG # Hibernate SQL 日志
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file:
name: logs/app.log # 日志文件路径

# ==========================================
# 自定义配置
# ==========================================
app:
version: 1.0.0
author: 旅人
features:
email-enabled: true # 邮件功能开关
sms-enabled: false # 短信功能开关

8.2 多环境配置

在真实开发中,我们通常有多个环境(开发、测试、生产),每个环境的配置可能不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# application.yml - 主配置(公共配置)
spring:
application:
name: my-spring-boot-demo

# ==========================================
# 开发环境配置(application-dev.yml)
# ==========================================
# spring:
# datasource:
# url: jdbc:mysql://localhost:3306/dev_db
# username: dev_user
# password: dev_password
# jpa:
# show-sql: true
# logging:
# level:
# com.example: DEBUG

# ==========================================
# 生产环境配置(application-prod.yml)
# ==========================================
# spring:
# datasource:
# url: jdbc:mysql://prod-db-server:3306/prod_db
# username: prod_user
# password: ${DB_PASSWORD} # 从环境变量读取
# jpa:
# show-sql: false
# logging:
# level:
# com.example: INFO

激活特定环境的方式:

1
2
3
4
# 方式一:在 application.yml 中指定
spring:
profiles:
active: dev # 激活开发环境
1
2
# 方式二:通过命令行参数指定
java -jar myapp.jar --spring.profiles.active=prod
1
2
3
4
5
6
7
8
// 方式三:在启动类中指定
@SpringBootApplication
@ActiveProfiles("dev")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

8.3 @ConfigurationProperties 配置绑定

将配置文件中的属性绑定到 Java 对象,便于管理和使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* 配置属性类
*
* @ConfigurationProperties 会自动将 app.* 前缀的属性绑定到这个类
* 例如:app.version -> version, app.author -> author
*/
@Data
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {

private String version; // 对应 app.version
private String author; // 对应 app.author
private Features features = new Features(); // 嵌套对象

@Data
public static class Features {
private boolean emailEnabled; // 对应 app.features.email-enabled
private boolean smsEnabled; // 对应 app.features.sms-enabled
}
}

/**
* 使用配置类(推荐方式)
*/
@Configuration
@EnableConfigurationProperties(AppProperties.class)
public class AppConfig {
// AppProperties 已自动注册为 Bean,可以直接注入使用
}

@Service
public class UserService {

@Autowired
private AppProperties appProperties;

public void sendNotification() {
if (appProperties.getFeatures().isEmailEnabled()) {
// 发送邮件通知
emailService.send();
}
if (appProperties.getFeatures().isSmsEnabled()) {
// 发送短信通知
smsService.send();
}
}
}

九、Spring Data 简化数据访问

9.1 Spring Data JPA 详解

Spring Data JPA 是 Spring Data 项目的一部分,它极大简化了数据访问层的开发。通过继承 JpaRepository,你无需编写实现类,就能获得完整的 CRUD 功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/**
* Spring Data JPA Repository
*
* JpaRepository<EntityType, PrimaryKeyType>
* - EntityType:实体类类型
* - PrimaryKeyType:主键类型
*/

// 1. 基本 CRUD( JpaRepository 已提供,无需编写)
public interface UserRepository extends JpaRepository<User, Long> {
// findById, save, deleteById, findAll 等方法已自动提供
}

// 2. 自动方法名解析查询
// Spring Data 会根据方法名自动生成 SQL
public interface UserRepository extends JpaRepository<User, Long> {

// SELECT * FROM users WHERE username = ?
List<User> findByUsername(String username);

// SELECT * FROM users WHERE status = ?
List<User> findByStatus(Integer status);

// SELECT * FROM users WHERE email = ?
User findByEmail(String email);

// SELECT * FROM users WHERE username LIKE '%keyword%'
List<User> findByUsernameContaining(String keyword);

// SELECT * FROM users WHERE status = ? AND username LIKE '%keyword%'
List<User> findByStatusAndUsernameContaining(Integer status, String keyword);

// 分页查询
Page<User> findByStatus(Integer status, Pageable pageable);

// 排序查询
List<User> findByStatusOrderByCreatedAtDesc(Integer status);

// 统计查询
long countByStatus(Integer status);

// 存在性查询
boolean existsByEmail(String email);
}

// 3. 自定义 HQL/JPQL 查询
public interface UserRepository extends JpaRepository<User, Long> {

// 使用 @Query 注解自定义查询
// :status 是命名参数,等同于 ?1
@Query("SELECT u FROM User u WHERE u.status = :status ORDER BY u.createdAt DESC")
List<User> findUsersByStatus(@Param("status") Integer status);

// 聚合查询
@Query("SELECT COUNT(u) FROM User u WHERE u.status = :status")
long countByStatusQuery(@Param("status") Integer status);

// 原生 SQL 查询(nativeQuery = true)
@Query(value = "SELECT * FROM users WHERE status = :status", nativeQuery = true)
List<User> findUsersByStatusNative(@Param("status") Integer status);
}

// 4. 使用 Specification 进行复杂查询
public interface UserRepository extends JpaRepository<User, Long>,
JpaSpecificationExecutor<User> {
// JpaSpecificationExecutor 提供了 findAll(Specification) 方法
}

// 使用 Specification 进行动态查询
@Service
public class UserService {

@Autowired
private UserRepository userRepository;

/**
* 动态查询:根据条件组合构建查询
*/
public List<User> search(UserSearchCriteria criteria) {
// 构建 Specification
Specification<User> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();

// 关键字模糊查询
if (criteria.getKeyword() != null) {
predicates.add(cb.like(root.get("username"),
"%" + criteria.getKeyword() + "%"));
}

// 状态精确查询
if (criteria.getStatus() != null) {
predicates.add(cb.equal(root.get("status"),
criteria.getStatus()));
}

// 日期范围查询
if (criteria.getStartDate() != null) {
predicates.add(cb.greaterThanOrEqualTo(root.get("createdAt"),
criteria.getStartDate()));
}

// 使用 AND 组合所有条件
return cb.and(predicates.toArray(new Predicate[0]));
};

// 执行查询
return userRepository.findAll(spec);
}
}

9.2 Spring Data Redis 缓存操作

Spring Data Redis 提供了简洁的 API 来操作 Redis:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
* Spring Data Redis 操作示例
*/
@Service
public class RedisService {

@Autowired
private StringRedisTemplate redisTemplate;

// ==========================================
// String(字符串)操作
// ==========================================
public void setString(String key, String value) {
// 设置字符串
redisTemplate.opsForValue().set(key, value);

// 设置字符串并指定过期时间
redisTemplate.opsForValue().set(key, value, Duration.ofHours(1));
}

public String getString(String key) {
return redisTemplate.opsForValue().get(key);
}

// ==========================================
// Hash(哈希表)操作
// 适用于存储对象
// ==========================================
public void setHash(String key, String field, String value) {
redisTemplate.opsForHash().put(key, field, value);
}

public Object getHash(String key, String field) {
return redisTemplate.opsForHash().get(key, field);
}

// ==========================================
// List(列表)操作
// 适用于队列、栈等场景
// ==========================================
public void leftPush(String key, String value) {
// 左侧插入
redisTemplate.opsForList().leftPush(key, value);
}

public String rightPop(String key) {
// 右侧弹出
return redisTemplate.opsForList().rightPop(key);
}

// ==========================================
// Set(集合)操作
// 适用于去重、标签等场景
// ==========================================
public void addToSet(String key, String... values) {
redisTemplate.opsForSet().add(key, values);
}

public Set<String> getSetMembers(String key) {
return redisTemplate.opsForSet().members(key);
}

// ==========================================
// 缓存注解示例
// ==========================================

// 查询缓存:先查缓存,缓存不存在则查数据库并写入缓存
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
return userRepository.findById(id).orElse(null);
}

// 更新缓存:执行方法后更新缓存
@CachePut(value = "users", key = "#result.id")
public User saveUser(User user) {
return userRepository.save(user);
}

// 清除缓存:执行方法后清除缓存
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}

// 清除所有缓存
@CacheEvict(value = "users", allEntries = true)
public void clearUserCache() {
// 清空所有 "users" 缓存
}
}

十、Spring Boot 日志与监控

10.1 日志配置

Spring Boot 默认使用 Logback 作为日志框架,我们可以通过 application.yml 进行配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
logging:
level:
root: INFO # 根日志级别:INFO 及以上
com.example: DEBUG # 指定包:DEBUG 及以上
org.springframework.web: DEBUG # Spring Web 详细日志
org.hibernate.SQL: DEBUG # Hibernate SQL 日志
org.hibernate.type.descriptor.sql.BasicBinder: TRACE # SQL 参数绑定详情
pattern:
# 控制台输出格式
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
# 文件输出格式
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
file:
name: logs/application.log # 日志文件路径
max-size: 10MB # 单个日志文件最大 10MB
max-history: 30 # 保留最近 30 天的日志

10.2 Spring Boot Actuator 监控端点

Spring Boot Actuator 提供了生产级别的监控功能:

1
2
3
4
5
<!-- 添加 Actuator 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 开启 Actuator 端点
management:
endpoints:
web:
exposure:
include: health,info,metrics,env,beans,caches,conditions
endpoint:
health:
show-details: when_authorized # 健康检查详情(需要授权才能看)
health:
redis:
enabled: true # 包含 Redis 健康检查
db:
enabled: true # 包含数据库健康检查

常用 Actuator 端点:

端点 说明 示例
/actuator/health 应用健康状态 查看应用是否正常运行
/actuator/info 应用信息 查看应用基本信息
/actuator/metrics 性能指标 查看内存、CPU、请求数等
/actuator/env 环境变量 查看所有配置的环境变量
/actuator/beans 所有 Bean 查看容器中所有 Bean
/actuator/mappings 所有 URL 映射 查看所有接口路径
/actuator/conditions 配置条件 查看自动配置结果

十一、常见问题与最佳实践

11.1 常见问题与解决方案

问题一:循环依赖(Circular Dependency)

当两个 Bean 互相依赖时会形成循环:

  • A Bean 构造器需要注入 B
  • B Bean 构造器需要注入 A

解决方案:

  1. 使用 @Lazy 延迟加载其中一个依赖
  2. 重构代码,消除循环依赖
  3. 使用 Setter 注入代替构造器注入
1
2
3
4
5
6
7
8
9
// 解决方案:@Lazy 延迟加载
@Component
public class A {
private B b;

public A(@Lazy B b) {
this.b = b;
}
}

问题二:事务不生效

常见原因是内部方法调用(this.method())不经过代理对象。

解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class UserService {
@Autowired
private UserService self; // 注入自身代理对象

@Transactional
public void outerMethod() {
self.innerMethod(); // 通过代理对象调用
}

@Transactional
public void innerMethod() {
// 现在有事务了
}
}

11.2 Spring Boot 开发建议

实践 说明 推荐程度
分层清晰 Controller → Service → Repository ✅✅✅
统一响应格式 所有接口返回 Result ✅✅✅
全局异常处理 @ControllerAdvice 统一处理异常 ✅✅✅
参数校验 @Valid + BindingResult ✅✅✅
事务合理使用 不要在事务中做耗时操作 ✅✅✅
日志规范 使用 SLF4J,合理配置日志级别 ✅✅
配置文件分离 多环境配置,敏感信息加密 ✅✅

十二、总结

12.1 核心知识点回顾

12.2 Spring 学习路线

12.3 下一步推荐学习

  • 📖 Spring Cloud 微服务:服务注册、配置中心、熔断器、网关
  • 📖 Spring Security 安全:认证授权、OAuth2、JWT
  • 📖 Spring Batch 大数据:批处理框架
  • 📖 Spring Integration:消息集成
  • 📖 Spring 源码解读:深入理解框架设计思想

💡 写给读者的话:Spring 是 Java 开发者的必备技能,它不仅简化了企业级开发,更提供了一种优秀的软件设计思想。从 Spring Framework 到 Spring Boot,再到 Spring Cloud,Spring 生态已经成为了 Java 后端开发的事实标准。希望本文能帮助你建立完整的 Spring 技术体系,为今后的深入学习打下坚实基础!💪


📅 本文首次发布于 2026 年 5 月 24 日