如果你也是个Java后端新手,大概率被问过或者问过自己一个问题:Spring到底是个什么东西?听到的回答往往是“轻量级容器框架”“IOC、AOP那一套”,但真让你把Spring的架构全貌讲清楚,又说不利索。我当年学Spring也踩过这个坑,后来是靠着反复画架构图、逐个模块过源码,才把这张地图装进脑子里。这篇文章就把这张图重新画给你看,同时附一条适合自学的系统化路线,希望能帮你少走一点弯路。
这篇文章适合两类人:刚入门Java、准备啃Spring的初学者,以及用过Spring但一直没把整体架构打通关的“半熟手”。我会按“整体架构一眼看懂”的思路,把Spring的核心模块拆开,再深入讲IOC容器、Bean生命周期、三级缓存、AOP与事务这几个关键机制,最后给出一条可执行的学习路线和避坑建议。
1. 一张图先入局:Spring整体架构的第一印象
很多人看Spring架构图,第一反应是“模块太多,记不住”。其实不用记,你要做的是把这张图按“层次”和“职责”拆开看。Spring整体架构可以分四层:核心层、数据层、Web层、外围生态。核心层就是IOC容器和AOP,这是Spring的命根子;数据层负责跟数据库打交道,包括事务管理、JDBC封装、ORM集成;Web层就是Spring MVC那套请求处理链路;外围生态则是Spring Boot、Spring Cloud这些基于核心层扩展出来的东西。
我习惯把这四层想象成一栋楼:核心容器是地基,AOP是管道系统,数据层是水电入户,Web层是前台接待,外头延伸出去的花园车库就是Spring Boot和Spring Cloud。你只有把地基和管道摸透了,后面用Boot、Cloud才不至于老出莫名其妙的问题。
再看另一个维度,那就是“运行期”视角。Spring应用启动后,第一件事是创建容器,接着扫描配置、实例化Bean、处理依赖,最后暴露出来给你用。整个过程走一遍,你看到的就是IOC容器的工作现场。把这个过程中的关键节点画出来,比如BeanDefinition加载、实例化、属性填充、初始化、代理生成、注入使用,基本就是一张动态的Spring架构图。静态模块图加上动态运行图,两张图叠在一起,Spring的整体印象就出来了。
1.1 这张图应该怎么画
我建议你用一张图把Spring的主要组件放进去,从上到下是:
Spring生态(Boot / Cloud / Data / Security / AI 等) ---------------------------------------------- Web层 Spring MVC(DispatcherServlet、HandlerMapping、Controller) ---------------------------------------------- 数据层 JDBC / ORM 集成、事务管理(@Transactional、PlatformTransactionManager) ---------------------------------------------- 核心 IOC容器(BeanFactory、ApplicationContext) AOP(切点、通知、动态代理) ---------------------------------------------- 基础 Spring Core(资源管理、类型转换、SpEL表达式)这张图不复杂,但它把所有学习内容都定位好了。你学到任何一个知识点的第一反应应该是:它属于这张图的哪一层?能解决哪一层的问题?比如学Spring Boot的自动配置,你会发现在图里它处于最上层,做的是“简化下层配置”这件事,而不是替代IOC和AOP。
1.2 先分清三个最容易混的名字
不少初学者把Spring、Spring Boot、Spring Cloud当成同一个东西,其实它们是递进关系。Spring(通常指Spring Framework)是地基,提供IOC、AOP这些核心能力;Spring Boot是基于Spring的快速开发框架,用自动配置和起步依赖,把搭建应用的体力活给干了;Spring Cloud则是微服务治理方案,解决的是分布式环境下的服务发现、配置管理、熔断限流等问题,它本身也依赖Spring Boot。三者的关系可以类比成:Spring是发动机,Spring Boot是整车,Spring Cloud是车队管理调度系统。分清这一点,学习路线的顺序也就自然出来了。
2. 核心容器层:IOC容器到底做了哪些事
IOC(Inversion of Control,控制反转)是Spring最核心的思想,但很多初学者对它的理解停留在“把对象交给Spring管理”这句话上。这个说法没毛病,但它没回答关键问题:为什么要交给Spring管?交付之后,Spring又做了哪些事?
其实IOC解决的是“依赖管理”的痛点。以前你自己new对象,对象A依赖对象B,B依赖C,你手动创建的时候,顺序错了、重复了、生命周期不一致,很容易出问题。Spring把创建和组装对象的活接过去,你只要声明“我这个类依赖什么东西”,容器负责把依赖准备好再给你。这样做的直接收益是代码解耦,替换实现类不用改业务代码,改配置就行。
但更深一层的价值,是对象生命周期统一管理。容器创建对象、填充属性、执行初始化方法、注入到其他对象、最后销毁,每个阶段都是可控的。这也是Spring能在这之上实现AOP、事务、缓存这些增强能力的前提。没有容器统一管理,你不可能在对象初始化前后无缝插入逻辑。
2.1 IOC容器两种形态:BeanFactory与ApplicationContext
BeanFactory是Spring容器的最底层接口,定义了getBean、containsBean这些最基础的操作。ApplicationContext是它的增强版,增加了很多企业级功能:国际化、事件发布、资源加载、自动注册后置处理器等。实际开发中,我们基本不会直接用BeanFactory,而是用ApplicationContext。
有一个点值得注意,ApplicationContext在启动时会默认预实例化所有单例Bean(除非配置了懒加载),而BeanFactory是等你getBean时才创建。这一差异带来的直接影响就是:启动慢一点,但能提前发现配置错误。所以生产环境里,Spring Boot应用启动报错,很多都是Bean创建失败,就是容器启动时预加载导致的,这其实是好事,早炸比晚炸强。
2.2 Bean的生命周期:从定义到销毁的完整链路
Bean生命周期是Spring面试的高频题,也是理解“容器管理对象”的最好切口。一个Bean从被加载到被销毁,大致经历这些阶段:
- 解析配置,生成BeanDefinition。BeanDefinition里保存了类的全限定名、作用域、是否懒加载、初始化方法名等元信息。
- 实例化Bean,也就是通过构造器创建对象,此时对象还是个“半成品”,属性都是默认值。
- 属性填充(依赖注入)。Spring扫描Bean的字段、setter方法,把依赖的Bean注入进来。
- Aware回调。如果Bean实现了BeanNameAware、BeanFactoryAware、ApplicationContextAware,Spring会在这里回调,把BeanName、容器实例传给你。
- BeanPostProcessor前置处理。在初始化方法执行前,你可以通过postProcessBeforeInitialization对Bean做自定义处理。
- 执行初始化方法。包括@PostConstruct注解方法、InitializingBean接口的afterPropertiesSet方法、自定义init-method,三者有固定执行顺序。
- BeanPostProcessor后置处理。postProcessAfterInitialization在这个阶段执行,Spring AOP的代理对象就是在这里生成的。
- Bean就绪,可以被使用了。
- 容器关闭时销毁,执行@PreDestroy、DisposableBean、自定义destroy-method。
我建议你自己写一个简单的Bean,把这些阶段全部塞上打印日志,跑一遍看输出顺序。这个过程胜过背十遍面试题。
2.3 三级缓存:Spring是怎么解决循环依赖的
循环依赖是Bean工厂里最容易炸的场景,比如A依赖B,B又依赖A。如果容器只做“创建A、创建B、然后互相注入”,必然陷入死循环:创建A时缺B,创建B时缺A。Spring的解法是用三级缓存提前暴露实例。
三级缓存分别是:一级缓存singletonObjects,存放完整的单例Bean;二级缓存earlySingletonObjects,存放提前暴露的、还没完成初始化的Bean;三级缓存singletonFactories,存放ObjectFactory类型的工厂,可以生成早期Bean的引用。
整个流程大概是这样的:创建A时发现依赖B,于是先去创建B;创建B时发现依赖A,此时去三级缓存里拿到A的ObjectFactory,通过getEarlyBeanReference拿到A的早期引用,注入给B;B创建完成,被存进一级缓存;A继续执行后续初始化和属性填充,最终也进入一级缓存。
为什么不直接用二级缓存就够了?关键在于AOP代理。三级缓存里存的是ObjectFactory,它的getEarlyBeanReference方法可以决定是否提前生成代理对象。如果A被切面增强,循环依赖发生时就要提前暴露代理,否则B拿到的是原始对象,AOP就会失效。用三级缓存,意味着“是否提前代理”是延迟决策的。所以这个设计不是无谓的复杂,它是为了同时满足两个需求:让循环依赖能正常工作,同时让AOP代理正常生效。这块建议结合源码看Spring源码解析时重点看getSingleton、getEarlyBeanReference这两个方法,理解会深很多。
3. 横切与事务:AOP把架构里的公共逻辑抽出来了
IOC解决了对象创建管理的问题,但业务系统里还有一类逻辑是横跨所有业务方法的,比如日志记录、权限校验、性能监控、事务控制。这类逻辑如果散落在每个方法里,会严重破坏代码的可维护性。AOP(面向切面编程)就是Spring给出的解法,把这类公共逻辑从业务代码里抽出来,做个“横切面”统一处理。
AOP的核心概念其实就三个:切点(Pointcut)、通知(Advice)、切面(Aspect)。切点定义“在哪些方法上生效”,通知定义“在什么时候做什么”,切面是把两者打包在一起的模块。用生活中的例子说,小区物业就是一个大切面,各家各户什么时间倒垃圾、哪里需要维修,都由物业统一处理,业主不用自己操心。
Spring AOP底层是动态代理,分两种:JDK动态代理和CGLIB代理。JDK动态代理要求目标类实现接口,它是通过实现同一组接口的代理对象来增强方法的;CGLIB是通过生成目标类的子类来代理,不要求有接口。Spring Boot 2.x之后,默认使用CGLIB代理,因为很多业务类没有专门抽接口,用CGLIB更通用。
3.1 AOP的落地:切面表达式与通知类型
实际开发中用注解式AOP比较多,核心就是@Aspect、@Pointcut、@Before、@AfterReturning、@AfterThrowing、@Around这几个注解。切点表达式常用execution和@annotation两种。execution适合表达“哪些包下、哪些方法的签名”,@annotation适合表达“方法上打了哪个注解就生效”,后者更灵活,比如你自定义一个@OperationLog注解,标记上就会被切面拦截。
我在项目中做得最多的AOP场景是操作日志记录。做法是:自定义注解,切面里通过环绕通知把方法入参、返回值、异常信息、执行时长打包成日志记录入库。这样做的好处是业务代码完全不需要写日志逻辑,新接一个模块时只要给关键方法打上注解即可。
一个容易踩的坑是:AOP自调用失效。同一个类里,一个方法调用另一个方法时,内部调用走的是this调用,不经过代理对象,所以增强不会生效。解决办法是注入自身代理,或者把被调方法拆分到另一个Bean里。
3.2 @Transactional事务失效的那些坑
Spring的声明式事务也是基于AOP实现的。给方法加上@Transactional,事务管理器会在方法执行前开启事务、成功后提交、异常时回滚。这个机制用起来很爽,但坑也特别多,我挨个说:
第一,方法非public时事务不生效。Spring AOP基于代理,非public方法不会被代理拦截,但要注意private方法连报错都不会有,很难排查。第二,同类内部调用导致事务失效,这个和上面说到的AOP自调用是一样的道理,解决办法是把事务操作放到另一个Bean中调用。第三,异常被吞掉时不会回滚。如果代码里catch了异常但没有抛出,事务管理器感知不到失败,自然就提交了。第四,默认只回滚RuntimeException和Error,如果业务抛的是受检异常,需要显式指定rollbackFor。
还有一类是数据库层面的坑,比如MySQL的MyISAM引擎不支持事务,Spring这边配置得再好也没用。所以选型时要注意存储引擎,现在主流的InnoDB是支持事务的,但老项目里偶尔能碰到MyISAM的表。
4. 从Spring到Boot再到Cloud:架构的外延扩展
如果你只学Spring Framework,你会发现搭建一个能跑的项目还挺费劲:要配XML、要配数据源、要打包一堆依赖。Spring Boot把这些体力活全优化掉了,它的核心逻辑就是约定优于配置加自动配置。起步依赖帮你把常用依赖打成一个包,自动配置通过条件注解判断当前类路径有没有某个类,来决定要不要激活对应的配置。举个例子,你引入spring-boot-starter-web后,只要类路径里有DispatcherServlet,Spring Boot就会自动完成Spring MVC的基础配置,你直接写@Controller就行,不用再手动配一堆bean。
Spring Boot的自动配置原理其实不神秘:@EnableAutoConfiguration会通过AutoConfigurationImportSelector去加载META-INF/spring.factories文件里的自动配置类,然后按条件的匹配结果逐个启用。你自己第一次看源码可能会懵,但理清这个链路后,你会对“为什么加一个依赖就能跑”这件事产生通透的感觉。
4.1 微服务架构下,Spring Cloud Alibaba在扮演什么角色
单机时代,一个应用把所有功能都做了,那叫单体架构。团队大了、业务复杂了,单体应用发布一次要影响所有模块,拆分微服务就成了必然。微服务架构下,一个系统被拆分成了几十个甚至上百个独立服务,这时新的问题出现了:服务之间怎么互相发现?配置怎么统一管理?流量大了怎么限流熔断?分布式事务怎么处理?这些就是Spring Cloud要解决的治理问题。
在中文技术社区中,Spring Cloud Alibaba是使用很广泛的微服务解决方案。它对应到各个组件:Nacos负责服务注册发现和配置中心,Sentinel负责流量控制和熔断降级,OpenFeign做服务间的声明式HTTP调用,Gateway做API网关,Seata负责分布式事务。学这些东西的时候,不要一个组件一个组件瞎学,而是要有“服务治理全景”的意识,把每个组件想成是微服务生命周期里某一类问题的解决方案。
4.2 架构演进中的“不变与变”
你从单体切到微服务,就会发现有些东西变了:部署单元变了、通信方式变了、数据一致性方案变了。但有些东西其实没变:代码的组织方式还是分层,对象还是要交给容器管理,业务方法还是能用AOP做增强。这也是为什么说Spring的IOC和AOP是地基,它们不管上面怎么变,都是底层的那套机制。
我见过不少同学直接学Spring Cloud Alibaba,跳过了Spring Framework的基础,最后出现问题就不知道怎么排查。这不是说不能用,而是说基础不牢时排查问题的能力会很弱。正确的策略是先吃透Spring Framework的核心,再学Boot的自动配置,最后进入Cloud生态,这样一个台阶一个台阶上去,遇到问题才能准确定位到对应层。
5. 初学者系统化学习路线:按这个顺序走,效率翻倍
很多初学者最大的问题不是不努力,而是努力的方向太散或者太超前。今天看到别人学源码,自己也去啃源码;明天看到微服务很火,又开始看Spring Cloud。这种东一榔头西一棒子的学法,学习效率很低。我给一条自己验证过的系统化路线,分五个阶段,按顺序推进即可。
第一阶段是Java基础补强。核心要覆盖集合、并发、JVM、IO这几个方向,不用达到专家级,但至少要知道HashMap、线程池、类加载机制这些概念。因为Spring的源码大量用到这些基础,基础不牢,读源码就会卡壳。
第二阶段是Spring Framework核心。优先学IOC和AOP,包括Bean生命周期、循环依赖、动态代理。这个阶段不用追求把所有源码读完,但要能回答“Bean是怎么创建的”“AOP是怎么实现的”。
第三阶段是Spring MVC和数据库集成。把HTTP请求怎么从DispatcherServlet走到Controller的这一路走通,同时学Spring JDBC、MyBatis Plus、Spring Data JPA中的至少一种,把事务管理放到这个阶段一起学。
第四阶段是Spring Boot。学自动配置原理、起步依赖的机制、配置文件的读取规则,然后把项目脚手架自己手动搭一遍,不依赖在线生成器。
第五阶段是Spring Cloud Alibaba微服务。按“服务注册发现、配置中心、网关、熔断限流、分布式事务”的次序推进,每个组件先跑通demo,再考虑深入原理。有精力的话可以了解Spring AI相关生态,这个方向目前也很热,但它是增量技能,不应该影响主线的学习先后次序。
5.1 每个阶段需要掌握的“能力标准”
我推荐每个阶段结束时做一个自测:能不能不用教程,独立写一个能跑的项目?第二阶段能独立写一个基于注解的IOC小demo,第三阶段能写一个带权限拦截和事务的Web应用,第四阶段能独立从零搭建一个Spring Boot项目并解释每个自动配置类的意义。只有达到这个标准才算通过,否则就不要急着进入下一阶段。
这里插一句个人经验:学Spring Boot,最好的资料其实是官方文档加上源码。很多人一开始求快,去看各种短视频,结果学了三个月连自动配置的原理都说不清。我建议遇到任何一个注解,都去官网查它的Javadoc,虽然刚开始看得慢,但长期来看建立的是准确的体系。
5.2 踩过坑之后,给你的避坑清单
第一,不要一上来就啃Spring源码。源码阅读对基础要求高,初学阶段正确动作是先把框架用熟,带着问题再去读,比如遇到循环依赖了,去读三级缓存的处理逻辑,这样效率比漫无目的读源码高太多。
第二,出现问题先查官方文档,再考虑搜索引擎。中文技术社区里的答案质量参差不齐,很多是老版本内容,直接套用反而会踩新坑,版本问题是一切坑的根源。
第三,多问“为什么”而不仅仅是“怎么用”。比如Spring Boot自动配置为什么用条件注解?MyBatis为什么能自动扫描Mapper?这些问题想清楚一次,你对框架的理解就会上一个台阶。
第四,学会看报错堆栈。新手最常见的毛病是一看到报错就慌,直接把最后一行异常贴出去。正确做法是把完整堆栈逐行读一遍,找到自己业务代码对应的那一行,往往问题就集中在那一行。排查问题的能力是区分资深开发和新手的重要标准。
5.3 学习这件事,最后拼的是构建体系
按这条路线走下来,你对Spring应该不再有一个个孤立的知识点,而是有了一张自己的架构地图。以后再接触到新的Spring组件,你会下意识地把它放进地图里对应的层次,很快就能判断出它解决的是什么问题、和哪些东西相关联。构建起这个体系之后,不管生态怎么演变,Spring AI也好、新的组件也好,学起来都会快很多。
最后分享一个我一直在用的习惯:每学完一块内容,不急着往下冲,而是画一张自己的架构图或者写一篇极简笔记,讲清楚这个模块在整个Spring体系里的位置,以及它和其他模块的依赖关系。刚开始会慢,但坚持几个月后,你会发现自己对Spring的整体把握远超同龄人。技术这东西,入门靠代码量,进阶靠体系感,希望这篇文章能成为你构建体系感的起点。