Spring个人知识体系总结
Spring知识体系总结
本文基于 Spring 框架核心原理,系统整理 IOC、AOP、事务、循环依赖、SpringMVC 等核心知识点,适合面试复习与源码学习。
目录
- Spring知识体系总结
- 一、Spring 核心概述
- 1.1 配置方式与容器启动
- 1.2 Spring 扫描机制
- 1.3 BeanDefinition:声明式 vs 编程式
- 二、Bean 的生命周期(从生成到销毁)
- 2.1 完整生命周期流程
- 2.2 推断构造方法
- 2.3 实例化方式
- 2.4 Aware 接口
- 2.5 Bean 的销毁
- 三、依赖注入(DI)
- 3.1 手动注入
- 3.2 XML 的 autowire 自动注入
- 3.3 @Autowired 自动注入
- 3.4 @Resource 自动注入
- 四、循环依赖与三级缓存
- 4.1 问题出现
- 4.2 三级缓存机制
- 4.3 为什么需要三级缓存?单级不能解决吗?
- 4.4 createBean 与 getBean 的相互调用
- 4.5 关键源码方法
- 五、Spring AOP
- 5.1 核心概念
- 5.2 动态代理方式
- 5.3 CGLIB 原理
- 5.4 代理工厂与实现方式
- 5.5 Advisor
- 六、Spring 事务
- 6.1 事务本质
- 6.2 基本执行原理
- 6.3 事务传播机制
- 6.4 事务失效场景
- 七、Spring 整合 MyBatis
- 7.1 整合核心思想
- 7.2 Spring-MyBatis 执行流程
- 7.3 一级缓存失效问题
- 八、SpringMVC
- 8.1 原生 MVC 弊端
- 8.2 SpringMVC 核心组件
- 8.3 父子容器
- 九、手写 Spring 心得(注解与工具)
- 9.1 @Target 注解
- 9.2 RetentionPolicy 保留策略
- 9.3 Scope 作用域
- 9.4 类型转化
- 9.5 元数据读取器
一、Spring 核心概述
1.1 配置方式与容器启动
两种配置方式:
- AnnotationConfigApplicationContext:注解配置,可指定扫描路径、直接定义 Bean
- ClassPathXmlApplicationContext:XML 形式配置扫描路径
ApplicationContext 继承体系:
| 类/接口 | 功能 |
|---|---|
ConfigurableApplicationContext | 增加事件监听器、BeanFactoryPostProcessor、Environment 设置 |
AbstractApplicationContext | 实现 ConfigurableApplicationContext |
GenericApplicationContext | 继承 AbstractApplicationContext,实现 BeanDefinitionRegistry,可注册 BeanDefinition |
AnnotationConfigRegistry | 处理@Configuration、@Bean,支持扫描 |
AnnotationConfigApplicationContext | 拥有以上全部功能 |
核心功能:扫描 Bean、构建容器、资源加载、获取运行时环境、事件发布。
启动流程:
- 构造 BeanFactory
- 解析配置(
@ComponentScan等) - 国际化
- 初始化
ApplicationEventMulticaster - 将
ApplicationListener添加到ApplicationContext - 创建非懒加载的单例 Bean
- 调用 Lifecycle Bean 的
start() - 发布
ContextRefreshedEvent事件
1.2 Spring 扫描机制
- 解析
AppConfig.class,得到扫描路径 - 遍历扫描路径下的所有 Java 类,发现
@Component、@Service等注解则记录到BeanDefinitionMap - Spring 根据规则生成 beanName 作为 key,当前类作为 value
为什么不使用多线程扫描?
- 扫描是一次性操作,性能收益小,风险大
- 少量类提升不明显,大量类受限于 I/O 或类加载器瓶颈
- Spring 启动瓶颈不在扫描,而在类加载、Bean 实例化、注解解析、AOP 代理生成
- 使用索引(indexer)、延迟加载、减少扫描范围等方式更有效
1.3 BeanDefinition:声明式 vs 编程式
- 声明式定义:使用
@Bean、@Component等注解定义 - 编程式定义:用代码从上下文获取 Bean、用代码写事务
无论哪种方式,最终都会被 Spring 解析为 BeanDefinition 对象放入容器。
生成 BeanDefinition 的过程:
- Spring 扫描包路径
- 读取 class 文件,使用ASM 技术(非 JVM 加载)转化为元数据
- 提取类名、接口名、注解等信息
二、Bean 的生命周期(从生成到销毁)
2.1 完整生命周期流程
1. InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation() [实例化前] 2. 实例化(推断构造方法) 3. MergedBeanDefinitionPostProcessor.postProcessMergedBeanDefinition() 4. InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation() [实例化后] 5. 自动注入 6. InstantiationAwareBeanPostProcessor.postProcessProperties() [处理属性] 7. Aware 接口回调(BeanNameAware、BeanClassLoaderAware、BeanFactoryAware) 8. BeanPostProcessor.postProcessBeforeInitialization() [初始化前] 9. 初始化(@PostConstruct → InitializingBean.afterPropertiesSet() → init-method) 10. BeanPostProcessor.postProcessAfterInitialization() [初始化后,AOP在此] 11. 使用 Bean 12. 销毁(@PreDestroy → DisposableBean.destroy() → destroy-method)简化版:
[实例化] ↓ [@Autowired 注入] ↓ [Aware 回调] ↓ [@PostConstruct] ↓ [InitializingBean.afterPropertiesSet()] ↓ [initMethodName()] ↓ [BeanPostProcessor.afterInitialization()] ← AOP 在此阶段实现 ↓ [使用 Bean] ↓ [销毁]2.2 推断构造方法
| 场景 | 结果 |
|---|---|
| 有默认构造 | 选默认 |
| 只有一个构造 | 选唯一 |
| 有多个构造且有默认无参 | 选无参 |
| 多个构造且无无参 | 报错 |
多个构造中某个有@Autowired | 选该注解方法 |
2.3 实例化方式
- Supplier创建对象
- 工厂方法(
factory-bean/factory-method,类似@Bean) - 推断构造方法(最常用,反射实例化)
- 若未找到构造方法,则寻找
@Lookup注解获取实例
- 若未找到构造方法,则寻找
2.4 Aware 接口
| Aware 接口 | 作用 |
|---|---|
BeanNameAware | 回传 beanName |
BeanClassLoaderAware | 回传 classLoader |
BeanFactoryAware | 回传 beanFactory |
ApplicationContextAware | 回传 ApplicationContext(通过 ApplicationContextAwareProcessor 处理) |
2.5 Bean 的销毁
- 发布
ContextClosedEvent事件,通知容器即将关闭 - 调用
LifecycleProcessor.onClose(),处理 SmartLifecycle Bean - 销毁单例 Bean:
- disposableBean 从单例池移除
- 调用
destroy()方法 - 按依赖顺序递归销毁
- 清空用户手动设置的单例 Bean
销毁时的设计模式:
Spring 使用DisposableBeanAdapter将DisposableBean、AutoCloseable等接口适配为统一的DisposableBean接口,涉及适配器模式 + 策略模式 + 工厂模式。
三、依赖注入(DI)
3.1 手动注入
- set 方法注入
- 构造方法注入
3.2 XML 的 autowire 自动注入
| 模式 | 说明 |
|---|---|
byType | 按类型注入 |
byName | 按名称注入 |
constructor | 构造方法注入 |
default | 默认 |
no | 不注入 |
需要 set 方法!构造方法注入相当于 byType + byName。
3.3 @Autowired 自动注入
@Autowired是byType 和 byName 的结合:
| 位置 | 规则 |
|---|---|
| 属性上 | 先根据属性类型找,找到多个再根据属性名确定 |
| 构造方法上 | 先根据参数类型找,找到多个再根据参数名确定 |
| set 方法上 | 先根据参数类型找,找到多个再根据参数名确定 |
寻找注入点流程:
- 遍历当前类所有属性字段
- 查看是否存在
@Autowired、@Value、@Inject,存在则为注入点 - static 字段不注入(static 属于类本身,Spring 依赖注入面向对象,无法设置类级别成员)
- 获取
@Autowired的 required 属性值 - 构造
AutowiredFieldElement存入currElements - 遍历所有方法,判断是否是桥接方法,找到原方法
- 查看方法是否有注解,static 不注入
- 构造
AutowiredMethodElement存入currElements - 遍历父类直到没有父类
- 将
currElements封装成InjectionMetadata缓存
桥接方法:Java 泛型通过类型擦除实现,编译器为保证子类重写父类泛型方法的多态性,会生成桥接方法。处理时需要找到对应的原方法。
注入点注入:
- 字段注入:字段封装为
DependencyDescriptor→BeanFactory.resolveDependency()查找 → 反射赋值 - Set 方法注入:参数封装为
MethodParameter→DependencyDescriptor→resolveDependency()→ 反射执行方法
3.4 @Resource 自动注入
| 特性 | @Autowired | @Resource |
|---|---|---|
| 默认方式 | byType | byName |
| 找不到匹配 | required=false 注入 null,否则抛异常 | 回退到 byType |
流程:
- 判断 BeanFactory 中是否存在注入点名字对应的 Bean
- 存在 → 按 name 注入
- 不存在 → 判断是否指定了 name 属性
- 指定了 → 按 name 注入
- 未指定 → 和
@Autowired一致(先 byType 后 byName)
四、循环依赖与三级缓存
4.1 问题出现
Spring 中循环依赖是问题,因为 Bean 不是简单 new 出来的,而是根据生命周期创建的:
ABean 创建 → 依赖 B 属性 → 触发 BBean 创建 → B 依赖 A 属性 → 需要 ABean(但 A 还在创建中)4.2 三级缓存机制
| 缓存级别 | 名称 | 作用 |
|---|---|---|
| 一级缓存 | singletonObjects | 已完成初始化的单例 Bean(可直接使用) |
| 二级缓存 | earlySingletonObjects | 提前暴露的"早期 Bean"(已实例化但未填充属性) |
| 三级缓存 | singletonFactories | 用于生成"早期 Bean"的工厂对象(ObjectFactory) |
循环依赖解决示例(A 依赖 B,B 又依赖 A):
- 创建 A:实例化 A → 将 A 的工厂放入三级缓存
- 注入 B:发现需要 B,转而创建 B
- 创建 B:实例化 B → 发现需要 A → 从三级缓存获取 A 的早期引用 → 移到二级缓存
- 继续初始化 B,完成注入 A
- 返回继续初始化 A
4.3 为什么需要三级缓存?单级不能解决吗?
由于AOP 的存在,循环依赖用单缓存无法解决代理对象的问题:
- B 注入 A 的原始对象时,若 A 经过 AOP,会导致 B 里的 A 是原始对象,而真正的 A 应该是代理后对象
- 则 B 依赖的 A 和最终的 A不是同一个对象
singletonFactories 的作用:
- 存的是
ObjectFactory(函数式接口,支持 Lambda) () → getEarlyBeanReference(beanName, mbd, bean)- 作用:设置原始对象 →将原始对象进行 AOP 处理
完整创建逻辑:
- A 创建时,A 原始对象存入
singletonFactories - A 创建需要 B,去创建 B
- 创建 B 需要 A,根据 A 的 beanName 去
singletonFactories获取ObjectFactory,执行getEarlyBeanReference - 获取 A 原始对象的代理对象,存入
earlySingletonObjects - B 创建完成,注入 A 的代理对象
- A 继续初始化,由于参与过 AOP,不会重复进行(通过
earlyProxyReferences判断) - 最后将 A 放入
singletonObjects
4.4 createBean 与 getBean 的相互调用
createBean() → 调用 getBean() → 注入依赖 ↑ ↓ 按需创建 ← getBean() 调用 createBean()createBean()调用getBean():为字段注入依赖getBean()调用createBean():Bean 未实例化时按需创建(单例已创建则直接返回缓存)- 三级缓存机制支持循环依赖的解决
4.5 关键源码方法
org.springframework.context.support.AbstractApplicationContext#refresh (入口) org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization org.springframework.beans.factory.config.ConfigurableListableBeanFactory#preInstantiateSingletons org.springframework.beans.factory.support.AbstractBeanFactory#getBean (万恶之源) org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean五、Spring AOP
5.1 核心概念
| 概念 | 说明 |
|---|---|
| Aspect | 切面,可以定义 Pointcut |
| Join point | 连接点,某个方法 |
| Advice | 通知,连接点上的具体动作(日志、异常处理等) |
| Pointcut | 切点,匹配一个或多个连接点 |
| Introduction | 使用@DeclareParents给匹配的类添加接口默认实现 |
| Target object | 目标对象,被代理的对象 |
| AOP proxy | 代理对象 |
| Weaving | 织入,创建代理对象的动作 |
5.2 动态代理方式
| 特性 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 机制 | 基于接口,反射实现 | 基于类,继承机制实现 |
| 适用对象 | 必须实现至少一个接口 | 不要求实现接口,不能代理 final 方法/类 |
| 性能 | 足够好,实现简单 | 某些场景更高,但代理对象更复杂 |
| 灵活性 | 基于接口,更灵活 | 基于继承,可能受限 |
Spring AOP 选择策略:优先 JDK 动态代理,目标对象没有实现接口则自动切换到 CGLIB。
5.3 CGLIB 原理
Enhancer是 CGLIB 提供的类,用于运行时动态生成类的子类:
- 被代理类是父类,代理类是子类
- 代理对象就是代理类的实例
- 常用于:Spring AOP、事务管理、日志记录
- 可配置多个回调,自定义不同方法的回调规则
代理的含义:在不改变原始对象的前提下,对其进行功能增强和行为拦截。
CGLIB AOP 大致流程:
- 生成代理类
UserServiceProxy,继承UserService - 代理类重写父类方法(如
test()) - 代理类包含
target属性(被代理对象) - 执行代理方法逻辑:
@Before切面逻辑 → 调用target.test()
5.4 代理工厂与实现方式
ProxyFactory:封装了两种代理方式,根据是否实现接口选择(实现了用 JDK,否则用 CGLIB)。
代理 Bean 的实现方式:
| 方式 | 说明 |
|---|---|
ProxyFactoryBean | 只针对单个 Bean 代理 |
BeanNameAutoProxyCreator | 通过 bean 名字自动代理 |
DefaultAdvisorAutoProxyCreator | 找所有 Advisor,根据 PointCut 和 Advice 代理 |
| 注解形式(常用) | @Aspect+@Component |
5.5 Advisor
- Pointcut:指定代理规则,匹配类中的方法
- Advice:
Before Advice:方法之前执行After returning advice:return 后执行After throwing advice:抛异常后执行After (finally) advice:finally 后执行Around advice:功能最强,可自定义执行顺序
六、Spring 事务
6.1 事务本质
Spring 事务基于AOP 实现,失效原因核心在于未被成功代理就使用。
@EnableTransactionManagement作用:
AutoProxyRegistrar:开启自动代理ProxyTransactionManagementConfiguration:判断@Transactional注解并进行代理
6.2 基本执行原理
- Bean 执行创建生命周期,匹配 Advisor(
@Transactional注解) - 若存在,Bean 需要动态代理产生代理对象
- 代理对象执行:
- 再次匹配
@Transactional - 执行
TransactionInterceptor.invoke() - 新建数据库连接
- 修改
autocommit为false - 执行业务方法
- 无异常提交,有异常回滚
- 再次匹配
6.3 事务传播机制
| 传播机制 | 说明 |
|---|---|
REQUIRED(默认) | 共享事务 |
REQUIRES_NEW | 完全独立,a、b 独立提交 |
SUPPORTS | 存在事务则加入,否则非事务运行 |
REQUIRES_NEW 执行流程(a() 调用 b()):
- 代理对象执行 a(),事务管理器新建连接 a
- 连接 a 的 autocommit 改成 false,设置到 ThreadLocal
- 执行 a() 中的 SQL
- 执行 a() 调用 b() →挂起连接 a(从 ThreadLocal 移除)
- 新建连接 b,autocommit 改成 false,设置到 ThreadLocal
- 执行 b() 中的 SQL
- b() 执行完,从 ThreadLocal 拿连接 b提交
- 恢复连接 a(挂起资源重新设置到 ThreadLocal)
- a() 执行完,ThreadLocal 中连接 a提交
6.4 事务失效场景
- 类内部调用:类本身调用
@Transactional方法,未经代理直接调用,事务失效 - 其他待补充…
七、Spring 整合 MyBatis
7.1 整合核心思想
将其他框架中所产生的对象放入 Spring 容器中。
7.2 Spring-MyBatis 执行流程
Spring 嵌入 MyBatis 部分:
- 通过
@MapperScan导入MapperScannerRegistrar MapperScannerRegistrar实现ImportBeanDefinitionRegistrar,Spring 启动时调用registerBeanDefinitions- 注册
MapperScannerConfigurer类型的 BeanDefinition MapperScannerConfigurer实现BeanDefinitionRegistryPostProcessor,调用postProcessBeanDefinitionRegistry- 生成
ClassPathMapperScanner进行扫描 - 扫描到的 BeanDefinition 修改为
MapperFactoryBean,AutowireMode 调整为byType - Spring 基于 BeanDefinition 创建 Bean,每个 Mapper 对应一个 FactoryBean
MapperFactoryBean.getObject()调用getSqlSession()得到 sqlSession,生成 Mapper 接口代理对象
MyBatis 执行 SQL 部分:
- 开启事务,塞入
LinkedHashSet - 利用事务管理器创建数据库连接
- 利用 MyBatis 生成的 Mapper 代理对象执行 SQL
- 代理对象执行方法时,进入
SqlSessionTemplate的SqlSessionInterceptor - 取 sqlSession 对象,若为空则在
DefaultSqlSessionFactory创建 - 判断是否开启事务
- 若开启事务,将 sqlSession 存入
resources(ThreadLocal) - 利用 sqlSession 执行 SQL
7.3 一级缓存失效问题
- MyBatis 一级缓存基于sqlSession实现,同一个 sqlSession 执行 SQL 可利用一级缓存
- Spring 整合后,若方法上没有
@Transactional注解,每执行一个 SQL 都会新生成一个 SqlSession,一级缓存失效 - 开启 Spring 事务时,事务中的多个 SQL 使用同一个 sqlSession,一级缓存生效
- 不是 bug,是机制:没有事务则 SQL 单独执行,生命周期过短
八、SpringMVC
8.1 原生 MVC 弊端
- JSP + Servlet + JavaBean
- XML 配置 Servlet 映射,开发效率低
- 入侵性强
- 分发给不同方法麻烦、参数解析麻烦、数据响应麻烦
8.2 SpringMVC 核心组件
| 组件 | 作用 |
|---|---|
| DispatcherServlet | 前端调度器,负责请求拦截分发到控制器 |
| HandlerMapping | 根据请求 URL 和@RequestMapping映射 |
| HandlerAdapter | 调用 Handler 具体方法,返回视图名 |
| ModelAndView | 封装视图名、request 域数据 |
| ViewResolver | 根据 ModelAndView 找具体 View 对象 |
| View | 视图渲染 |
8.3 父子容器
Spring 和 SpringMVC 整合涉及父子容器概念:
- 先去父容器找,再找子容器
- 父容器 service 无法访问子容器 controller,子容器可以访问父容器
面试题:
Spring 和 SpringMVC 一定需要父子容器吗?
- 不一定,如 Spring Boot
- 原因:单一职责、规范架构、方便切换、节省重复 Bean 创建
是否可以把所有 Bean 都通过 Spring 容器管理?
- 不可以,会导致请求接口 404。SpringMVC 初始化时无法根据 Controller 注册 HandlerMethod,没有查找父容器的 bean
是否可以把所有 Bean 使用 SpringMVC 子容器管理?
- 可以,但不推荐,可能导致 AOP 误配失效
九、手写 Spring 心得(注解与工具)
9.1 @Target 注解
| 类型 | 作用范围 |
|---|---|
ElementType.TYPE | 类、接口、枚举、记录 |
ElementType.FIELD | 字段、枚举常量 |
ElementType.METHOD | 方法(不含构造方法) |
ElementType.PARAMETER | 方法或构造方法参数 |
ElementType.CONSTRUCTOR | 构造方法 |
ElementType.LOCAL_VARIABLE | 局部变量 |
ElementType.ANNOTATION_TYPE | 注解接口 |
ElementType.PACKAGE | 包声明 |
ElementType.MODULE | 模块声明(Java 9+) |
ElementType.RECORD_COMPONENT | record 组件(Java 16+) |
9.2 RetentionPolicy 保留策略
| 策略 | 说明 |
|---|---|
SOURCE | 仅保留在源文件,编译后遗弃 |
CLASS | 保留到 class 文件,JVM 加载时遗弃(默认) |
RUNTIME | 保留到 class 文件,JVM 加载后仍然存在 |
9.3 Scope 作用域
| 作用域 | 说明 |
|---|---|
singleton | 单例模式,容器中只创建一次(默认) |
prototype | 原型模式,每次请求都创建新实例 |
request | 每个 HTTP 请求创建新实例(Web 环境) |
session | 每个 HTTP 会话创建新实例(Web 环境) |
工具方法:
beanName = Introspector.decapitalize(clazz.getSimpleName())—— 首字母转小写
9.4 类型转化
Spring 注入@Value("xxx")到private User user字段时:
- 字符串
"xxx"是配置中的原始值 - Spring 检测到目标字段是 User 类型
- 使用内部注册的ConversionService尝试转换
- 如果存在
Converter<String, User>,调用该转换器完成转换
9.5 元数据读取器
- MetadataReader、ClassMetadata、AnnotationMetadata
- 使用ASM 技术解析类的元数据(类名、方法、注解等),无需 JVM 加载类
本文持续更新中,如有错误或补充,欢迎留言交流!
