文章

《Spring 框架知识个人笔记》

《Spring 框架知识个人笔记》

一、IOC(控制反转)

1. 介绍

IoC(Inverse of Control:控制反转)是一种设计思想。

核心概念:

  • 将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理
  • 对象之间的相互依赖关系交给 IoC 容器来管理
  • 由 IoC 容器完成对象的注入
  • 而且是一个单例的注入

2. 解决问题

✅ 解决对象之间的耦合度过高的问题


3. 功能

(1)依赖注入功能

IoC 容器的本质:

  • IoC 容器是 Spring 用来实现 IoC 的载体
  • IoC 容器实际上就是个 Map(key, value)
  • Map 中存放的是各种对象

二、AOP(面向切面编程)

1. 介绍

AOP(Aspect-Oriented Programming:面向切面编程)

核心思想:

  • 能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来
  • 例如:事务处理、日志管理、权限控制等

优点:

  • ✅ 减少系统的重复代码
  • ✅ 降低模块间的耦合度
  • ✅ 有利于未来的可拓展性和可维护性

2. Spring AOP 支持的代理类型

Spring AOP 同时支持 CGLIB、JDK 动态代理

选择规则

情况使用的代理方式
目标类实现了接口JDK 动态代理
目标类没有实现接口CGLIB(通过继承目标类得到一个子类,覆盖其中的方法)

3. Spring AOP 和 AspectJ AOP 有什么区别

特性Spring AOPAspectJ AOP
增强时机运行时增强编译时增强
实现方式基于代理(Proxying)基于字节码操作(Bytecode Manipulation)
性能切面较少时,两者性能差异不大切面太多时,AspectJ 性能更好

💡 建议:当切面太多的话,最好选择 AspectJ


三、Spring Bean

1. Spring Bean 的作用域

作用域说明
singleton唯一 bean 实例
⭐️ Spring 中的 bean 默认都是单例的
prototype每次请求都会创建一个新的 bean 实例
request每一次 HTTP 请求都会产生一个新的 bean
该 bean 仅在当前 HTTP request 内有效
session每一次 HTTP 请求都会产生一个新的 bean
该 bean 仅在当前 HTTP session 内有效

2. Spring 中的单例 bean 的线程安全问题

问题分析

何时会有线程安全问题?

  • 当多个线程操作同一个对象的时候
  • 对这个对象的成员变量的写操作会存在线程安全问题

实际情况:

  • 一般情况下,我们常用的 Controller、Service、Dao 这些 Bean 是无状态的
  • 无状态的 Bean 不能保存数据,因此是线程安全的

解决方案

方案 1:配置为 prototype 模式
1
2
3
4
5
@Scope("prototype")
@Service
public class UserService {
    // ...
}

特点:

  • ✅ 每一个线程都创建一个 prototype 实例
  • ❌ 会产生很多的实例,消耗较多的内存空间

方案 2:使用 ThreadLocal 变量
1
2
3
4
5
6
@Service
public class UserService {
    private ThreadLocal<User> currentUser = new ThreadLocal<>();
    
    // ...
}

特点:

  • ✅ 为每一条线程设置变量副本
  • ✅ 不会消耗过多内存

3. @Component 和 @Bean 的区别是什么?

特性@Component@Bean
作用对象作用于类作用于方法
扫描方式通过类路径扫描来自动侦测以及自动装配到 Spring 容器中
(使用 @ComponentScan 注解定义要扫描的路径)
在标有该注解的方法中定义产生这个 bean
@Bean 告诉了 Spring 这是某个类的实例,当需要用它的时候还给我
自定义性较弱较强
使用场景自己编写的类第三方库中的类需要装配到 Spring 容器时

💡 总结:@Bean 注解比 @Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册 bean。


4. Spring Bean 的生命周期

Spring Bean 的生命周期分为四个主要阶段以及多个扩展点。


四个主要阶段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──────────────┐
│ 1. 实例化     │  Instantiation
│ Instantiation│
└──────┬───────┘
       ↓
┌──────────────┐
│ 2. 属性赋值   │  Populate
│ Populate     │
└──────┬───────┘
       ↓
┌──────────────┐
│ 3. 初始化     │  Initialization
│ Initialization│
└──────┬───────┘
       ↓
┌──────────────┐
│ 4. 销毁      │  Destruction
│ Destruction  │
└──────────────┘

主要逻辑

主要逻辑都在 doCreate() 方法中,逻辑很清晰,就是顺序调用以下三个方法:

  1. createBeanInstance() → 实例化
  2. populateBean() → 属性赋值
  3. initializeBean() → 初始化

Bean 的完整生命流程

  1. Bean 容器找到配置文件中 Spring Bean 的定义

  2. Bean 容器利用 Java Reflection API 创建一个 Bean 的实例

  3. 如果涉及到一些属性值,利用 set() 方法设置一些属性值

  4. 如果 Bean 实现了 BeanNameAware 接口
    • 调用 setBeanName() 方法,传入 Bean 的名字
  5. 如果 Bean 实现了 BeanClassLoaderAware 接口
    • 调用 setBeanClassLoader() 方法,传入 ClassLoader 对象的实例
  6. 如果还实现了其他 *.Aware 接口
    • 就调用相应的方法
  7. 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象
    • 执行 postProcessBeforeInitialization() 方法
  8. 如果 Bean 实现了 InitializingBean 接口
    • 执行 afterPropertiesSet() 方法
  9. 如果 Bean 在配置文件中的定义包含 init-method 属性
    • 执行指定的方法
  10. 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象
    • 执行 postProcessAfterInitialization() 方法
  11. 当要销毁 Bean 的时候,如果 Bean 实现了 DisposableBean 接口
    • 执行 destroy() 方法
  12. 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性
    • 执行指定的方法

5. Spring Bean 的循环依赖问题,以及解决方式

问题定义

Bean 的循环依赖:

  • Spring bean 的创建过程中,会遇到对象与对象之间的依赖
  • 比如 A 类中有一个属性 B 类
  • 那么 Spring 在创建 A 对象的时候发现了需要一个 B 对象
  • 这个时候 Spring 就会暂时停止对 A 的创建,会先去创建 B 对象
  • 等 B 对象创建完之后,将 B 对象注入到 A 对象中,再接着创建 A 对象剩下的工作

问题场景:

  • 假如 A 类中需要一个 B 对象,B 类需要一个 A 对象
  • 就会产生死循环,这就是循环依赖问题

解决方案

结论:Spring Bean的二阶段创建 + Spring 三级缓存设计。 先实例化A的半对象,放入二级缓存,再实例化依赖的B对象,把依赖的B对象注入A属性,A就可以放入一级缓存了。 Spring AOP的引入导致代理对象的存在,所以实例化A对象的时候,不再是直接创建A的半对象放入二级缓存,而是创建一个A的生产工厂对象, 放入三级缓存,当B需要注入A的时候,从A的工厂对象中去判断A是否需要被代理,来确保是返回代理A对象还是A得原始半对象放入二级缓存。

Spring 实例化 Bean 的两个步骤
  1. Bean 对象实例化(还只是个半成品)
  2. Bean 对象属性实例化(完全体)

三级缓存

一级缓存:保存的是完整的 Bean 对象 二级缓存:保存的是 Bean 的半成品 三级缓存:保存的是 Bean 的工厂

四、Spring 框架中用到了哪些设计模式?

设计模式应用场景
工厂设计模式Spring 使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象
代理设计模式Spring AOP 功能的实现
单例设计模式Spring 中的 Bean 默认都是单例的
模板方法模式Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类
包装器设计模式项目需要连接多个数据库,不同的客户在每次访问中根据需要会去访问不同的数据库
这种模式让我们可以根据客户的需求能够动态切换不同的数据源
观察者模式Spring 事件驱动模型就是观察者模式很经典的一个应用
适配器模式Spring AOP 的增强或通知(Advice)使用到了适配器模式
Spring MVC 中也是用到了适配器模式适配 Controller

五、Spring 事务

Spring 管理事务的方式有几种?

1. 编程式事务

在代码中硬编码(❌ 不推荐使用)


2. 声明式事务(✅ 推荐使用)

  • 基于 XML 的声明式事务
  • 基于注解的声明式事务

Spring 事务中的隔离级别有哪几种?

TransactionDefinition 接口中定义了五个表示隔离级别的常量:

隔离级别说明脏读不可重复读幻读
ISOLATION_DEFAULT使用后端数据库默认的隔离级别
• MySQL 默认:REPEATABLE_READ
• Oracle 默认:READ_COMMITTED
---
ISOLATION_READ_UNCOMMITTED
(读未提交)
最低的隔离级别
允许读取尚未提交的数据变更
✅ 可能✅ 可能✅ 可能
ISOLATION_READ_COMMITTED
(读已提交)
允许读取并发事务已经提交的数据
可以阻止脏读
❌ 不会✅ 可能✅ 可能
ISOLATION_REPEATABLE_READ
(可重复读)
对同一字段的多次读取结果都是一致的
除非数据是被本身事务自己所修改
可以阻止脏读和不可重复读
❌ 不会❌ 不会✅ 可能
ISOLATION_SERIALIZABLE
(可串行化)
最高的隔离级别
完全服从 ACID 的隔离级别
所有的事务依次逐个执行
可以防止脏读、不可重复读以及幻读
⚠️ 严重影响程序的性能,通常不会用到
❌ 不会❌ 不会❌ 不会

Spring 事务中哪几种事务传播行为?

支持当前事务的情况

传播行为说明
PROPAGATION_REQUIRED如果当前存在事务,则加入该事务
如果当前没有事务,则创建一个新的事务
PROPAGATION_SUPPORTS如果当前存在事务,则加入该事务
如果当前没有事务,则以非事务的方式继续运行
PROPAGATION_MANDATORY如果当前存在事务,则加入该事务
如果当前没有事务,则抛出异常
(mandatory:强制性)

不支持当前事务的情况

传播行为说明
PROPAGATION_REQUIRES_NEW创建一个新的事务
如果当前存在事务,则把当前事务挂起
PROPAGATION_NOT_SUPPORTED以非事务方式运行
如果当前存在事务,则把当前事务挂起
PROPAGATION_NEVER以非事务方式运行
如果当前存在事务,则抛出异常

其他情况

传播行为说明
PROPAGATION_NESTED如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行
如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED

六、Spring MVC

1. MVC 设计模式

以早期 JavaWeb 开发模式举例:

层次技术职责
Model 模型
(JavaBean)
Entity、Service、Dao承载数据,并对用户提交请求进行计算
• 数据承载 Bean:实体类,专门承载业务数据(如 Student、User 等)
• 业务处理 Bean:Service 或 Dao 对象,专门用于处理用户提交的请求
View 视图
(JSP)
JSP为用户提供使用界面,与用户直接进行交互
Controller 控制器
(Servlet)
Servlet用于将用户请求转发给相应的 Model 进行处理
并将处理 Model 的计算结果向用户提供相应响应

2. 基于 MVC 开发模式下的 Spring MVC 模式

层次组成
Model 模型• 数据承载 Bean(entity)
• 业务处理 Bean(service、dao 层)
View 视图返回的 ModelAndView 类,返回的视图
Controller 控制器正常的控制层(Controller 层)

3. 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
┌─────────┐
│  Client │ 客户端
└────┬────┘
     │ 1. 发送请求
     ↓
┌─────────────────────┐
│ DispatcherServlet   │ 前端控制器
└────┬────────────┬───┘
     │            │
     │ 2. 查找   │ 5. 返回
     │  Handler   │   结果
     ↓            ↓
┌──────────────┐ ┌────────────┐
│ HandlerMapping│ │ViewResolver│
│ 处理器映射器   │ │视图解析器   │
└──────┬───────┘ └─────┬──────┘
       │                │
       │ 3. 执行        │ 4. 解析
       ↓                ↓
┌───────────────┐ ┌────────────┐
│HandlerAdapter │ │   View     │
│ 处理器适配器   │ │   视图     │
└───────────────┘ └────────────┘

4. Spring MVC 工作流程

流程说明(重要⭐️)

第 0 步:项目启动

  • 在第一条请求到来之前,也就是 Spring 项目启动之后
  • Spring 会先根据配置文件,扫描相应包下的 java 文件
  • 把所有带有 Spring bean 注解的文件都挑选出来
  • 通过反射机制 + 单例模式实例化这些 spring bean
  • 存到 IOC 容器中供其他 spring bean 依赖注入使用

第 1 步:请求到达

  • 客户端发送一条请求
  • Tomcat 的 socket 监听到了这条请求
  • 解析 URL,交给 Tomcat 下部署的相应项目
  • 其实就是交给了相应项目的 Spring MVC 的 DispatcherServlet
    • Tomcat 只能与 servlet 交互

第 2 步:查找处理器

  • DispatcherServlet 根据请求信息调用 HandlerMapping(处理映射器)
  • 找到对应的 handler(也就是 controller)

第 3 步:处理请求

  • 通过找到的 Controller,交由 HandlerAdapter(处理适配器)处理
  • 适配器会根据不同的 Controller 中方法的逻辑去处理请求

第 4 步:返回数据

  • 处理器处理完业务后,返回数据
  • 数据可以是视图类型的 ModelAndView 对象
  • 也可以是 JSON 格式的数据

第 5 步:视图解析(如果需要)

  • 如果是视图类型的话
  • 还需要 ViewResolver(视图解析器)处理一下
  • 最后将结果返回给 DispatcherServlet 返回给请求者

七、Spring Boot

1. Spring Boot 的启动流程

Spring Boot 的启动流程主要包括以下几个步骤:

步骤 1:SpringApplication 实例化

1
2
3
4
5
6
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

步骤 2:准备环境

  • 加载配置文件(application.properties/yml)
  • 初始化系统属性
  • 准备应用程序上下文环境

步骤 3:创建应用上下文

根据应用类型创建对应的 ApplicationContext:

  • Web 应用:创建 AnnotationConfigServletWebServerApplicationContext
  • 非 Web 应用:创建 AnnotationConfigApplicationContext

步骤 4:准备上下文

  • 设置环境变量
  • 执行 ApplicationContextInitializer
  • 打印 Banner

步骤 5:刷新上下文

  • 扫描并加载所有的 Bean 定义
  • 实例化单例 Bean
  • 初始化 Bean
  • 执行自动配置

步骤 6:启动完成

  • 执行 CommandLineRunner 和 ApplicationRunner
  • 发布 ApplicationReadyEvent 事件
  • 应用启动完成,准备接收请求

2. Spring Boot 自动装配原理

Spring Boot 的自动装配是通过 @EnableAutoConfiguration 注解实现的。


核心注解

1
2
3
4
@SpringBootApplication
    ├── @SpringBootConfiguration
    ├── @EnableAutoConfiguration  // 核心
    └── @ComponentScan

@EnableAutoConfiguration 的工作原理

步骤 1:导入 AutoConfigurationImportSelector
1
2
@EnableAutoConfiguration
    └── @Import(AutoConfigurationImportSelector.class)

步骤 2:加载自动配置类

AutoConfigurationImportSelector 会:

  1. 读取 META-INF/spring.factories 文件
  2. 获取所有的自动配置类(xxxAutoConfiguration)
  3. 根据条件注解(@ConditionalOnXxx)判断是否需要加载

步骤 3:条件装配

常见的条件注解:

条件注解说明
@ConditionalOnClass当类路径下有指定的类时才会装配
@ConditionalOnMissingClass当类路径下没有指定的类时才会装配
@ConditionalOnBean当容器中有指定的 Bean 时才会装配
@ConditionalOnMissingBean当容器中没有指定的 Bean 时才会装配
@ConditionalOnProperty当配置文件中有指定的属性时才会装配

步骤 4:属性绑定

通过 @EnableConfigurationProperties 和 @ConfigurationProperties 注解:

  • 将配置文件中的属性绑定到 Java 对象上

自动装配流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1. 启动应用
    ↓
2. 加载 @SpringBootApplication
    ↓
3. 触发 @EnableAutoConfiguration
    ↓
4. AutoConfigurationImportSelector 扫描
    ↓
5. 读取 META-INF/spring.factories
    ↓
6. 加载所有 xxxAutoConfiguration 类
    ↓
7. 根据 @ConditionalOnXxx 判断是否装配
    ↓
8. 装配符合条件的 Bean
    ↓
9. 绑定配置属性
    ↓
10. 应用启动完成

示例:DataSource 自动配置

1
2
3
4
5
6
7
8
9
10
11
@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public DataSource dataSource(DataSourceProperties properties) {
        // 创建 DataSource
    }
}

工作流程:

  1. 判断类路径下是否有 DataSource 类
  2. 如果有,且容器中没有 DataSource Bean
  3. 则根据 application.properties 中的配置创建 DataSource
  4. 将 DataSource 注册到容器中

核心优势

Spring Boot 自动装配的核心优势:

  • ✅ 约定大于配置:提供合理的默认配置
  • ✅ 开箱即用:添加依赖即可使用
  • ✅ 灵活可配:可以通过配置文件覆盖默认配置
  • ✅ 条件装配:根据实际情况智能装配

📚 参考资料:本文内容基于个人学习笔记整理

本文由作者按照 CC BY 4.0 进行授权