吃透 Spring 全家桶核心原理:从 Bean 生命周期到微服务,面试高频知识点全梳理
一、Spring Bean 完整生命周期
我们平时自己创建 Java 对象,只需要简单new一下,使用完毕后交给 JVM 垃圾回收,整个过程非常简单。但在 Spring 框架中,对象不再由开发者手动创建和管理,而是统一交给 Spring IOC 容器托管,这个被容器管理的对象我们称之为Bean。
Bean 从被 Spring 扫描识别,到最终容器关闭被销毁,中间会经历十多个步骤,这一套完整的流程就是Bean 的生命周期,也是 Spring 最基础、面试必考的知识点。
1. 扫描类,识别待管理组件
Spring 项目启动之后,第一件事就是进行包扫描。以 SpringBoot 为例,@SpringBootApplication注解默认会扫描启动类所在包以及所有子包下的类。当框架检测到类上标注了@Service、@Component、@Controller、@Repository等注解时,就会判定这个类需要交给 IOC 容器管理。
这个阶段 Spring 并不会直接创建对象,只是做标记识别,相当于先把所有 “待管理的类” 全部统计出来。
2. 生成 BeanDefinition(Bean 说明书)
识别完所有需要托管的类之后,Spring 会为每一个类生成一个BeanDefinition。大家可以把它理解为Bean 的说明书,它不会存储对象实例,而是专门记录这个 Bean 的所有配置元数据:类的全限定名、Bean 名称、作用域(单例 / 多例)、是否懒加载、依赖的其他 Bean、自定义初始化方法、销毁方法等等。
有了这份说明书,后续 Spring 创建对象、属性注入、执行回调逻辑,都能按照配置有条不紊地执行。
3. 实例化:创建原始对象
有了BeanDefinition之后,Spring 开始执行实例化操作,本质就是通过反射执行类的构造方法,等同于我们手动写new UserService()。
这里有一个关键点:实例化阶段仅仅是把对象的内存空间开辟出来,此时对象内部定义的依赖属性,比如被@Autowired修饰的成员变量,全部都是null,还没有完成赋值。简单说,现在的对象只是一个 “空壳”。
4. 属性注入:完成依赖装配
实例化完成后,进入属性注入阶段。Spring 会解析对象中所有依赖,包括@Autowired、@Resource标注的属性、构造参数等,然后从 IOC 容器中找到对应的 Bean 实例,通过反射为当前对象的属性赋值。
这也是 IOC 控制反转、依赖注入的核心体现:开发者不用手动创建依赖对象并赋值,全部由容器自动完成。执行完这一步,对象的依赖关系就全部补齐了。
5. Aware 接口回调
如果当前 Bean 实现了一系列Aware接口,Spring 会依次执行接口中的方法,把容器内部的资源、信息主动传递给 Bean。
比如实现ApplicationContextAware接口,就能获取到 Spring 容器上下文ApplicationContext;实现BeanNameAware可以获取当前 Bean 在容器中的名称。这个阶段的意义,就是让 Bean 能够感知到自己所处的容器环境。
6. BeanPostProcessor 前置处理
BeanPostProcessor是 Spring 提供的全局后置处理器,可以理解为所有 Bean 在初始化阶段的统一拦截器。在执行自定义初始化方法之前,Spring 会遍历所有实现了BeanPostProcessor接口的类,调用其中的postProcessBeforeInitialization前置方法。
我们可以在这个统一入口中做通用逻辑,比如解析自定义注解、补充对象属性、数据校验等,很多中间件、框架的增强逻辑都是基于它实现的。
7. 执行自定义初始化方法
依赖注入完成后,就可以执行开发者自定义的初始化逻辑了。日常开发中常用的初始化方式有三种:@PostConstruct注解、实现InitializingBean接口、XML 配置init-method。
这个阶段依赖已经全部注入完毕,所以我们可以放心地在初始化方法中调用其他 Bean、初始化连接池、加载缓存数据等业务逻辑。
8. BeanPostProcessor 后置处理
初始化方法执行完成后,会再次回到BeanPostProcessor,调用postProcessAfterInitialization后置方法。Spring AOP 的代理对象,就是在这个阶段生成的。如果当前 Bean 需要被切面增强,Spring 不会把原始对象放入容器,而是在这里生成代理对象并返回。
9. 放入单例池,对外提供服务
经过上面所有流程处理后,一个完整、可用的 Bean 就成型了。如果是单例 Bean(Spring 默认作用域),会被存入一级缓存 singletonObjects中,也就是我们常说的单例池。后续业务代码中通过@Autowired注入获取的,就是这个已经处理完成的 Bean。
在容器运行期间,这个 Bean 会一直常驻内存,处理各类业务请求。
10. 销毁阶段
当 Spring 容器正常关闭时,会触发 Bean 的销毁逻辑。我们可以通过@PreDestroy注解、实现DisposableBean接口、XML 配置destroy-method三种方式定义销毁方法。一般会在这里做资源释放工作,比如关闭数据库连接、停止线程池、清空临时缓存等。
二、Spring 循环依赖问题(面试重中之重)
理解完 Bean 生命周期,就很容易弄懂循环依赖,这也是面试的高频考点。
1. 什么是循环依赖
循环依赖就是两个或多个 Bean 互相依赖对方。举个最简单的例子:AService 中注入了 BService,同时 BService 中又注入了 AService。
@Service public class AService { @Autowired private BService bService; } @Service public class BService { @Autowired private AService aService; }两个对象彼此需要对方,形成了循环引用。很多初学者会疑惑:为什么我们自己手动new对象会直接报错,而 Spring 却能解决这个问题?
2. 核心解决方案:三级缓存
Spring 解决单例 Bean 的属性注入循环依赖,核心依靠三级缓存,三个缓存各司其职:
- 一级缓存 singletonObjects:存放完全初始化完毕、可以直接使用的完整 Bean;
- 二级缓存 earlySingletonObjects:存放已经实例化、但还未完成属性注入和初始化的半成品 Bean;
- 三级缓存 singletonFactories:存放
ObjectFactory对象,也就是 Bean 的工厂对象,用于延迟生成 Bean(尤其是代理对象)。
3. 完整执行流程
- Spring 开始创建 AService,先实例化得到原始对象(半成品),此时属性还未赋值;
- 为了避免循环依赖,Spring 将创建 AService 的
ObjectFactory存入三级缓存,提前暴露半成品对象; - 开始为 AService 做属性注入,发现依赖 BService,于是转而创建 BService;
- 创建 BService 并完成实例化后,属性注入阶段发现依赖 AService;
- Spring 先去一级缓存查找 AService,没有找到;再去二级缓存查找,依旧没有;最后从三级缓存拿到 AService 对应的
ObjectFactory,通过工厂获取半成品 AService; - 将这个半成品 AService 存入二级缓存,同时删除三级缓存中的工厂对象,BService 成功完成属性注入、初始化,存入一级缓存;
- 回到 AService 的属性注入流程,此时 BService 已经就绪,AService 顺利完成所有流程,最终存入一级缓存;
- 最后清空二级缓存中的临时数据,循环依赖问题彻底解决。
4. 为什么构造器注入的循环依赖无法解决?
上面的方案只适用于属性注入的循环依赖,如果是构造器注入,Spring 会直接抛出异常。
原因很简单:构造器注入要求在实例化阶段就必须拿到依赖对象。而实例化是对象创建的第一步,此时还没有半成品对象,更无法提前暴露,三级缓存机制完全无法生效,因此构造器循环依赖 Spring 无法处理。
5. 为什么需要三级缓存,两级不行?
有同学会问,只用一级和二级缓存难道不够吗?答案是不行,核心原因是AOP 代理。
如果 Bean 需要 AOP 增强,最终对外提供的是代理对象,而非原始对象。如果直接把原始半成品 Bean 放入二级缓存,其他 Bean 注入的就是原始对象,和最终容器中的代理对象不一致。
三级缓存中存储的是ObjectFactory工厂,只有当对象被其他 Bean 依赖时,才会调用工厂方法生成对象(如果需要 AOP 则直接生成代理对象),保证所有地方注入的都是同一个对象,这就是三级缓存存在的意义。
三、Spring AOP 与事务原理
AOP(面向切面编程)是 Spring 两大核心之一(另一核心是 IOC),日常开发中的日志、权限校验、事务控制、接口耗时统计,全部都依赖 AOP 实现。
1. AOP 核心本质:动态代理
AOP 的设计目标很明确:在不修改原有业务代码的前提下,对方法进行功能增强。它的底层核心就是 Java 动态代理,结合我们前面讲到的 Bean 生命周期,代理对象正是在BeanPostProcessor后置处理阶段生成的。
当 Spring 检测到当前 Bean 存在切面、@Transactional等需要增强的逻辑时,不会把原始对象放入容器,而是生成代理对象。后续所有方法调用,都会先走代理对象,再由代理对象去调用原始目标方法。
Spring 提供两种动态代理实现方式:
- JDK 动态代理:要求目标类必须实现接口,基于接口生成代理对象;
- CGLIB 动态代理:无需实现接口,通过继承目标类生成子类作为代理对象,SpringBoot 默认使用该方式。
2. AOP 执行流程
- 业务代码调用目标方法,实际调用的是代理对象的方法;
- 代理对象拦截请求,执行前置增强逻辑(比如打印日志、开启事务);
- 通过反射调用原始目标对象的业务方法;
- 业务方法执行完毕后,执行后置增强逻辑(比如提交事务、统计耗时);
- 如果业务方法抛出异常,执行异常增强逻辑(比如事务回滚、异常告警);
- 最终将结果返回给调用方。
3. @Transactional 事务失效常见场景
Spring 声明式事务基于 AOP 实现,所以大部分事务失效问题,本质都是代理没有生效,结合实际开发总结三类高频失效场景:
场景一:类内部方法调用
@Service public class OrderService { public void test() { // 内部调用,没有走代理对象 createOrder(); } @Transactional public void createOrder() { // 业务逻辑 } }test方法内部直接调用本类的createOrder,属于原生对象内部调用,不会经过代理对象,切面逻辑无法执行,事务自然失效。
场景二:方法非 public 修饰
Spring AOP 默认只会对public方法生成代理,如果事务方法定义为private、protected,代理无法生效,事务失效。
场景三:异常被捕获 / 异常类型不匹配
- 如果业务代码手动
try-catch捕获了所有异常,代理无法感知异常,就不会触发回滚; - Spring 事务默认只对 RuntimeException 运行时异常回滚,如果抛出受检异常(Exception),也不会触发回滚。
4. Spring 事务底层原理
Spring 事务并不是凭空创造的,而是对原生 JDBC 事务做了封装,整体架构:AOP + 动态代理 + ThreadLocal + 数据库事务。
- 带有
@Transactional的 Bean,启动时生成代理对象; - 调用方法时进入代理逻辑,从数据源获取数据库连接,关闭自动提交;
- 将数据库连接存入
ThreadLocal,保证同一个线程内所有数据库操作使用同一个连接; - 执行业务 SQL,执行成功则手动提交事务;
- 一旦捕获异常,立刻执行事务回滚操作,释放连接。
简单来说,Spring 只是把我们手写的 JDBC 事务模板代码,通过 AOP 自动织入到方法前后,极大简化了开发。
四、注解底层原理
现在的 Spring 开发几乎离不开注解,@Service、@Autowired、@Transactional、@RequestMapping…… 注解让我们告别了繁琐的 XML 配置。很多人只会用注解,却不清楚它的底层逻辑。
1. 注解的本质
注解本质就是附加在类、方法、字段上的元数据标签,它本身不具备任何执行能力,仅仅是一段描述信息。代码编译后,注解信息会被保留在 Class 文件中。
注解想要生效,必须依靠框架扫描 + 反射解析:框架启动时扫描所有类,通过反射判断类 / 方法上是否存在指定注解,再根据注解执行对应的业务逻辑。
2. @Retention 注解生命周期
注解有一个核心元注解@Retention,用来定义注解的存活范围,也是注解能否在运行时被读取的关键:
- SOURCE:仅存在于源码阶段,编译后丢失,运行时无法读取;
- CLASS:保留到 Class 文件中,JVM 运行时不会加载,反射无法获取;
- RUNTIME:运行时依旧保留,可以通过反射读取,Spring 所有核心注解都是该类型。
3. @Autowired 底层原理
@Autowired自动注入的流程非常清晰:
- Spring 在属性注入阶段,扫描类中所有被
@Autowired修饰的字段; - 根据字段类型 / 名称,去 IOC 容器中匹配对应的 Bean 实例;
- 利用 Java 反射
field.set()方法,手动为字段赋值,完成自动注入。
说白了,@Autowired就是框架帮我们省略了手动获取 Bean、手动赋值的过程。
五、SpringMVC 核心流程与原理
SpringMVC 是 Spring 体系下的 Web 框架,专门用来处理 HTTP 请求,现在所有 SpringBoot Web 项目底层都依赖它。
1. 核心入口:DispatcherServlet
DispatcherServlet是 SpringMVC 的前端控制器,也是整个请求流程的总调度中心。所有浏览器发起的 HTTP 请求,都会先经过 Tomcat 容器,再统一进入DispatcherServlet,由它进行分发处理。
在传统原生 Servlet 开发中,一个接口就要定义一个 Servlet,管理起来极其繁琐。而 SpringMVC 采用前端控制器模式,用一个统一入口接收所有请求,彻底解决了原生 Servlet 的弊端。
2. SpringMVC 完整请求流程
- 浏览器发起 HTTP 请求,Tomcat 监听端口,建立连接并解析 HTTP 协议;
- 请求进入
DispatcherServlet,核心方法doDispatch开始调度; - HandlerMapping:根据请求 URL,匹配到对应的 Controller 和目标方法;
- HandlerAdapter:接管目标方法,开始做参数绑定、类型转换;
- 执行 Controller 中的业务方法,得到返回结果;
- 如果返回视图名称,由ViewResolver视图解析器渲染页面;如果返回 JSON 数据,由HttpMessageConverter做序列化转换;
- 组装响应数据,返回给浏览器。
3. 参数绑定原理
我们在 Controller 方法中直接定义参数,Spring 就能自动把请求数据赋值进去,这就是参数绑定。
底层逻辑:HandlerAdapter会调用对应的参数解析器,不同注解对应不同解析器(@RequestParam、@PathVariable、@RequestBody等)。解析器从HttpServletRequest中提取请求数据,再通过类型转换器把字符串转为 Java 对应类型,最后通过反射调用目标方法并传参。
4. 拦截器与过滤器区别
在 Web 开发中,Filter、Interceptor、AOP 都可以做拦截增强,三者层级完全不同,也是面试常考点:
- Filter(过滤器):属于 Servlet 规范,工作在 Tomcat 和 DispatcherServlet 之间,偏向底层请求过滤;
- Interceptor(拦截器):属于 SpringMVC 组件,工作在 DispatcherServlet 内部,可以获取 Controller 方法信息,适合做登录校验、JWT 鉴权;
- AOP:属于 Spring Bean 层,工作在 Service 方法执行前后,适合做事务、日志、方法增强。
执行顺序:请求 → Filter → Interceptor → Controller → AOP → Service
六、SpringBoot 自动装配原理
SpringBoot 的出现,最大的价值就是简化配置,告别 SSM 时代繁琐的 XML 文件、手动配置组件。而它实现简化的核心,就是自动装配。
1. 核心入口:@SpringBootApplication
启动类上的@SpringBootApplication是一个组合注解,其中最核心的就是@EnableAutoConfiguration,作用就是开启自动装配功能。
2. 自动装配完整流程
- 项目启动,
@EnableAutoConfiguration生效,框架加载AutoConfigurationImportSelector; - 读取指定配置文件:SpringBoot2 读取
META-INF/spring.factories,SpringBoot3 读取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports; - 配置文件中定义了大量自动配置类(XXXAutoConfiguration),比如 WebMvc 自动配置、Jackson 序列化配置、Tomcat 配置等;
- 加载这些配置类,配置类上标注
@Configuration,内部通过@Bean向 IOC 容器注册组件; - 配合条件注解(
@ConditionalOnClass、@ConditionalOnMissingBean)做判断:只有 classpath 下存在对应类、容器中没有手动创建 Bean 时,自动配置才会生效。
3. Starter 依赖的作用
我们引入spring-boot-starter-web这类依赖,本质并不是引入功能代码,而是通过 Maven 传递依赖,把 spring-webmvc、tomcat、jackson 等核心 Jar 包引入项目。
Jar 包加入后,classpath 下就会出现对应类,触发条件注解,对应的自动配置类生效,相关组件自动注册到容器,我们无需任何配置就能直接使用 Web 功能。
七、Spring、SpringMVC、SpringBoot、SpringCloud 关系
很多初学者分不清这四个框架的定位,其实它们是层层递进、依赖兼容的关系:
- Spring:基础核心框架,提供 IOC、AOP、事务等底层能力,是所有框架的基石;
- SpringMVC:Spring 的 Web 模块,专门处理 HTTP 请求,构建 Web 应用;
- SpringBoot:基于 Spring 和 SpringMVC,主打自动装配,简化项目搭建和配置,快速开发单体应用;
- SpringCloud:基于 SpringBoot,一套完整的微服务解决方案,提供服务注册发现、网关、配置中心、熔断限流、远程调用等分布式能力。
简单总结:Spring 是地基,SpringMVC 做 Web,SpringBoot 简化开发,SpringCloud 搭建微服务。
八、微服务架构与 Nacos 核心原理
1. 单体架构的痛点
传统单体架构,所有业务模块(用户、订单、商品、支付)打包成一个 Jar 包,部署在一台服务器。优点是开发、部署简单,但随着业务扩张,问题越来越明显:
- 代码臃肿,模块耦合严重,维护成本高;
- 无法按模块独立扩容,一个模块出现问题,整体服务崩溃;
- 技术栈统一,无法针对不同模块选择适配的技术。
2. 微服务架构介绍
微服务就是按照业务领域拆分系统,把用户、订单、商品拆分成独立的工程,每个服务独立开发、独立部署、独立扩容、独立使用数据库。
拆分之后,原本 JVM 内部的本地方法调用,变成了跨进程、跨机器的网络调用,因此需要配套的分布式组件支撑。
3. OpenFeign 远程调用原理
微服务之间不能直接本地调用,于是出现了 RPC 框架,SpringCloud 中的 OpenFeign 就是典型代表。
它底层基于 HTTP 请求,结合动态代理,把网络请求封装成接口方法调用。开发者只需要定义 Feign 接口,标注服务名和请求路径,调用接口方法就等同于发起远程 HTTP 请求,极大降低了远程调用的开发成本。
4. Nacos 核心功能与原理
Nacos 是微服务架构中常用的组件,两大核心功能:服务注册与发现、配置中心。
(1)服务注册与发现
- 微服务启动后,自动将自身服务名、IP、端口注册到 Nacos 服务端;
- 服务启动后定时发送心跳包(默认 5 秒一次),证明服务健康在线;
- 服务消费者从 Nacos 拉取服务实例列表,通过负载均衡选择实例,发起调用;
- 长时间未收到心跳,Nacos 判定服务下线,自动剔除实例。
(2)临时实例 & 永久实例
- 临时实例:默认类型,依赖心跳,服务宕机后自动从 Nacos 剔除,适用于常规业务服务;
- 永久实例:不依赖心跳,服务宕机也不会自动删除,需要手动下线,适用于数据库、MQ 等中间件服务。
(3)CAP 理论与 Nacos 选型
分布式领域 CAP 理论:一致性 (C)、可用性 (A)、分区容错 (P)。分布式环境下网络分区 (P) 无法避免,因此只能在 C 和 A 之间二选一:
- AP 架构:优先保证可用性,数据短暂不一致也正常返回,Nacos服务注册采用 AP;
- CP 架构:优先保证数据强一致性,牺牲部分可用性,Nacos配置中心采用 CP。
九、总结
整篇内容从 Spring 最基础的 Bean 生命周期出发,延伸到循环依赖、AOP、事务、注解、SpringMVC、SpringBoot 自动装配,最后拓展到微服务架构、OpenFeign、Nacos 等分布式组件,覆盖了 Java 后端面试和日常开发中 80% 以上的高频知识点。
这些技术并不是孤立存在的,而是环环相扣:Bean 生命周期是基础,AOP 基于 Bean 的后置处理器实现,事务基于 AOP,SpringBoot 自动装配基于 Spring 的注解和 Bean 管理,微服务则是在 SpringBoot 之上做的分布式扩展。
日常开发中,我们不能只停留在 “会用注解写代码” 的阶段,理解底层执行流程和设计思想,不仅能快速排查线上问题,在面试中也能脱颖而出。后续大家可以结合源码、断点调试,一步步跟踪执行流程,把理论知识落到实处,真正吃透这套 Java 主流技术栈。
