Spring工作原理图全景解析
Spring工作原理图并非一张静态的架构图,而是一套动态的、可配置的、高度模块化的运行时行为模型。它像一座精密的机械钟表,表面简洁优雅,内里齿轮咬合紧密,每个组件都在恰当时机精准启动——这正是Spring框架历经18年依然屹立不倒的核心原因。
在企业级Java开发中,我们常说“Spring是Java生态的基石”,但真正理解其底层机制的开发者却不足30%。许多开发者习惯于在Controller中写业务逻辑,在Service层调用Repository,却对“为什么@Autowired能自动装配”、“@Transactional为何能自动回滚”等核心问题缺乏系统认知。这种认知断层导致在性能调优、故障排查、架构设计等场景中举步维艰。
本文将从时间维度与空间维度两个角度重构Spring工作原理图:时间上,以应用启动为轴心,依次解析容器初始化、Bean创建、请求处理、事务提交等阶段;空间上,以组件协作关系为骨架,梳理IoC容器、AOP代理、事务管理器、数据访问层等模块的交互逻辑。所有内容均基于Spring 5.3与6.x最新源码实现,结合真实生产环境案例,力求还原一个可理解、可预测、可控制的Spring运行世界。
容器启动流程全景
从Web容器加载DispatcherServlet开始,到IoC容器完成Bean注册、初始化,详解BeanDefinition加载、后置处理器执行、循环依赖处理等关键环节。
Bean生命周期深度解析
从实例化、属性填充、初始化到销毁,完整梳理Spring Bean的13个生命周期阶段,揭示@PostConstruct与InitializingBean的执行优先级差异。
事务管理机制剖析
从声明式事务@Transactional切入,详解事务传播行为(REQUIRED、REQUIRES_NEW等7种)、隔离级别、超时控制及回滚规则的底层实现。
数据流完整路径
追踪一次HTTP请求从浏览器到数据库的完整流转路径,涵盖DispatcherServlet、HandlerMapping、Controller、Service、Repository、JDBC/MyBatis等全链路细节。
IoC容器启动:从零构建Bean的“王国”
当Tomcat(或其他Servlet容器)启动时,Spring的IoC容器初始化流程便悄然开始。这一过程可分为五个关键阶段:Web容器初始化 → Spring上下文创建 → BeanDefinition注册 → Bean实例化 → Bean初始化。每一阶段都暗藏玄机,稍有不慎便可能导致启动失败或性能瓶颈。
第一阶段:Spring上下文创建
在Web应用中,Spring上下文通常由ContextLoaderListener监听器创建,其核心入口为WebApplicationContext。该监听器在web.xml或Spring Boot的自动配置中注册,负责在Servlet容器启动时初始化Spring容器。
若使用Spring Boot,则由SpringApplication自动完成上下文创建,支持AnnotationConfigServletWebServerApplicationContext(Servlet环境)或AnnotationConfigReactiveWebServerApplicationContext(响应式环境)。
第二阶段:BeanDefinition注册
Spring通过BeanDefinitionReader将配置元数据(XML、注解、Java Config)转换为BeanDefinition对象,并注册到DefaultListableBeanFactory的beanDefinitionMap中。此过程涉及:
- 扫描类路径:通过
ClassPathBeanDefinitionScanner扫描@Component、@Service等注解的类 - 解析注解元数据:如
@Scope(作用域)、@Lazy(延迟初始化)、@Primary(优先注入) - 注册后置处理器:如
BeanFactoryPostProcessor、BeanPostProcessor
@SpringBootApplication注解隐式包含@ComponentScan,其默认扫描路径为启动类所在包及其子包。若需扫描其他包,必须显式指定basePackages属性。
第三阶段:Bean实例化与初始化
容器启动的最后阶段是懒加载或预实例化Bean。默认情况下,非@Lazy标注的Singleton Bean在容器启动时即被实例化(预实例化)。实例化过程分为三步:
- 实例化:调用构造器或工厂方法创建对象实例
- 属性填充:通过反射设置属性值,处理
@Autowired依赖注入 - 初始化:执行
InitializingBean.afterPropertiesSet()、自定义init-method、@PostConstruct方法
整个启动过程可在日志中清晰追踪。典型日志片段如下:
BeanDefinition的结构与来源
BeanDefinition是Spring对Bean的“蓝图”,包含类名、作用域、构造参数、属性值、依赖关系、初始化/销毁方法等元数据。其主要来源有三:
- XML配置:通过
XmlBeanDefinitionReader解析<bean>标签 - 注解扫描:通过
ClassPathBeanDefinitionScanner识别@Component等注解 - Java Config:通过
ConfigurationClassPostProcessor解析@Configuration类
以下为BeanDefinition的核心属性示例:
| 属性名 | 说明 | 默认值 |
|---|---|---|
| beanClass / beanClassName | Bean的类定义或类名 | 无 |
| scope | 作用域(singleton/prototype等) | singleton |
| lazyInit | 是否延迟初始化 | false |
| dependsOn | 依赖的其他Bean | 空数组 |
| autowireCandidate | 是否参与自动装配 | true |
| initMethod / destroyMethod | 自定义初始化/销毁方法 | 空 |
后置处理器的关键作用
Spring容器启动过程中,BeanFactoryPostProcessor与BeanPostProcessor是两个核心扩展点:
- BeanFactoryPostProcessor:在BeanDefinition加载完成后、Bean实例化前执行,典型代表为
PropertySourcesPlaceholderConfigurer(处理${...}占位符) - BeanPostProcessor:在Bean实例化后、初始化前后执行,典型代表为
AutowiredAnnotationBeanPostProcessor(处理@Autowired)
自定义后置处理器示例:
循环依赖的三大解决方案
循环依赖指Bean A依赖Bean B,Bean B又依赖Bean A。Spring通过三级缓存机制解决单例Bean的循环依赖问题:
- 一级缓存(singletonObjects):存放完全初始化好的单例Bean
- 二级缓存(earlySingletonObjects):存放早期暴露的Bean(尚未完成属性填充与初始化)
- 三级缓存(singletonFactories):存放ObjectFactory,用于生成早期Bean引用
工作流程如下:
- Bean A创建时,先放入三级缓存
- 填充属性时发现依赖Bean B,开始创建B
- Bean B创建时,从三级缓存获取A的ObjectFactory,生成早期引用并放入二级缓存
- A继续创建,最终放入一级缓存
- B完成创建,放入一级缓存
ObjectCurrentlyInCreationException。
以下场景会导致循环依赖报错:
- 构造器注入的循环依赖(如
@Autowired public UserService(UserRepository repo)) - 原型Bean的循环依赖
- 使用
@Lazy但未正确配置
Bean生命周期:13个阶段的精密编排
个Spring Bean从诞生到消亡,需经历13个明确阶段。理解这些阶段,是排查初始化失败、属性注入异常、资源泄露等问题的关键。
允许自定义Bean实例化逻辑(如CGLIB代理)。若返回非null对象,跳过默认实例化。
通过反射调用构造器或静态工厂方法生成Bean实例。若存在多个构造器,按@Autowired或@Primary选择。
判断是否跳过属性填充(如@RequiredProperties)。若返回false,跳过后续属性设置。
通过BeanWrapper设置属性值,处理@Autowired、@Value等注解注入。
如CommonAnnotationBeanPostProcessor处理@PostConstruct注解。
实现InitializingBean接口的afterPropertiesSet方法。
XML中<bean init-method="init">或@Bean(initMethod="init")指定的方法。
如AnnotationAwareAspectJAutoProxyCreator创建AOP代理。
Bean已完全初始化,可被其他Bean或应用代码使用。
如@PreDestroy注解的处理。
实现DisposableBean接口的destroy方法。
XML中<bean destroy-method="cleanup">或@Bean(destroyMethod="cleanup")指定的方法。
Bean从容器中移除,等待GC回收。
关键阶段详解
@PostConstruct vs InitializingBean
两者均用于初始化逻辑,但存在以下差异:
| 对比项 | @PostConstruct | InitializingBean.afterPropertiesSet |
|---|---|---|
| 来源 | JSR-250标准注解 | Spring特有接口 |
| 执行时机 | 属性填充后、init-method前 | 在@PostConstruct之后 |
| 依赖注入 | 支持 | 不支持(因在构造器后立即执行) |
| 推荐度 | ✅ 推荐(解耦、标准) | ⚠️ 慎用(强依赖Spring) |
常见初始化异常排查
若启动日志中出现BeanCreationException,请按以下步骤排查:
- 检查构造器注入:是否存在循环依赖或必填参数缺失
- 检查@Required属性:是否遗漏配置或空值
- 检查@PostConstruct方法:是否抛出异常或阻塞(如死循环)
- 检查init-method:方法是否存在、是否抛出异常
依赖注入机制:@Autowired的“魔法”背后
当你写下@Autowired private UserService userService;时,Spring背后执行了约200行代码逻辑。依赖注入(DI)是IoC的核心实现方式,其本质是控制反转——将对象创建的控制权从代码移交给容器。
种注入方式的适用场景
| 注入方式 | 代码示例 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|---|
| 构造器注入 | public UserService(UserRepository repo) { this.repo = repo; } |
✅ 必填依赖清晰 ✅ 不可变性 ✅ 循环依赖可检测 |
❌ 复杂依赖时构造器过长 | ⭐⭐⭐⭐⭐ |
| Setter注入 | @Autowired public void setUserService(UserService service) { this.service = service; } |
✅ 可选依赖灵活 ✅ 支持重置依赖 |
❌ 可能出现空指针 ❌ 循环依赖不报错 |
⭐⭐⭐ |
| 字段注入 | @Autowired private UserService service; |
✅ 代码简洁 | ❌ 无法用于final字段 ❌ 难以单元测试 ❌ 循环依赖不报错 |
⚠️ 不推荐 |
依赖解析的四大步骤
当容器遇到@Autowired注解时,按以下顺序解析依赖:
- 按类型匹配(byType):查找与目标类型兼容的BeanDefinition
- 按名称匹配(byName):若多个候选Bean,尝试按属性名匹配Bean名
- 按@Qualifier指定:若显式指定
@Qualifier("userServiceImpl"),直接定位 - 报错或抛出NoUniqueBeanDefinitionException:若仍无法唯一确定
典型报错示例:
@Primary与@Primary的深层机制
@Primary注解标记“首选Bean”,在类型匹配阶段优先考虑。其本质是修改BeanDefinition的primary属性为true。Spring在AutowiredAnnotationBeanPostProcessor中通过AutowiredAnnotationBeanPostProcessor#determineCandidateHints实现优先级判断。
@Primary与@Qualifier实战对比
构造器注入的@Primary生效规则
当构造器参数类型存在多个候选Bean时,@Primary优先级高于@Qualifier。但若构造器参数名与Bean名一致,Spring会优先按名称匹配(即使未标注@Qualifier)。
事务管理机制:@Transactional的“暗箱操作”
@Transactional是Spring最常用的注解之一,但其背后涉及AOP代理、事务传播行为、隔离级别、回滚规则等复杂逻辑。许多开发者误以为“只要加了@Transactional就万事大吉”,却在生产环境中遭遇数据不一致、死锁等问题。
种传播行为详解
事务传播行为定义了方法被调用时如何参与事务。以下是Spring定义的7种传播行为:
| 传播行为 | 说明 | 适用场景 |
|---|---|---|
REQUIRED |
有事务则加入,无则新建(默认) | 绝大多数业务场景 |
REQUIRES_NEW |
挂起当前事务,新建独立事务 | 日志记录、异步通知(需独立提交) |
SUPPORTS |
有事务则加入,无则非事务执行 | 只读查询操作 |
NOT_SUPPORTED |
挂起当前事务,非事务执行 | 与事务无关的耗时操作(如发送邮件) |
NEVER |
必须无事务执行,否则报错 | 明确禁止事务的操作 |
NESTED |
若当前有事务,则在嵌套事务中执行 | 部分回滚(需JDBC 3+支持) |
MANDATORY |
必须在已有事务中执行,否则报错 | 强制业务逻辑必须在事务中 |
典型陷阱:在REQUIRED传播行为下,若方法A调用同类中的方法B(B标注@Transactional),由于Spring AOP代理机制,B()的事务注解不会生效!需通过AopContext.currentProxy()或拆分到新类解决。
种隔离级别与并发问题
隔离级别定义了事务间的可见性,需权衡一致性与性能:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 性能 |
|---|---|---|---|---|
READ_UNCOMMITTED |
❌ 允许 | ❌ 允许 | ❌ 允许 | ⭐⭐⭐⭐⭐ |
READ_COMMITTED |
✅ 防止 | ❌ 允许 | ❌ 允许 | ⭐⭐⭐⭐ |
REPEATABLE_READ |
✅ 防止 | ✅ 防止 | ❌ 允许 | ⭐⭐⭐ |
SERIALIZABLE |
✅ 防止 | ✅ 防止 | ✅ 防止 | ⭐ |
MySQL默认隔离级别:REPEATABLE_READ(InnoDB引擎)
PostgreSQL默认隔离级别:READ_COMMITTED
@Transactional失效的7大场景
- 非public方法:Spring AOP仅拦截public方法
- 同类方法调用:绕过代理对象,注解失效
- 异常被catch:未抛出异常时,事务不会回滚
- 异常类型错误:默认仅回滚RuntimeException,checked异常需显式配置
- 事务被propagation=NEVER:强制禁止事务的传播行为
- 数据库引擎不支持:如MySQL MyISAM引擎不支持事务
- 超时或死锁:事务自动回滚,但需检查日志确认
正确配置回滚规则
AOP切面编程:解耦横切逻辑的“瑞士军刀”
面向切面编程(AOP)是Spring的另一大支柱,用于将横切关注点(如日志、权限、缓存)与核心业务逻辑解耦。其核心是代理模式:通过动态代理(JDK/CGLIB)在目标方法执行前后织入增强逻辑。
JDK动态代理 vs CGLIB代理
| 特性 | JDK动态代理 | CGLIB代理 |
|---|---|---|
| 实现原理 | 基于接口的反射 | 生成子类字节码 |
| 代理对象 | 目标类的接口实现 | 目标类的子类 |
| 限制 | 必须有接口 | 不能代理final类/方法 |
| 性能 | 调用快,生成慢 | 调用稍慢,生成快 |
| Spring默认 | 有接口时优先 | 无接口时使用 |
如何强制使用CGLIB?
spring.aop.proxy-target-class=true,即优先使用CGLIB代理。这简化了配置,但需注意CGLIB对final类的限制。
切点表达式(Pointcut Expression)
切点定义了在哪里织入增强逻辑,常用表达式如下:
| 表达式 | 说明 | 示例 |
|---|---|---|
execution() |
方法签名匹配 | execution( com.example.service..(..)) |
within() |
类级别匹配 | within(com.example.service.) |
this() |
代理对象类型匹配 | this(com.example.service.UserService) |
target()
| 目标对象类型匹配 | target(com.example.service.UserService) |
@annotation() |
注解匹配 | @annotation(org.springframework.transaction.annotation.Transactional) |
组合表达式示例:
种通知类型
| 通知类型 | 注解 | 执行时机 | 是否可修改返回值 |
|---|---|---|---|
| 前置通知 | @Before |
目标方法执行前 | ❌ 否 |
| 后置通知 | @After |
目标方法执行后(无论是否异常) | ❌ 否 |
| 返回通知 | @AfterReturning |
目标方法成功返回后 | ✅ 是(可通过returning绑定) |
| 异常通知 | @AfterThrowing |
目标方法抛出异常后 | ❌ 否 |
| 环绕通知 | @Around |
环绕目标方法(可控制是否执行) | ✅ 是(可修改参数、返回值) |
环绕通知的典型应用
数据流走向:一次HTTP请求的完整旅程
当用户在浏览器输入URL并回车,到页面显示结果,背后经历了约12个关键步骤。以下以Spring MVC为例,追踪数据流的完整路径:
请求包含URL、Method、Headers、Body等信息,通过TCP/IP协议发送至服务器。
Servlet容器解析HTTP请求,创建HttpServletRequest与HttpServletResponse对象。
执行web.xml或@WebFilter注册的过滤器(如字符编码、跨域处理)。
Spring MVC入口,根据URL匹配HandlerMapping。
返回HandlerExecutionChain,包含Controller方法及拦截器。
将请求参数绑定到Controller方法参数(如@RequestBody、@PathVariable)。
调用Service层,处理核心业务逻辑。
可能包含事务控制、缓存逻辑、异步处理等。
通过JdbcTemplate、JPA、MyBatis等访问数据库。
执行SELECT/INSERT/UPDATE/DELETE,返回结果集。
将Java对象转换为JSON/XML(通过Jackson/Jackson2ObjectMapper)。
通过HttpServletResponse.write()返回数据,经Filter链返回浏览器。
关键组件交互示意图
参数绑定与类型转换
Spring通过HttpMessageConverter将HTTP请求体转换为Java对象,支持JSON、XML、表单数据等格式。典型流程如下:
- 请求体JSON →
MappingJackson2HttpMessageConverter→@RequestBody User user - URL路径参数 →
@PathVariable Long id - 查询参数 →
@RequestParam String name - 表单数据 →
@ModelAttribute
结语:理解Spring,从“知其然”到“知其所以然”
本文从容器启动、Bean生命周期、依赖注入、事务管理、AOP切面、数据流走向六大维度,完整还原了Spring工作原理图的动态全貌。我们看到,Spring并非魔法,而是一套高度工程化的系统设计——它的每一个注解、每一行日志背后,都蕴含着清晰的逻辑链条。
真正掌握Spring,不是死记硬背源码细节,而是理解其设计哲学:约定优于配置、面向接口编程、关注点分离、开闭原则。当您下次看到InitializingBean或DisposableBean接口时,不再困惑于其存在意义,而是能立刻联想到Bean的完整生命周期;当您配置@Transactional(propagation = REQUIRES_NEW)时,能准确预判事务的隔离与传播行为。
最后,请记住:Spring的源码是开放的,它的设计思想是可迁移的。无论您使用Spring、Quarkus还是Micronaut,理解这些底层原理,都将让您在技术演进中保持定力与远见。
- 《Spring源码深度解析》(郝佳)
- Spring Framework官方文档(https://spring.io/projects/spring-framework)
- 《Spring Boot实战》(Craig Walls)
- 《深入理解Java虚拟机》(周志明)——理解类加载与代理机制