Spring Boot总结
一、 说说Spring启动过程
可将 Spring 整个启动过程 = 筹备一场演唱会
1、 加载配置文件,初始化容器
这段讲的是 Spring 核心容器的初始化过程,这是一个“容器内视角”:讲的是 ApplicationContext 内部做了哪些活儿。
Spring 启动时首先会读取配置文件(如 XML 配置文件、Java Config 类等),包括配置数据库连接、事务管理、AOP 配置等。
类比演唱会:👉 相当于咱打开策划案,看看都要搞什么:歌手名单、舞台设计、灯光、音响、安保、应急预案全写在策划案(XML 配置 / 注解类)里。
🎶 Spring:我知道我这个“演唱会”要有哪些元素了!
2、实例化容器:
Spring 根据配置文件中的信息创建容器 ApplicationContext,在容器启动阶段实例化BeanFactory,并加载容器中的 BeanDefinitions。
类比演唱会:👉 咱成立了个演唱会指挥部(ApplicationContext),搭建核心团队(BeanFactory):有项目经理、导演组、物资组、后勤组,准备开始分工。
3、解析 BeanDefinitions:
Spring 容器会解析配置文件中的 BeanDefinitions,即声明的 Bean 元数据,包括 Bean的作用域、依赖关系等信息。
类比演唱会:👉 指挥部详细研究策划案:
• 哪个歌手唱什么歌
• 灯光什么时候亮
• 哪个保安负责哪片区域
• 哪个设备租的、哪个是自家的
🎶 Spring:我知道每个 Bean 是啥,有啥用,跟谁配合。
4、实例化 Bean:
Spring 根据 BeanDefinitions 实例化 Bean 对象,将其放入容器管理,
类比演唱会:👉 咱把人请到现场、设备搬到位:
歌手来了,灯光师来了,音响运到了。
但这些人和设备现在都只是站着、摆着,还没调试好。
5、注入依赖:
Spring 进行依赖注入,将 Bean 之间的依赖关系进行注入,包括构造函数注入、属性注入等。
类比演唱会:👉 开始分配工作和资源:
• 给歌手发麦克风
• 灯光师拿到控制台
• 音响接上电源线
🎶 Spring:我把 Bean 之间的关系都搭好,比如 userService 需要 dao,我给他装上。
6、处理 Bean 生命周期初始化方法
Spring 调用 Bean 初始化方法(如果有定义的话),对 Bean 进行初始化。如果 Bean 实现了 InitializingBean 接口,Spring 会调用其 afterpropertiesset 方法。
类比演唱会:👉 各部门自检:
歌手试音,灯光调光,音响试声。
有的部门还有初始化规程:比如“灯光师必须调好至少三种灯效方案”。
7、处理 BeanPostProcessors:
容器定义了很多 BeanPostProcessor,处理其中的自定义逻辑,例如postProcessBeforelnitialization 会在 Bean 初始化前调用,postProcessAfterlnitialization 则在之后调用,
Spring AOP 代理也在这个阶段生成。
类比演唱会:👉 导演组检查每个人:
• 上台前化化妆(Before 初始化处理)
• 上台后拍个定妆照(After 初始化处理)
• 给歌手、设备套上摄像头和麦(AOP 代理):全程记录,必要时在关键时刻切入,比如自动打开追光灯。
8、发布事件:
Spring 可能会在启动过程中发布一些事件,比如容器启动事件。
类比演唱会:👉 指挥部发通知:
• 演唱会已正式启动!
• 保安、后勤、消防开始进入监控状态。
听到消息的各方都准备进入各自的工作状态。
9)完成启动:
当所有 Bean 初始化完毕、依赖注入完成、AOP 配置生效等都准备就绪时,Spring 容器启动完成。
类比演唱会:👉 一切就绪,演唱会开场!
歌手嗨唱,灯光起舞,音响轰鸣,后台人员也都在运作。
🎶 Spring:所有 Bean 初始化完毕,依赖齐全,代理就位,容器已准备好服务请求。
二、 Spring会用到哪些设计模式?
- 工厂模式:从名字就能看出来是 BeanFactory,整个 Spring lOC 就是一个工厂
- 模板方法:例如 JdbcTemplate、RestTemplate,名字是 xxxTemplate 的都是模板.
- 代理模式:AOP 整个都是代理模式,
- 单例模式:默认情况下 Bean 都是单例的。
- 责任链模式:比如 Spring MVC 中的拦截器,多个拦截器串联起来就形成了责任链。
- 观察者模式:在 Spring 中的监听器实现。
- 适配器模式:在 Spring MVC 中提到的 handlerAdapter 其实就是适配器。
三、 Spring有哪几种事务传播行为?
- PROPAGATION REQUIRED(默认):如果当前存在事务,则用当前事务,如果没有事务则新起一个事务
- PROPAGATION SUPPORTS :支持当前事务,如果不存在,则以非事务方式执行
- PROPAGATION MANDATORY:支持当前事务,如果不存在,则抛出异常。
- PROPAGATION REQUIRES NEW:创建一个新事务,如果存在当前事务,则挂起当前事务
- PROPAGATION NOT SUPPORTED:不支持当前事务,始终以非事务方式执行。
- PROPAGATION NEVER:不支持当前事务,如果当前存在事务,则抛出异常
- PROPAGATION NESTED:如果当前事务存在,则在嵌套事务中执行,内层事务依赖外层事务,如果外层失败,则会回滚内层,内层失败不影响外层。
四、 Spring Boot的启动流程是什么?
这是一个“全景视角”:包括了容器初始化 + 环境构建 + 自动配置 + 启动服务器 + 发布事件 + 运行阶段。
1、启动 main()方法
应用从 main()方法启动,并通过springApplication.run()引导应用启动。
🧍♂️ 类比:你早上睁眼起床,意识到“啊!今天还得上班”。
2、创建 SpringApplication
应用会创建 springApplication 对象,推断应用类型、设置初始化器、设置启动监听器、确定主应用类。
🧠 类比:你开始准备出门:
• 判断今天是工作日还是休息日(推断应用类型)
• 决定穿什么(设置初始化器)
• 拿手机看看有没有通知(启动监听器)
• 想起今天的主要安排(确定主类)
3、准备环境(ConfigurableEnvironment)
Spring Boot 在启动过程中准备应用环境,加载配置文件、系统环境变量以及命令行参数。
🌍 类比:你检查外部环境:
• 看天气预报(配置文件)
• 记日程表(系统变量)
你得根据这些信息决定要不要带伞/提前出门等。
4、创建并刷新 ApplicationContext
创建应用上下文,加载配置类和自动配置类,注册 Bean 并执行依赖注入等初始化操作。
🏠 类比:你开始穿衣服、洗漱、收拾背包(加载配置、注册 Bean)。
这一步你就像是把整个早上出门前的准备搞定,为接下来“上班”做好准备。
5、在刷新上下文时启动嵌入式 Web 服务器
对于 Web 应用,Spring Boot 会自动启动嵌入式 Web 容器(如 Tomcat)并注册相关的 Servlet 和 Filter。
🚗 类比:你打开车门、打火、开上路(启动 Tomcat)。
Web 项目这一步就是启动“服务器”,为服务别人(比如客户)做好准备。
6、发布应用己启动事件
对应监听 stated 事件逻辑会被触发。
📣 类比:你发了条朋友圈:“我出发了!”
监听器就像关注你动态的朋友,会根据这条消息做事(比如提醒你买早餐)。
7、执行 CommandLineRunner 和ApplicationRunner
接口的初在应用启动完成后,执行实现了commandLineRunner和ApplicationRunner始化逻辑。
⚙️ 类比:你在通勤路上顺便做点事,比如:
• 打个电话(初始化数据)
• 发一封工作邮件(准备缓存)
这一步是在程序启动后,但正式工作前做的一些自定义逻辑。
8.发布 ready 事件、应用启动完成
触发 ApplicationReadyEvent,应用进入运行状态,处理业务请求或任务。
🏢 类比:你进公司、坐下、打开电脑,开始干活!
这个阶段就是 Spring Boot 正式准备完毕,开始处理外部请求或任务。
Spring Boot 阶段 | 是否包含 Spring |
---|---|
1. 创建 SpringApplication | ❌ Spring 不涉及 |
2. 准备 Environment | ❌ Spring 没有统一的环境概念 |
3. 创建 ApplicationContext | ✅ Spring IOC 容器开始建立 |
4. 刷新 ApplicationContext | ✅ ✅ 核心:Spring 初始化 Bean、DI、生命周期全都在这 |
5. 启动嵌入式 Tomcat | ❌ Spring 传统项目没嵌入式容器 |
6. 发布事件(Starting、Ready等) | ❌ Spring 没有 Boot 的事件体系 |
7. 执行 Runner | ❌ 纯 Spring 没有 Runner 接口 |
五、 Spring Boot如何实现自动配置?
Spring Boot 的自动配置是通过 @EnableAutoconfiguration注解实现,这个注解包含@Import({AutoConfigurationImportSelector.class})注解,导入的这个类会去扫描classpath 下所有的 META-INF/spring.factories 中的文件,根据文件中指定的配置类加载相应的 Bean 的自动配置。
这些 Bean 通常会使用 @conditionalonclass、@ConditionalOnMissingBean @conditionalonProperty 等条件注解。来控制自动配置的加载条件,例如仅在类路径中存在某个类时,才加载某些配置。
六、 如何理解 Spring Boot 中的 Starter?
在 Spring Boot 中,Starter 是一组有用的依赖集合,用于简化构建配置。Spring Boot 通过 Starter POMs 提供了各种常用功能模块的集成,开发者只需引入一个 Starter 依赖,便可以自动获得该模块的所有相关依赖和配置,而无需手动添加多个依赖库和配置文件。有点像我们去买一些必需品,然后有人打包成一个套餐售卖,我们就不需要一个一个再去找了,直接一次性打包拿走,节省了时间。
七、 什么是配置中心?有哪些常见的配置中心?
配置中心是一个用于配置集中化管理且支持动态更新、分发配置文件的工具(服务)它实现了配置的统一管理和动态刷新。当配置信息发生变化时,配置中心可以自动通知服务实例进行配置更新,这样就可以实例无需重启即可应用最新的配置,从一定程度上减少了系统访问的空窗期,非常灵活方便。
常见的配置中心:
- Spring Cloud Config:Spring 提供的分布式配置管理工具,支持从 Git、SVN 等版本控制系统加载配置。
- Apollo:由携程开源的配置管理中心,支持配置的实时推送和权限管理
- Nacos:阿里巴巴的配置管理和服务发现工具,既支持配置中心功能,又能作为注册中心使用。
- Consu:HashiCorp 提供的分布式系统管理工具,既可以用作服务发现,也可以用于存储和分发配置。
- Etcd:分布式键值存储工具,常用于 Kubernetes 集群中的配置管理。
- Zookeeper:Zookeeper 是一个开源的分布式协调服务,和 Nacos 一样,其既可以作为注册中心,又可以作为配置中心。
配置中心的优点
- 集中管理:将分布在各个微服务中的配置集中到一个中心位置,便于管理和维护。
- 动态更新:支持配置文件的热更新,减少重启应用的次数,提高系统可用性。
- 多环境支持:可以针对开发、测试、生产等不同环境加载不同的配置文件,增强系统的灵活性。
- 权限控制:许多配置中心如 Apollo 支持对配置文件的访问控制,保证配置安全。
配置中心如何实现动态更新?配置中心通常使用长连接或长轮询的方式来实现配置的动态刷新。
1、长连接:
长连接是一种基于 TCP 或 WebSocket 协议的连接方式,在建立连接后,客户端和服务器之间可以持续进行数据的双向传输,而无需每次都重新建立连接。长连接的典型实现是WebSocket 或 qRPC,能够实现实时的推送
工作原理:
客户端与服务器建立一个持久的连接,该连接保持打开状态。当服务器检测到配置变化时,立即通过这个连接向客户端推送变更信息,客户端即时接收和处理更新。
连接一直保持直到手动关闭或由于网络中断等因素断开
优缺点:
- 优点:实时性强,服务器可以即时推送更新;无需频繁建立连接,减少了连接开销。
- 缺点:长连接需要消耗更多的系统资源,并且对网络环境的要求较高,断线重连和连接管理需要额外的处理。
2、长轮询
长轮询是一种模拟服务器推送的机制,客户端主动向服务器发起请求,并保持连接(比如保持 30s),直到服务器有更新或超时为止。如果有更新,服务器会返回新的数据,客户端在接收到数据后,再次发起新一轮的请求(如果等待超时,也再次发起新的请求)
工作原理:
客户端发送 HTTP 请求给服务器,询问是否有配置更新。
服务器保持这个请求打开,直到检测到配置发生变更,或者达到超时时间。如果配置有更新,服务器返回更新的配置数据,客户端处理并再次发起新请求;如果没有更新,连接会超时,客户端也会重新发起请求模拟推送的效果,但本质上是客户端的连续请求。
优缺点:
- 优点:实现相对简单,兼容性好,适用于大多数网络环境。
- 缺点:即便没有配置变化,也需要不断发起请求,造成资源浪费;响应速度取决于轮询频率,不够实时。
八、 Spring Boot如何通过main方法启动web项目?
Spring Boot 应用的启动流程都封装在 SpringApplication.run 方法中,它的大部分逻辑都是复用 Spring 启动的流程,只不过在它的基础上做了大量的扩展。在启动的过程中有一个刷新上下文的动作,这个方法内会触发 webServer 的创建,此时就会创建并启动内嵌的 web 服务,默认的 web 服务就是 tomcat。
九、 Spring Boot的核心特性有哪些?
- 开箱即用,内嵌服务器。这个特点是程序员最直观的感受,相较于原本的开发,springboot 可以省略以前繁琐的 tomcat配置,快速创建一个 web 容器。
- 自动化配置。在 spring boot 中我们可以按照自动配置的规定(将自动加载的 bean写在自己jar 包当中的 meta/info/spring.factories 文件中或者通过的注解 @lmport导入时加载指定的类)这样我们的配置类就会被 Spring boot 自动加载到容器当中。同时还支持通过改写 yaml 和 propreties来覆盖默认配置 。
- 支持jar 包运行。传统部署web 容器都是打成 jar 包放在 tomcat 中。spring boot 可以打成jar 包只要有 java 运行环境即可运行 web 容器。
- 完整的生态支持。spring boot 可以随意整合 spring 全家桶的支持。像 Actuator 健康检查模块,Spring Data JPA 数据库模块,Spring Test 测试模块。这些都可以很优雅的集成在 spring boot 当中。
- 监控、健康检查支持。spring boot Actuator 支持开发者监控应用的运行状态,包括性能指标、应用信息和日志级别控制等。
十、 什么是Spring Boot?
Spring Boot 是一个简化 Spring 应用程序开发的框架。它的主要目标是减少 Spring 应用程序的配置和开发复杂性,使我们能够更快地构建、测试和部署 Spring 应用。
简单来说它通过提供默认配置、自动化配置和嵌入式服务器等功能,简化了传统 Spring 应用的繁琐配置过程。
有人将一些依赖关系、默认配置都梳理好了,我们直接一个引用就搞定了,这就是它的本质。
Spring Boot 主要特点
- 简化配置:Spring Boot 通过自动配置( @EnableAutoconfiguration)根据项目中的类路径依赖、环境变量等自动为应用配置适当的 Spring 模块,避免了大量的 XML 配置。
- 内置服务器:Spring Boot 内置了 Tomcat、Jetty、Undertow 等服务器,应用程序可以直接通过 java -jar方式启动,而不需要部署到外部的 Web 服务器中。
- 快速开发:Spring Boot 提供了开箱即用的项目结构、默认配置和依赖管理,支持快速原型开发。它还提供了许多常用的开发工具(如开发时热部署、应用健康检查等)
- 独立运行:Spring Boot 应用打包成一个独立的 JAR 或 WAR 包,可以通过命令行直接运行,简化了部署过程。
Spring Boot 的核心注解
1、@SpringBootApplication
@SpringBootApplication 是Spring Boot 的核心注解,它是以下三个注解的组合
- @Configuration :表示该类是 Spring 配置类,
- @EnableAutoConfiguration:启用 Spring Boot 的自动配置功能。
- @ComponentScan :自动扫描当前包及其子包中带有 Spring 注解的类(如@Controller、@Service 等)。
2、@EnableAutoConfiguration
@EnableAutoconfiguration 是Spring Boot 的自动配置核心注解,它会根据类路径中的依赖自动配置 Spring 应用中的各种组件。
示例:
如果应用中引入了 spring-boot-starter-web 依赖,Spring Boot 会自动为应用配置嵌入式 Tomcat 服务器、MVC 框架等。
十一、什么是服务熔断?
服务熔断指的是当某个服务的调用失败率持续升高时,通过中断对该服务的请求,防止系统资源被不断消耗,进而保护整个系统不受影响。熔断机制的灵感来源于电路熔断器(保险丝),在出现异常时,通过快速切断服务调用,避免故障进一步扩散。
服务熔断的常见触发条件
请求失败率高:当一个服务在设定的时间窗口内,连续多次请求失败(如超时、异常、HTTP 5xx 错误等),并且失败率超过设定阈值时,熔断器会自动触发,进入打开状态。
请求响应时间长:如果一个服务的响应时间长,导致调用超时,并且这种情况在一定时间内多次发生,熔断器也会触发熔断,阻止继续发送请求。服务不可达:当服务完全不可访问(如网络故障或服务宕机)熔断器可以直接切断请求,快速返回错误,避免进一步的资源浪费。
十二、Spring AOP默认用的是什么动态代理,两者的区别?
Spring Framework 默认使用的动态代理是 JDK动态代理,SpringBoot 2.x 版本的默认动态代理是 CGLIB。
两者的主要区别
代理方式:
- JDK动态代理:基于接口实现,通过java.lang.reflect.Proxy动态生成代理类。
- CGLIB 动态代理:基于类继承,通过字节码技术生成目标类的子类,来实现对目标方法的代理。
使用场景:
- JDK 动态代理:推荐用于代理接口的场景,适合代理的类实现了接口。
- CGLIB 动态代理:适合没有接口的类,或需要代理具体类中的非接口方法的场景。由于基于继承实现,不能代理 final 类和 final 方法。
十三、什么是AOP?
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,用于将跨领域的关注点(如日志记录、安全检查、事务管理等)与业务逻辑分离开来。它允许开发者通过“切面”(Aspect)将这些通用功能模块化,并将其应用到应用程序中的多个地方,从而避免代码重复。
核心思想:AOP 的核心思想是将与业务逻辑无关的横切关注点抽取出来,通过声明的方式动态地应用到业务方法上,而不是将这些代码直接嵌入业务逻辑中。
主要组成部分:AOP 包括几个关键概念:切面(Aspect)、连接点(Join Point)、通知(Advice)、切入点(Pointcut)和织入(Weaving)。
AOP 的核心概念
1、切面(Aspect):切面是一个模块,包含跨领域的关注点,比如日志、事务等。它可以包含多个通知(Advice)来定义在何时何地应用特定的逻辑。
2、连接点(Join Point):连接点是程序执行中的一个特定位置,例如方法调用或异常抛出。AOP 允许在这些点上插入切面逻辑。
3、通知(Advice):通知是定义在连接点执行的操作。常见的通知类型包括
- 前置通知(Before):在方法执行之前执行的操作。
- 后置通知(After):在方法执行之后执行的操作。
- 环绕通知(Around):在方法执行前后都可以执行的操作,可以控制方法是否执行。
- 异常通知(AfterThrowing):在方法抛出异常后执行的操作。
- 返回通知(AfterReturning):在方法成功返回后执行的操作。
4、切入点(Pointcut):切入点定义了在何处应用通知,通常是通过表达式来匹配方法或类。例如,可以定义某个包下所有方法为切入点。
5、织入(Weaving):织入是将切面应用到目标对象的过程。可以在编译时、类加载时或运行时进行织入。
• 切面(Aspect) 是“逻辑的组织者”,是一整个功能模块(比如日志、事务、权限验证);
• 连接点(Join Point) 是“可以插入逻辑的具体位置”,是代码运行中的“点”或“时机”。
👗 连接点(Join Point):所有报名参加的选手,理论上你都可以选。
👑 切入点(Pointcut):评委真正挑选出来的入围者(你关心的那几个)
十五、什么是服务雪崩?
服务雪崩是指在微服务架构或分布式系统中,由于某个服务不可用或性能下降,导致依赖它的其他服务也出现连锁故障,最终使整个系统或大部分服务不可用的现象。
主要原因:服务调用链复杂
在微服务架构中,各个服务之间存在大量的相互调用关系。一个服务的不可用或性能下降可能会导致依赖它的多个上游服务响应变慢,甚至出现请求堆积,从而影响到整个调用链。
示例:服务 A 调用服务 B,服务 B调用服务 C。如果服务C 发生故障且请求无法及时返回,服务 B 的请求将被阻塞,进而导致服务 A的响应变慢或超时。
重试机制的反作用:
当服务调用失败时,通常会有重试机制以增加成功的概率。然而,在服务故障或超时情况下,重试机制可能会产生更多的请求,进一步加剧下游服务的压力,导致故障范围扩大。
示例:服务 A 调用服务 B,如果服务 B 出现超时,服务 A 可能会发起多次重试,这些重试请求可能会给服务 B 带来更大的压力,最终导致服务 B的彻底崩溃。
服务雪崩的防范措施
1、使用熔断器
原理:熔断器(如 Hystrix、Resilience4j)能够在检测到某个服务请求的失败率达到一定阈值时,自动中断对该服务的进一步调用,从而防止服务继续被拖垮。
优势:通过熔断器,可以快速阻止请求进入故障服务,从而减少服务调用链中其他服务受到的影响。
2、服务降级
原理:当某个服务不可用时,可以提供降级方案,返回默认值或简化的结果,确保系统在部分功能不可用时仍能为用户提供基本服务。
示例:当库存服务出现故障时,可以返回一个库存数据缓存值或提示“库存信息暂时不可用”
3、限流与隔离
原理:通过限流(如令牌桶、漏桶算法)和隔离(如线程池隔离、信号量隔离),可以限制单个服务的请求数量,防止服务因流量过大而被压垮。
优势:限流和隔离可以控制服务的最大并发量,保护系统的关键服务在高并发场景下的稳定性。
十六、什么是循环依赖?
循环依赖(Circular Dependency)是指两个或多个模块、类、组件之间相互依赖,形成一个闭环。简而言之,模块A依赖于模块B,而模块B又依赖于模块A,这会导致依赖链的循环,无法确定加载或初始化的顺序。
十七、Spring如何解决循环依赖?
关键就是提前暴露未完全创建完毕的 Bean。
在 Spring 中主要是使用三级缓存来解决了循环依赖:
- 一级缓存(Singleton Objects Map):用于存储完全初始化完成的单例Bean。
- 二级缓存(Early Singleton Objects Map):用于存储尚未完全初始化,但已实例化的Bean,用于提前暴露对象,避免循环依赖问题。
- 三级缓存(Singleton Factories Map):用于存储对象工厂,当需要时,可以通过工厂创建早期Bean(特别是为了支持AOP代理对象的创建)。
解决步骤:
- Spring 首先创建 Bean 实例,并将其加入三级缓存中(Factory)。
- 当一个 Bean 依赖另一个末初始化的 Bean 时,Spring 会从三级缓存中获取 Bean 的工厂,并生成该 Bean 的对象(若有代理则是代理对象)
- 代理对象存入二级缓存,解决循环依赖。
- 一旦所有依赖 Bean 被完全初始化,Bean 将转移到一级缓存中。
类比背景:小明和小红互相抄作业
• 小明的作业需要看小红怎么写,小红的作业也要看小明怎么写 —— 典型的 循环依赖。
• 班主任(Spring 容器)就用了一套「提前暴露还没写完的作业草稿」机制,避免两个同学干等。
Spring 名词 | 类比为学生写作业场景 |
---|---|
Bean | 学生的作业 |
创建 Bean | 学生在写作业 |
完成 Bean | 作业写完了交上去 |
一级缓存(singletonObjects) | 正式提交的完整作业本 |
二级缓存(earlySingletonObjects) | 作业草稿(写了一大半,可以预览但还没交) |
三级缓存(singletonFactories) | 作业工厂(写作计划+写作手法,还没真正写) |
整体流程:小明和小红互相抄
- 小明要写作业(Spring准备创建 Bean A)
o 还没写完,但先把“写作计划”登记到三级缓存(singletonFactory)里。
o 这是:“我还没写,但等会你可以来问我大致思路。” - 同时,小明作业中需要引用小红(依赖 Bean B),于是:
o Spring 去创建小红的作业,照样登记小红的“写作计划”到三级缓存。 - 小红也发现需要引用小明!这就是循环依赖!
这时候小红就会去三级缓存找小明的工厂 → 生成一个“草稿版小明” → 放进二级缓存
也就是说,小红可以暂时“先抄一下小明的大致思路”(虽然小明还没写完) - 当小明小红都真正写完了,就把作业从二级缓存转移到一级缓存,表示作业正式完成!
如果是 构造器注入(constructor),是不能解决循环依赖的,因为根本没机会把“半成品”提前放出去;
十八、Spring循环需要三级缓存,二级缓存不够吗?
Spring 之所以需要三级缓存而不是简单的二级缓存,主要原因在于AOP代理和Bean的早期引用问题。
二级缓存虽然可以解决循环依赖的问题,但在涉及到动态代理(AOP)时,直接使用二级缓存不做任何处理会导致我们拿到的 Bean 是未代理的原始对象。如果二级缓存内存放的都是代理对象,则违反了 Bean 的生命周期。
Spring 中三级缓存的作用
缓存层级 | 作用 | 什么时候填 |
---|---|---|
一级缓存(singletonObjects) | 存的是最终成品 Bean(可能是代理对象) | 初始化完成后,完整 Bean 放入 |
二级缓存(earlySingletonObjects) | 存的是早期暴露的 Bean 实例(半成品,没初始化) | 解决循环依赖时,提早暴露还没完全初始化的 Bean |
三级缓存(singletonFactories) | 存的是一个工厂(ObjectFactory),可以生成 Bean(甚至带 AOP) | 创建 Bean 时,提前放入,等别人需要就生成 |
🎭 AOP 问题在哪里?
假设:你只用二级缓存来解决循环依赖
- Spring 创建 Bean A,开始实例化,还没完成初始化。
- A 依赖 B,于是去创建 B。
- B 依赖 A,于是试图从缓存中找 A:
o 只能从二级缓存拿到一个 早期的 A 实例(还不是代理对象)
o 所以此时注入进 B 的是 “未增强的 A”
❌ 这就错了!如果之后 A 被 AOP 增强了(比如被事务、日志拦截),那 B 里面引用的还是原始 A,AOP白做了!
🎯 正确做法:引入三级缓存机制
- 创建 A 的时候,先把一个创建 A 的工厂(ObjectFactory)放入三级缓存
👉 这个工厂知道如何生成 可能被 AOP 增强的 A 对象 - B 依赖 A 时,Spring 检查三级缓存:
o 不直接用二级缓存里的对象
o 而是从 三级缓存里的工厂调用生成对象,这个时候可以判断是否需要代理(比如有 @Transactional) - **AOP 就在这个点切入!**生成代理对象,并将代理对象放入二级缓存(供其他 Bean 依赖)
二级缓存只能存“已经创建出来的对象”,但那时候还没做 AOP 增强。
而三级缓存存的是“生成对象的工厂”,调用它的时候可以顺便判断“要不要包一层代理”,保证别人拿到的是最终版 Bean。
十九、Spring Bean的生命周期?
实例化:Spring 容器根据配置文件或注解实例化 Bean 对象
属性注入:Spring 将依赖(通过构造器、setter 方法或字段注入)注入到 Bean 实例中。
初始化前的扩展机制:如果 Bean 实现了 BeanNameAware 等 aware 接口,则执行aware 注入。
初始化前(BeanPostProcessor):在 Bean 初始化之前,可以通 BeanPostProcessor 接口对 Bean 进行一些额外的处理,
初始化:调用 InitializingBean 接囗的 afterPropertiesSet() 方法或通过 init-method属性指定的初始化方法。
初始化后(BeanPostProcessor):在 Bean 初始化后,可以通过 BeanPostProcessor 进行进一步的处理。
使用 Bean:Bean 已经初始化完成,可以被容器中的其他 Bean 使用。
销毁:当容器关闭时,Spring 调用 DisposableBean 接口的 destroy() 方法或通过destroy-method 属性指定的销毁方法。
二十、SpringMVC具体工作原理
1、客户端请求:用户通过浏览器发送 HTTP 请求,所有请求都被 DispatcherServlet 接收。
2、请求映射:DispatcherServlet 根据配置的处理器映射器(HandlerMapping)查找与请求 URL 对应的控制器(Controller)
3、调用控制器方法:找到控制器后,DispatcherServlet 将请求转发给对应的控制器方法进行处理。控制器方法处理业务逻辑后,通常返回一个 ModelAndView 对象,包含数据模型和视图名称。
4、视图解析:DispatcherServlet 根据控制器返回的视图名称,使用视图解析器(ViewResolver)将逻辑视图名称解析为实际的视图(如 JSP、Thymeleaf 等)
5、视图渲染返回:视图渲染引擎根据数据模型渲染视图,并将生成的 HTML 响应返回给客户端
二十一、Spring中BI是什么?
Dl(Dependency Injection,依赖注入)普遍认为是 Spring 框架中用于实现控制反转(IOC)的一种机制。DI的核心思想是由容器负责对象的依赖注入,而不是由对象自行创建或查找依赖对象。
通过 DI,Spring 容器在创建一个对象时,会自动将这个对象的依赖注入进去,这样可以让对象与其依赖的对象解耦,提升系统的灵活性和可维护性。
依赖注入的优势
- 解耦合:通过将对象的依赖从代码中抽离出来,由容器负责管理,降低了类之间的耦合度。
- 提高测试性:通过注入 mock 对象或替代实现,DI使得单元测试变得更容易.
- 提高可维护性和灵活性:通过配置文件或注解开发者可以在不修改代码的情况下更改依赖,增加了应用程序的可扩展性和灵活性