Spring 面试知识点
本页只放 Spring 面试标准回答、项目话术和知识点跳转。IOC、AOP、事务、循环依赖等底层原理统一放到 Spring 知识点文档中学习。
使用方式
mermaid
flowchart TD
A["面试页:标准回答"] --> B["知识点页:原理、流程图、Demo"]
B --> C["回到项目:组织医疗采集平台话术"]IOC和Bean生命周期
| 面试题 | 标准回答 | 原理知识点 |
|---|---|---|
| 怎么判断 Spring 是否真正学懂 | 不能只背 IOC、AOP、事务。真正学懂要能从 BeanDefinition、BeanFactory、ApplicationContext、Bean 生命周期、依赖注入、循环依赖、AOP 代理、声明式事务、编程式事务、事务传播、事件机制、扩展点和生产排查一路讲下来,并能说明项目里为什么这样设计、不这样会出什么问题。 | Spring从零到精通验收清单 |
| Spring 核心主线是什么 | Spring 的主线是:先把类解析成 BeanDefinition,再由 BeanFactory/ApplicationContext 创建和管理 Bean,通过依赖注入组装对象,通过生命周期扩展点介入创建过程,最后用 AOP 代理实现事务、日志、权限等横切能力。 | Spring从零到生产级掌握 |
| IoC 和 DI 有什么区别 | IoC 是对象定义、创建、依赖关系、作用域和生命周期控制权从业务代码反转给容器;DI 是实现 IoC 的主要手段,容器解析候选后通过构造器、方法或字段注入。IoC 不只等于依赖赋值。 | IoC与DI |
| BeanFactory 和 ApplicationContext 区别 | BeanFactory 是 Bean 获取、创建、依赖解析和作用域核心接口;ApplicationContext 在其上提供 refresh 生命周期、事件、Environment、Resource、国际化等应用能力,并通常在启动时预实例化非懒 singleton。 | BeanFactory与ApplicationContext |
| BeanDefinition 是什么 | BeanDefinition 是 Bean 的创建元数据,描述 class/工厂方法、scope、lazy、构造参数、属性、primary、depends-on、初始化和销毁方法。它不是实例;Spring 先注册和修改定义,再按定义创建对象。 | BeanDefinition注册全过程 |
| BeanDefinition 为什么重要 | 它把“怎样创建”与实例分离,使注册器/BFPP能在实例化前修改定义,BPP能在对象创建后注入和代理。配置类解析、占位符、自动配置、AOP都依赖先定义后创建的扩展链。 | BeanDefinition注册全过程 |
| ApplicationContext refresh 有哪些关键阶段 | prepareRefresh、取得BeanFactory、prepareBeanFactory、执行BFPP/BDRPP、注册BPP、初始化事件等基础设施、finishBeanFactoryInitialization预实例化非懒singleton,最后finishRefresh发布完成事件。 | ApplicationContext refresh全过程 |
| getBean 大致流程是什么 | 先转换别名和FactoryBean前缀,检查singleton缓存;没有则查找并合并BeanDefinition、处理dependsOn和scope,进入createBean完成实例化、注入、初始化和代理;若是FactoryBean,普通名称返回其产品,&名称返回工厂。 | getBean全过程 |
| 实例化、属性填充、初始化有什么区别 | 实例化通过构造器/工厂方法产生对象;属性填充由注入处理器设置字段和Setter;初始化执行Aware、BPP、PostConstruct/init等并可能生成代理。构造器循环在实例化前就互相需要,因此不能靠早期对象解决。 | 实例化与属性填充、Bean生命周期 |
| Spring Bean 完整生命周期是什么 | 先注册并合并BeanDefinition,getBean触发实例化;MergedBeanDefinitionPostProcessor准备注入元数据,满足条件时注册早期引用工厂;populateBean完成依赖注入,initializeBean执行Aware、BPP前置、PostConstruct、afterPropertiesSet、init-method和BPP后置,后置处理器可能返回代理。最终对象进入scope缓存,关闭时按依赖顺序销毁。 | Bean生命周期总流程 |
| 实例化、属性填充和初始化有什么区别 | 实例化通过构造器、工厂方法或Supplier产生对象;属性填充在实例产生后解析字段、Setter和配置值;初始化执行Aware、BPP、PostConstruct和初始化方法,最后可能得到代理。字段注入在构造器中为null,是因为它晚于实例化。 | 生命周期术语边界、populateBean阶段 |
| Spring 的 Aware 回调都在一个位置执行吗 | 不是。BeanNameAware、BeanClassLoaderAware、BeanFactoryAware通常由initializeBean中的invokeAwareMethods直接调用;ApplicationContextAware、EnvironmentAware、ResourceLoaderAware等通常由ApplicationContextAwareProcessor在BPP前置链中处理。 | Aware完整顺序 |
@PostConstruct、afterPropertiesSet和init-method顺序是什么 | 常见顺序是BPP前置链中的PostConstruct,然后InitializingBean.afterPropertiesSet,再执行自定义init-method,最后进入BPP后置链。PostConstruct不是JVM自动调用,Spring 5常由CommonAnnotationBeanPostProcessor体系处理。 | 初始化回调完整顺序 |
BeanPostProcessor 返回 null 是把 Bean 变成 null 吗 | 通常不是。某个前置或后置处理器返回null时,Spring停止当前处理器链并返回前一个非null结果。自定义BPP一般应返回传入Bean;随意返回null会让后续注入、生命周期或代理处理器失去机会。 | BPP生命周期语义 |
| SmartInitializingSingleton 在什么时候执行 | preInstantiateSingletons完成普通非懒singleton创建后,调用已创建Bean的afterSingletonsInstantiated。适合检查完整策略集合或建立索引,但不保证Lazy、prototype已创建,也不等于SmartLifecycle已启动或外部系统健康。 | SmartInitializingSingleton原理 |
| prototype Bean 为什么不自动执行销毁回调 | Spring负责prototype的创建、注入和初始化,但把实例交给调用方后通常不长期保存所有实例,因此上下文关闭时不能统一回收。prototype持有文件、线程、Socket或连接时,调用方必须明确关闭。 | prototype生命周期 |
| Lazy Bean 的生命周期和普通 singleton 有何区别 | Lazy只改变创建触发时机;首次getBean或Lazy代理首次调用时仍执行完整实例化、注入和初始化。风险是构造、PostConstruct和远程初始化错误推迟到真实请求,readiness通过也不代表能力已验证。 | Lazy Bean生命周期 |
| Spring Bean 销毁回调的顺序是什么 | 常见由DisposableBeanAdapter协调:DestructionAwareBeanPostProcessor先触发PreDestroy等注解,再调用DisposableBean.destroy,最后执行自定义destroy-method。若A依赖B,通常先销毁依赖者A,再销毁B。 | Bean销毁完整顺序、依赖销毁顺序 |
| Bean 初始化失败后为什么不能保留对象 | 对象可能只完成构造或部分注入,并已产生early reference、线程或连接。Spring必须清理创建标记、singleton缓存、依赖关系和销毁注册;refresh失败还会销毁已创建singleton。业务手工申请的局部资源仍需自己在失败路径释放。 | 生命周期失败回滚 |
@PostConstruct 没执行怎样排查 | 先确认对象由Spring创建,再检查方法签名、CommonAnnotationBeanPostProcessor、javax/jakarta版本、Bean是否在初始化前失败、测试上下文和实例化前短路代理。不能通过手工调用PostConstruct掩盖对象不受容器管理。 | PostConstruct不执行排查 |
| Spring 关闭阶段卡住怎样定位 | 在终止宽限期内连续采集线程栈,定位LifecycleProcessor、SmartLifecycle.stop或destroy回调;检查无界awaitTermination、无超时远程关闭、锁等待、消费者继续接任务、stop回调未调用和非守护线程,并改为分阶段、有界、幂等关闭。 | Bean销毁卡住Runbook |
| 常见注入方式 | 构造器注入让强制依赖在创建时完整、字段可 final 且易测试;Setter 适合真正可选或可变属性;字段注入由后置处理器反射完成,写法短但隐藏依赖、难直接 new 测试。生产 Service 优先构造器。 | 构造器、字段与Setter选型 |
@Autowired 和 @Resource 分别由谁处理 | @Autowired 主要由 AutowiredAnnotationBeanPostProcessor 处理;@Resource 主要由 CommonAnnotationBeanPostProcessor 处理。它们先缓存字段、方法等注入元数据,再在 Bean 创建过程中解析依赖并反射赋值,不是 Java 虚拟机看见注解后自动赋值。 | 注解来源与处理器 |
| 构造器注入和字段注入发生在什么时候 | 构造器依赖必须在实例产生前解析,用于调用构造器;字段和 Setter 注入发生在实例化之后、初始化回调之前的属性填充阶段。因此构造器注入能保证对象一创建就满足不变量,字段注入则不能让依赖成为真正的 final。 | 注入执行时机 |
@Autowired 有多个实现时怎样选择 | 先按声明类型和泛型筛候选,再排除不可自动注入项并应用 @Qualifier;候选仍多时再判断 @Primary、优先级和注入点名称,仍无法唯一确定就抛 NoUniqueBeanDefinitionException。不要把它简单背成“先类型后名称”。 | Autowired候选算法 |
@Resource 到底是按名称还是按类型 | 显式写 name 时按该名称查找并校验类型;未写时先用字段名或 Setter 属性名作为默认名称。Spring 的处理器在允许回退且默认名称不存在时才可能按类型解析,所以“永远先名称、失败必按类型”并不严谨。 | Resource解析全过程 |
@Primary、@Qualifier 和字段名怎样选 | @Qualifier 表达注入点需要的业务限定语义,通常比默认候选更明确;@Primary 是多个同类型 Bean 中的默认首选;字段名只是在前面步骤仍不能唯一选择时的回退信号。商业代码优先自定义 Qualifier,避免重命名字段改变装配结果。 | Primary与Qualifier |
| 可选依赖有哪些写法,语义有何区别 | required=false 表示找不到可以跳过该注入;Optional<T> 明确表达有或没有;@Nullable 允许传入空值;ObjectProvider<T> 支持延迟、按需、可选及迭代获取。需要运行期按需获取或解决 singleton 持有 prototype 时优先 ObjectProvider。 | 可选依赖全过程 |
List、Map 和泛型依赖怎样注入 | List<T>/数组会收集全部匹配 Bean 并可按顺序排列,Map<String,T> 的键通常是 Bean 名;Repository<Order> 之类的泛型信息还会通过 ResolvableType 参与候选筛选。它们适合策略链,但必须明确顺序、重复执行和空集合语义。 | 集合依赖注入、泛型依赖注入 |
字段写了注入注解为什么仍然是 null | 最常见原因是对象由 new、反序列化或第三方框架创建,根本没进入 Spring Bean 生命周期;还要检查静态字段、扫描范围、条件配置、测试上下文和过早访问。先确认对象来源与 Bean 名,不要靠重复加注解碰运气。 | 依赖注入异常诊断 |
| JDK 动态代理为什么可能导致按实现类注入失败 | JDK 代理对象通常只实现目标接口,并不是目标实现类的实例;如果注入点声明具体实现类,类型匹配或最终赋值可能失败。应优先按接口注入,确需类代理时再评估 proxyTargetClass、final 限制与代理策略。 | 代理与注入类型 |
| 明明声明了 Bean,为什么当前位置仍找不到 | Bean 可能位于不可见的子容器、被 @Profile 或 @Conditional 排除、组件扫描未覆盖,或测试只加载了切片上下文。父容器通常看不到子容器 Bean,子容器通常可以向父容器查找;排查时必须确认实际上下文层级和条件报告。 | 上下文与条件注入 |
Spring 6 升级后 @Resource 为什么可能失效 | Java EE 时代常用 javax.annotation.Resource,Spring 6/Jakarta EE 9 基线改为 jakarta.annotation.Resource。只改依赖不改 import,或同时混用两套 API,会导致编译、扫描或运行期不一致;应成组迁移依赖、源码和容器版本。 | javax到jakarta迁移 |
| 生产环境依赖注入问题怎样排查 | 先记录完整异常链和注入点,再列出实际候选的 Bean 名、类型、Qualifier、Primary、代理类型及来源;随后核对扫描、Profile/Condition、父子上下文和版本,最后用最小上下文测试固定装配契约。 | 依赖注入生产排查Runbook |
@Resource 注解是 JVM 看见后自动赋值吗 | 不是,注解只是运行期元数据。Spring通常由CommonAnnotationBeanPostProcessor扫描字段或方法、缓存InjectionMetadata,在属性填充阶段委托BeanFactory解析依赖,再反射字段或调用Setter完成注入。 | Resource处理器原理、真实Resource解析 |
显式 @Resource(name="x") 找不到会自动按类型吗 | 不能这样无条件回答。显式name通常是明确资源契约,找不到应失败;未显式name时先推导字段或属性名,只有在处理器允许默认类型回退且默认名称不存在时才可能按类型解析,具体还受版本和配置影响。 | Resource简化解析规则 |
| 为什么按类型找到多个实现不能随机注入第一个 | 容器遍历或注册顺序不是业务语义,顺序变化会静默切换实现。无法唯一确定时应启动失败,并使用显式name、Qualifier、唯一Primary或集合策略表达真实选择规则。 | Resource失败流程 |
| 手写简易容器能代表真实 Spring 吗 | 不能。它只能演示定义、创建、候选查找和反射注入主线;真实Spring还有BPP链、构造器解析、泛型限定、作用域、三级缓存、早期代理、FactoryBean、生命周期、父子容器、并发协调、异常回滚和销毁。 | 简易容器与真实Spring对照 |
| 单构造器为什么可以不写 @Autowired | Spring 4.3+ 对只有一个构造器的 Bean 会把它作为注入构造器,即使没有注解。多个构造器时仍要遵循候选、required和参数可解析规则,不能简单背“参数最多一定被选”。 | 构造器解析全过程 |
| ObjectProvider 解决什么问题 | 它支持延迟、可选、按需和多Bean迭代获取,适合可选插件或singleton每次取得prototype,避免业务持有ApplicationContext到处getBean。getObject找不到会抛异常,getIfAvailable才是可选语义。 | ObjectProvider与Lazy |
@Lazy 只有一种工作方式吗 | 不是。定义级 @Lazy 修改 BeanDefinition 的 lazy-init,令启动预实例化跳过该 singleton;注入点 @Lazy 创建延迟解析代理,当前 Bean 先拿代理,首次方法调用时才解析真实依赖。两者处理阶段和作用对象不同。 | Lazy两种机制 |
| singleton 为什么默认在启动时创建 | 常用 ApplicationContext 在 refresh 后段调用 preInstantiateSingletons,遍历非抽象、非懒的 singleton 定义并执行 getBean。这样配置和初始化错误能在发布阶段暴露,也避免首个请求承担全部初始化。 | singleton预实例化 |
目标类加了 @Lazy 为什么仍然启动创建 | 定义级 Lazy 只阻止容器主动预实例化;如果 eager singleton 普通注入它、存在 @DependsOn、Runner/Listener 调用 getBean,或基础设施间接访问真实对象,仍会触发创建。应确认注解位置并沿依赖图和调用栈找触发者。 | Bean定义懒加载全过程、提前创建排查 |
注入点 @Lazy 的底层原理是什么 | 依赖处理器构造 DependencyDescriptor 后,候选解析器识别 Lazy 元数据,通过 ProxyFactory 和延迟 TargetSource 创建类型兼容代理;当前 Bean 注入代理,第一次代理调用才执行真实 doResolveDependency、创建目标并转发调用。 | Lazy注入代理全过程 |
@Lazy 和 ObjectProvider 怎样选择 | Lazy 让调用方仍持有 T,但由代理隐藏解析时机;ObjectProvider 把按需获取显式写进代码,并支持可选、迭代和每次获取。低频昂贵普通服务可用 Lazy;插件、可选依赖、prototype 和精确控制获取时机时优先 ObjectProvider。 | Lazy与ObjectProvider |
@Lazy 怎样打破构造器循环依赖 | 在其中一个构造器注入点上使用 Lazy,容器先注入目标类型代理,不必立即创建另一方,因此当前 Bean 可先完成;代理首次调用时再创建目标,此时前一方已可用。但它只改变创建时序,没有消除双向职责耦合。 | Lazy打破构造器循环 |
| 全局懒初始化为什么有生产风险 | 它把许多 Bean 的缺依赖、配置错误、连接失败和初始化耗时从启动期推迟到真实请求,可能出现 readiness 已通过但首调超时。必须有关键 Bean 排除、预热、功能健康检查、首调指标和回滚,不能用它掩盖启动慢。 | Boot全局懒初始化 |
| Lazy Bean 首次并发调用会创建多份 singleton 吗 | 普通 singleton 最终仍由 Spring singleton 创建协调和缓存保证容器内复用,通常不会成功创建多份;但并发线程可能一起等待昂贵初始化,放大尾延迟。应监控等待线程、初始化耗时、外部 I/O、超时和失败重试。 | Lazy首次并发 |
| 启动成功但第一笔请求因 Lazy 超时怎样排查 | 从慢请求 span 和线程栈找 Bean 创建、类加载、连接初始化或远程 I/O,再确认 Lazy/全局 lazy 配置,定位构造器和 init 耗时,比较上游超时,检查并发等待与失败清理;止损可预热或摘除未就绪实例。 | Lazy生产排查Runbook |
| Spring singleton 是 JVM 全局单例吗 | 不是,默认语义是每个BeanFactory中一个Bean名称一份。多个ApplicationContext可各有实例;同一singleton会被多线程共享,因此Service不能保存当前用户等请求可变状态。 | Spring Bean作用域 |
| singleton 注入 prototype 为什么没有每次新建 | singleton创建时只解析一次依赖,得到的prototype引用被长期保存。需要每次使用新实例时应注入ObjectProvider、使用lookup method或作用域代理。 | Spring Bean作用域 |
| BeanFactory 和 FactoryBean 区别 | BeanFactory 是Spring容器核心接口;FactoryBean是容器中的特殊Bean,用来创建复杂产品。getBean("name")通常得到getObject产品,getBean("&name")得到工厂本身,getObjectType影响按类型候选发现。 | FactoryBean全过程 |
| Bean 为什么有时没有被AOP代理 | 可能手工new、在BPP完整注册前被BFPP过早getBean、由容器外工厂创建、切点不匹配或拿到目标对象。应检查实际class、BeanPostProcessor注册和创建时机,而不是只看注解。 | IoC后置处理器、IoC生产排查 |
| Spring 容器启动慢怎么排查 | 先区分慢在组件扫描/定义处理还是finishBeanFactoryInitialization创建Bean;定位具体构造器、init、BFPP/BPP、远程IO和锁。使用启动指标、JFR、线程栈和创建日志,不能给所有Bean加Lazy把问题推到首个请求。 | IoC启动慢排查 |
| Spring 常见扩展点有哪些 | 按生命周期看,BeanDefinition 阶段有 BeanDefinitionRegistryPostProcessor,Bean 实例化前有 BeanFactoryPostProcessor,初始化前后有 BeanPostProcessor,特殊对象创建有 FactoryBean,配置导入有 ImportSelector、DeferredImportSelector、ImportBeanDefinitionRegistrar,事件解耦有 ApplicationListener。 | Spring核心扩展点 |
| BeanFactoryPostProcessor 和 BeanPostProcessor 区别 | 前者处理的是 BeanDefinition 和 BeanFactory,发生在 Bean 实例化之前;后者处理的是 Bean 实例,发生在 Bean 初始化前后。简单说,一个改“定义”,一个改“对象”。 | Spring核心扩展点 |
| BDRPP 为什么需要循环发现 | 一个BeanDefinitionRegistryPostProcessor执行时可以继续注册新的BDRPP。Spring先按PriorityOrdered、Ordered和普通组执行,并循环查找尚未处理的注册器,直到没有新增,再统一调用其postProcessBeanFactory,避免新处理器永远错过定义阶段。 | BDRPP循环发现与排序 |
| 为什么 BFPP 中不应注入普通 Service | BFPP阶段完整BPP链尚未注册,依赖Service会导致它过早实例化,可能错过Autowired、PostConstruct和AutoProxyCreator,并出现not eligible for all BeanPostProcessors日志。BFPP应操作定义、Environment和轻量元数据。 | BFPP排序与过早实例化 |
| BeanPostProcessor 怎样排序和注册 | registerBeanPostProcessors先临时加入Checker,再按PriorityOrdered、Ordered、普通三组创建注册;MergedBeanDefinitionPostProcessor等内部处理器会调整位置,最后重新注册ApplicationListenerDetector。顺序只在同一阶段内有效。 | BPP注册排序与Checker |
| BPP 返回 null 会把 Bean 变成 null 吗 | 通常不会。当前前置或后置处理器返回null时,Spring停止该次处理器链并返回进入当前处理器前的结果,后续BPP失去执行机会,可能漏掉PostConstruct、Aware或AOP。自定义BPP一般应返回传入Bean。 | BPP返回null语义 |
| MergedBeanDefinitionPostProcessor 解决什么 | 它根据最终class和合并RootBeanDefinition缓存注入、Resource、生命周期、销毁等元数据,让prototype重复创建时不必反复扫描全部字段方法;定义重置时还要清理旧元数据。 | MergedBeanDefinitionPostProcessor |
| SmartInstantiationAwareBeanPostProcessor 有什么能力 | 它可预测Bean类型、提供候选构造器,并在循环依赖时通过getEarlyBeanReference产生一致早期代理。AutoProxyCreator借此避免一部分Bean持有原始对象、另一部分持有最终事务代理。 | SmartInstantiationAware原理 |
| DestructionAwareBeanPostProcessor 在什么时候执行 | 它在目标Bean销毁前参与,可触发PreDestroy、注销监听或清理框架为Bean建立的资源。回调应幂等有界;prototype通常不由容器完整销毁,不能依赖它回收所有prototype。 | DestructionAware原理 |
| FactoryBean 工厂和产品如何区分生命周期 | FactoryBean本身是普通Bean;普通名称取得getObject产品,&name取得工厂。工厂实例在singleton缓存,singleton产品可能在factoryBeanObjectCache;产品不能简单假设完整经历普通Bean的全部填充和销毁。 | FactoryBean产品生命周期 |
| ImportSelector、DeferredImportSelector和Registrar怎么选 | ImportSelector返回普通配置类名;DeferredImportSelector延迟到普通配置处理后,适合自动配置分组排序;Registrar直接操作BeanDefinitionRegistry,适合动态名称、构造参数和代理定义。三者都不应访问远程服务。 | Import扩展点对比 |
| SmartLifecycle 的 phase 和 stop callback 有什么用 | phase控制相对启停顺序,启动通常小phase先、停止反向;isAutoStartup控制自动启动;异步stop必须在真实停止后调用callback,否则容器会等待到超时。适合消费者、调度器和长连接。 | SmartLifecycle phase与停止 |
| Spring事件能保证事务提交后消息可靠吗 | 不能。默认事件常同步执行;异步事件切线程后不共享原事务。TransactionalEventListener的AFTER_COMMIT也可能在提交后、外部消息发送前宕机。跨服务可靠通知要使用Outbox、事务消息或CDC。 | Spring事件事务边界 |
| 商业 Starter 怎样组合扩展点 | 用自动配置发现候选,条件注解让用户Bean优先,ConfigurationProperties绑定校验,Bean/FactoryBean创建客户端,BPP做受控增强,SmartInitializingSingleton校验集合,SmartLifecycle启停连接,再通过Actuator暴露健康和指标。 | 商业Starter扩展链 |
| 扩展点导致启动慢或代理缺失怎样排查 | 先按refresh阶段定位处理器,统计调用次数与耗时,检查远程I/O、重复扫描和无界缓存;代理缺失时检查BPP注册、Checker日志、过早创建、处理器返回null、FactoryBean产品和父子容器。 | 扩展点生产排查Runbook |
项目话术:
text
采集平台里采集服务、清洗服务、标准化服务、资产目录服务通过 Spring IOC 管理。这样可以用接口隔离不同数据源实现,也方便测试、替换实现和统一管理初始化资源。循环依赖
| 面试题 | 标准回答 | 原理知识点 |
|---|---|---|
| 循环依赖有哪些类型 | 要区分构造器循环、字段/Setter循环、prototype循环、dependsOn创建顺序环和运行时业务调用环。三级缓存只可能帮助允许循环引用的部分singleton属性注入,不解决所有创建环,更不解决业务方法无限递归。 | 循环依赖类型 |
| Spring 能解决循环依赖需要哪些条件 | 参与者通常要是singleton、allowCircularReferences开启、至少一方能先实例化、依赖在属性填充阶段才需要,并且early reference与最终代理能够一致。prototype、构造器和不支持早期引用的复杂包装通常失败。 | 循环依赖可解决条件 |
| Spring 三级缓存分别是什么 | 一级singletonObjects保存最终singleton;二级earlySingletonObjects保存已经生成的早期引用;三级singletonFactories保存能按需调用getEarlyBeanReference的ObjectFactory。三级不是第三份Bean对象。 | singleton三级缓存定义 |
| 什么时候把 ObjectFactory 放入三级缓存 | doCreateBean完成实例化并执行MergedBeanDefinitionPostProcessor后、populateBean之前,若Bean是singleton、允许循环引用且正在创建,就调用addSingletonFactory注册捕获原始Bean的工厂。构造器循环在这一步之前已经失败。 | addSingletonFactory时机 |
getSingleton(beanName, true) 怎样查三级缓存 | 先查一级;未命中且Bean正在创建时查二级;二级仍无且允许early reference时取三级工厂,调用getEarlyBeanReference,把结果放二级并删除三级。之后其他依赖者复用同一早期引用。 | getSingleton早期引用源码 |
| 为什么只有二级缓存不够 | 如果实例化后直接把原始A放二级,B会注入原始A;A初始化后又生成事务代理,其他Bean拿代理A,B却永久绕过事务。三级ObjectFactory把早期引用生成推迟到真正需要时,让AutoProxyCreator有机会生成一致代理。 | 为什么需要三级缓存 |
getEarlyBeanReference() 做什么 | 它让SmartInstantiationAwareBeanPostProcessor参与早期引用,AutoProxyCreator可查找Advisor并返回JDK/CGLIB早期代理;同时记录earlyProxyReferences,避免初始化后再次创建不同代理。无需代理时可以返回原始Bean。 | getEarlyBeanReference代理全过程 |
| 什么是 raw injection despite wrapping | 循环依赖者先注入了原始A,但A初始化后被另一个BPP包装成代理A,于是容器内同时存在原始和代理语义。Spring在不允许raw injection时可能抛BeanCurrentlyInCreationException。正确方向是消除环或让处理器支持一致早期代理。 | raw injection despite wrapping |
| 构造器循环为什么三级缓存解决不了 | 三级工厂在实例化后才注册;构造器参数必须在实例化前解析。A构造需要B、B构造需要A时,A还没有实例,无法构造ObjectFactory中的早期对象,因此直接报BeanCurrentlyInCreationException。 | 构造器循环失败原理 |
@Lazy 为什么能打断构造器循环 | Lazy注入点先得到目标类型代理,不立即创建真实依赖,因此当前Bean可以完成构造;以后再解析真实目标。这不是三级缓存解决,而且逻辑双向依赖、首调延迟、代理限制和运行期失败风险仍然存在。 | Lazy打断构造器循环 |
| Spring Boot 为什么默认禁止循环依赖 | Boot 2.6起默认禁止循环引用,因为它掩盖职责问题,增加初始化顺序、代理一致性、测试、升级和AOT难度。spring.main.allow-circular-references=true只适合遗留系统短期过渡,长期应重构为无环依赖。 | 循环依赖版本开关 |
| 线上 BeanCurrentlyInCreationException 怎样排查 | 保存完整cause链,提取所有Bean名和注入点,画出A→B→C→B最小依赖图;区分构造、属性、prototype、dependsOn、初始化回调和代理不一致,再结合scope、Lazy、版本、开关和BeanDefinition来源定位。 | 循环依赖生产排查Runbook |
| 业务代码怎样真正消除循环依赖 | 优先提取应用协调器,让原本互调的领域服务由上层编排;只需要查询时提取小端口接口;共享纯逻辑下沉无状态组件;非核心副作用使用可靠事件,并补事务一致性、幂等、重试和补偿。 | 循环依赖设计重构 |
项目话术:
text
如果采集服务、资产服务、字典服务互相依赖,优先考虑拆分职责、提取编排服务或通过事件解耦,而不是依赖 Spring 帮忙解决循环依赖。Spring MVC请求执行链
| 面试题 | 标准回答 | 原理知识点 |
|---|---|---|
| Spring MVC 一次请求的完整流程是什么 | 请求由 Servlet 容器解析并经过 Filter 链,进入 DispatcherServlet;HandlerMapping 找到 HandlerExecutionChain,HandlerAdapter 执行 Handler;RequestMappingHandlerAdapter 通过参数解析器、DataBinder、Converter和Validator准备实参,调用Controller后由返回值处理器选择视图、异步处理或HttpMessageConverter写响应。异常交给HandlerExceptionResolver,最后执行拦截器完成回调和资源清理。 | Spring MVC完整请求链 |
| DispatcherServlet 启动时初始化什么 | FrameworkServlet先创建或查找WebApplicationContext并refresh;DispatcherServlet随后初始化MultipartResolver、LocaleResolver、HandlerMapping、HandlerAdapter、HandlerExceptionResolver、ViewResolver、FlashMapManager等策略。RequestMappingHandlerMapping还会扫描Controller并注册HandlerMethod。 | DispatcherServlet初始化全过程 |
@RequestMapping 怎样注册 | RequestMappingHandlerMapping在初始化期扫描Handler Bean,把类级和方法级路径、HTTP方法、params、headers、consumes、produces等条件合并成RequestMappingInfo并注册。无法区分的重复映射会在启动或匹配阶段报歧义。 | RequestMapping注册全过程 |
| HandlerMapping 和 HandlerAdapter 为什么分开 | HandlerMapping只根据请求定位Handler并组装拦截器链;HandlerAdapter判断自己是否支持该Handler,并按注解方法、传统Controller或自定义Handler的具体模型执行。这样DispatcherServlet无需了解每类Handler的调用细节。 | HandlerMapping全过程、HandlerAdapter全过程 |
| Controller 方法如何被调用 | RequestMappingHandlerAdapter准备ServletInvocableHandlerMethod、参数解析器、返回值处理器、DataBinderFactory和ModelFactory;逐个解析方法参数,完成转换、绑定和校验后反射调用Controller,再根据返回类型选择ReturnValueHandler。 | HandlerMethod调用全过程 |
@RequestParam、@PathVariable、@RequestBody 数据来源有何区别 | RequestParam读取query/form参数,PathVariable读取路径模板匹配变量,RequestBody通过HttpMessageConverter消费HTTP body。请求体通常只能读一次,Filter提前读取却未包装缓存,会导致Controller拿到空body。 | 参数解析器全过程 |
| 类型转换、数据绑定和校验有什么区别 | 类型转换把字符串变成Long、日期或枚举;数据绑定把多个属性写入目标对象并收集字段错误;校验在对象形成后执行NotNull、Min等约束。quantity=abc是转换失败,quantity=0可能转换成功但校验失败。 | 数据绑定、转换与校验全过程 |
@RequestBody 底层原理是什么 | RequestResponseBodyMethodProcessor根据目标类型和Content-Type遍历HttpMessageConverter,选择canRead的转换器,从ServletInputStream反序列化对象,执行BodyAdvice与校验,再作为Controller参数。格式或字段错误通常包装为HttpMessageNotReadableException。 | HttpMessageConverter读取全过程 |
| 415 和 406 有什么区别 | 415发生在读取请求时,表示Content-Type、consumes和可读Converter无法匹配;406发生在写响应时,表示客户端Accept、方法produces和可写Converter没有媒体类型交集。一个关注“服务端能不能读”,一个关注“客户端接受且服务端能不能写”。 | 消息读取与415、内容协商与406 |
@ResponseBody 怎样变成 JSON | 返回值处理器识别ResponseBody语义,结合Accept、produces、返回类型和Converter的canWrite计算媒体类型,常由Jackson Converter序列化对象,设置Content-Type并写入response。序列化异常发生在Controller已经返回之后。 | 返回值处理全过程、内容协商与写出 |
| HandlerInterceptor 的执行顺序是什么 | 多个拦截器preHandle按注册顺序进入,Controller正常完成后postHandle通常逆序,afterCompletion也逆序;preHandle返回false会终止后续链。异步请求还会触发afterConcurrentHandlingStarted并在结果完成后重新派发。 | HandlerInterceptor全过程 |
| Filter、Interceptor、ControllerAdvice和AOP怎样选择 | Filter位于Servlet链,适合请求包装、编码和CORS;Interceptor位于Handler链,能看到HandlerMethod;ControllerAdvice处理MVC绑定、响应和异常;AOP拦截Spring Bean方法,适合事务和业务横切。它们覆盖范围与上下文不同。 | Filter、Interceptor与AOP区别 |
| Spring MVC统一异常处理怎样工作 | DispatcherServlet把异常交给HandlerExceptionResolverComposite,常见顺序包含处理@ExceptionHandler的ExceptionHandlerExceptionResolver、状态注解解析器和默认框架异常解析器。解析器可生成响应或ModelAndView;若响应已提交,就可能无法改写为统一错误JSON。 | 异常解析全过程 |
| 响应 committed 是什么意思 | 状态行和Header已发送或缓冲区已刷新后,响应进入committed状态,此时通常不能再修改状态、Header或重新生成统一错误body。下载和流式响应中途失败时,客户端可能已收到200和部分字节,只能中断连接并记录协议内错误与指标。 | 响应提交时机 |
| Callable 和 DeferredResult 的异步原理是什么 | MVC启动Servlet AsyncContext并释放原请求线程;Callable由异步Executor执行,DeferredResult由外部线程设置结果。结果就绪后容器async dispatch,DispatcherServlet再次进入并完成返回值处理。阻塞I/O仍会占用异步线程,因此不等于端到端非阻塞。 | Spring MVC异步全过程 |
| Spring MVC 常见扩展点有哪些 | 自定义参数用HandlerMethodArgumentResolver,自定义返回类型用ReturnValueHandler,类型转换用Converter/Formatter,绑定限制用InitBinder,全局异常用ControllerAdvice,请求响应体加工用BodyAdvice,自定义Handler协议则组合HandlerMapping与HandlerAdapter。 | Spring MVC扩展点地图 |
| 线上404、400、415、406和500怎样定位 | 先按发生阶段分类:404看网关路径、context path和HandlerMapping;400区分参数转换、JSON读取和校验;415看Content-Type与可读Converter;406看Accept与可写Converter;500继续区分Controller前的解析绑定、业务执行和Controller后的序列化或视图渲染。 | MVC错误阶段表、生产排查Runbook |
| Spring MVC 与 WebFlux 怎样选 | MVC基于Servlet,适合JDBC和传统阻塞生态;WebFlux基于Reactive Streams,需要调用链端到端使用非阻塞驱动。WebFlux不会让阻塞JDBC自动非阻塞,MVC异步也不等于响应式背压,选型应依据依赖栈和压测。 | Spring MVC与WebFlux |
Spring容器源码执行链
| 面试题 | 标准回答 | 原理知识点 |
|---|---|---|
refresh() 完整流程是什么 | 先准备环境并取得BeanFactory,再安装容器基础能力;随后执行BDRPP/BFPP完成定义注册和修改,注册全部BeanPostProcessor,初始化消息、事件和监听器;finishBeanFactoryInitialization预实例化非懒singleton,最后finishRefresh启动生命周期Bean并发布刷新完成事件。 | refresh完整源码流程 |
| refresh 中途失败会怎样 | refresh外层捕获BeansException后会销毁本轮已经创建的singleton、清理创建状态并标记refresh取消,然后继续抛出异常;finally还会清理反射和注解等公共缓存。这样避免半成品Bean、早期引用和资源永久残留。 | refresh失败回滚、Bean创建失败回滚 |
| BDRPP、BFPP和BPP有什么区别 | BDRPP可以继续注册BeanDefinition,必须最早并循环发现;BFPP修改已有定义和BeanFactory;BPP处理以后创建的Bean实例。顺序必须是先完成定义,再注册实例处理器,最后创建普通Bean,否则Bean可能错过注入、生命周期和AOP。 | BeanFactory后置处理器执行全过程、BeanPostProcessor注册全过程 |
| ConfigurationClassPostProcessor 做什么 | 它作为重要BDRPP解析Configuration、ComponentScan、Import、ImportSelector、DeferredImportSelector、Registrar和Bean方法,持续注册新BeanDefinition,并增强full configuration class以维护跨Bean方法的容器语义。 | 配置类解析全过程 |
| 为什么不应在 BFPP 中注入普通 Service | BFPP阶段完整BPP链尚未注册;此时getBean会让Service过早实例化,可能错过Autowired、Resource、PostConstruct和AutoProxyCreator,甚至让基础设施永久持有原始对象。BFPP应操作定义、Environment和工厂元数据。 | BFPP过早创建Bean |
finishBeanFactoryInitialization() 只负责创建Bean吗 | 不只。它还关联ConversionService、加入嵌入值解析器、提前创建LoadTimeWeaverAware、清理临时ClassLoader、冻结配置,最后才调用preInstantiateSingletons。 | finishBeanFactoryInitialization全过程 |
preInstantiateSingletons() 怎样工作 | 它遍历定义名称,跳过abstract、非singleton和lazy Bean,对普通Bean调用getBean;FactoryBean还要区分工厂本身和是否需要eager产品。完成后调用已创建SmartInitializingSingleton的afterSingletonsInstantiated。 | preInstantiateSingletons全过程 |
doGetBean() 的完整主线是什么 | 先转换别名和FactoryBean前缀,查singleton及早期缓存;未命中则检查父工厂和循环,合并BeanDefinition、处理dependsOn,并按singleton、prototype或自定义scope进入createBean;最后处理FactoryBean产品并校验所需类型。 | doGetBean完整源码流程 |
getBean("x") 与 getBean("&x") 有什么区别 | 若x是FactoryBean,普通名称通常返回getObject产品,&x返回工厂Bean本身。工厂实例在singleton缓存,singleton产品可能在factoryBeanObjectCache,它们是不同对象和不同缓存。 | FactoryBean源码流程 |
getSingleton 怎样协调单例创建 | 它在创建协调范围内二次检查缓存,标记beforeSingletonCreation,调用ObjectFactory进入createBean,finally清除创建标记;成功后addSingleton进入一级缓存并移除二级、三级条目,失败则传播异常并清理状态。 | getSingleton创建协调 |
createBean() 和 doCreateBean() 区别 | createBean负责解析class、准备RootBeanDefinition、校验method override和实例化前短路;常规路径进入doCreateBean,后者负责实例化、合并定义处理、早期暴露、populateBean、initializeBean、早期引用一致性和销毁注册。 | createBean源码流程、doCreateBean完整源码流程 |
| Bean 实例怎样选择构造器 | createBeanInstance会在Supplier、工厂方法、缓存的解析结果、候选构造器和默认构造器之间选择,并结合Autowired required、参数值、类型名称、候选Bean、可选依赖和类型转换,不是简单选择参数最多的构造器。 | createBeanInstance全过程 |
populateBean() 做了什么 | 它先允许InstantiationAwareBPP决定是否继续填充,再处理传统byName/byType;postProcessProperties阶段由Autowired和Resource处理器解析DependencyDescriptor并注入字段或方法,最后BeanWrapper应用其他PropertyValues。 | populateBean源码流程 |
initializeBean() 的准确顺序是什么 | 先调用BeanName、ClassLoader、BeanFactory等Aware,再执行BPP BeforeInitialization;PostConstruct等通常由前置BPP触发,随后执行afterPropertiesSet和自定义init-method,最后执行BPP AfterInitialization并可能返回AOP代理。 | initializeBean源码流程 |
| Spring 容器只有三级缓存吗 | 不是。三级缓存只描述singleton早期引用链;此外还有FactoryBean产品缓存、合并BeanDefinition缓存、注入元数据缓存、候选构造器缓存、创建中状态和依赖关系等。每种缓存的键、值和失效时机都不同。 | Spring容器缓存地图 |
| AOP早期代理为什么要与最终代理一致 | 循环依赖时B可能先注入A的earlyBeanReference;A初始化后若暴露另一个包装对象,就会一部分Bean持有旧引用、另一部分持有最终代理,事务和切面语义不一致。因此doCreateBean会检查早期引用与最终exposedObject。 | AOP接入Bean创建源码、三级缓存源码流程 |
| Spring 容器怎样关闭 | close会发布ContextClosedEvent、停止Lifecycle Bean、destroySingletons,并按依赖关系执行DestructionAwareBPP、PreDestroy、DisposableBean.destroy和自定义destroy-method,最后清缓存并关闭BeanFactory。 | 容器关闭源码流程 |
| Spring 启动慢怎样按源码阶段排查 | 先区分慢在配置类/BDRPP/BFPP、BPP注册、finishBeanFactoryInitialization还是finishRefresh;再按BeanName定位构造、依赖解析、PostConstruct、FactoryBean、远程I/O和锁。结合ApplicationStartup、JFR和多份线程栈,不用全局Lazy掩盖根因。 | Spring容器启动慢排查 |
AOP和事务
| 面试题 | 标准回答 | 原理知识点 |
|---|---|---|
| Spring AOP 是什么 | Spring AOP 是基于代理的方法执行拦截:Pointcut选择类和方法,Advice定义增强,二者组合成Advisor;目标Bean初始化时AutoProxyCreator创建代理,外部调用进入MethodInterceptor链后才调用Target。 | AOP核心概念、Spring AOP全过程 |
| @Aspect 怎样变成可执行拦截器 | @EnableAspectJAutoProxy等机制注册AnnotationAwareAspectJAutoProxyCreator;它发现Aspect Bean,把切点和通知解析成Advisor,再把不同Advice适配为MethodInterceptor,目标Bean匹配Advisor时创建代理。 | Aspect到Advisor全过程、Advice适配 |
| Advisor、Pointcut、Advice 有什么关系 | Pointcut回答哪些类和方法匹配,Advice回答匹配后做什么,Advisor把两者组合成Spring内部可排序的增强单元。Aspect是声明层模块,可被解析成一个或多个Advisor。 | AOP核心概念、Pointcut匹配全过程 |
| AOP代理是在Bean生命周期哪个阶段创建 | 常见路径是目标实例化、属性填充和初始化后,AutoProxyCreator作为BeanPostProcessor后置处理器查找匹配Advisor,并用ProxyFactory返回代理作为最终Bean;循环依赖还可能通过getEarlyBeanReference提前建立代理。 | 自动代理创建全过程 |
| JDK 动态代理和 CGLIB 区别 | JDK Proxy生成接口实现并由InvocationHandler处理,按接口注入最稳;CGLIB生成目标类子类并拦截可覆盖方法,final类/方法、private/static不能按子类方式增强。最终选择还受proxyTargetClass、Boot默认和接口集合影响。 | JDK动态代理全过程、CGLIB全过程、代理选型 |
| Spring AOP拦截器链怎样执行 | Proxy取得Target和匹配Interceptor,ReflectiveMethodInvocation保存当前下标;每次proceed推进到下一个拦截器,只有到链末尾才调用目标方法,返回时按Java调用栈反向退出,因此多个Around形成嵌套洋葱模型。 | MethodInterceptor调用链 |
| 多个切面的顺序怎么判断 | 先看Advisor的Ordered/@Order,数值小通常进入时更外层、退出时更晚;再看通知类型和实际Advisor链。同一Aspect内多个同类型通知不要依赖方法名顺序,事务、缓存、重试组合要用测试验证。 | AOP顺序全过程 |
| Before、AfterReturning、AfterThrowing、After、Around区别 | Before在下游前;AfterReturning仅正常返回;AfterThrowing仅匹配异常;After类似finally;Around可控制是否及几次proceed、返回值和异常。Around忘记proceed会短路,调用两次会重复业务,吞异常可能改变事务回滚。 | AOP异常与返回语义 |
@Transactional 为什么依赖 AOP | TransactionInterceptor是Advisor链中的拦截器。调用必须进入代理,拦截器才能读取事务属性、开启或加入事务,执行目标后提交/回滚;手工new、自调用或不可代理方法没有新的代理入口。 | AOP与Spring基础设施、Spring事务 |
| 声明式事务是什么 | 声明式事务常见写法是 @Transactional,它把事务边界声明在类或方法上,底层通过 AOP 代理和 TransactionInterceptor 自动开启、提交、回滚事务。优点是业务代码干净,适合大多数 Service 层业务方法。 | Spring事务 |
| 编程式事务是什么 | 编程式事务是在代码里主动使用 TransactionTemplate 或 PlatformTransactionManager 控制事务边界。它不依赖代理,适合批量导入分批提交、捕获异常但仍要回滚、方法内部只有一小段需要事务等场景。 | Spring事务 |
TransactionTemplate 内部怎么执行 | 它会先根据模板配置创建事务定义,调用 PlatformTransactionManager.getTransaction() 获取或加入事务,然后执行回调;回调正常返回就提交,抛异常或标记 setRollbackOnly() 就回滚,最后解绑连接和事务同步资源。 | TransactionTemplate内部执行流程 |
| 声明式事务和编程式事务怎么选 | 常规业务写操作优先声明式事务;复杂流程中需要精确控制一小段事务、批处理每批提交、返回失败结果但仍要回滚时,用编程式事务。简单说,常规业务用声明式,复杂边界用编程式。 | Spring事务 |
| 事务传播是什么 | 事务传播解决一个事务方法调用另一个事务方法时,内层方法加入当前事务、新建事务还是非事务执行的问题。常用 REQUIRED、REQUIRES_NEW、SUPPORTS。 | Spring事务 |
| 逻辑事务和物理事务区别 | 逻辑事务是每个事务方法在 Spring 层形成的一层事务语义,物理事务是数据库连接上真正的事务。多个 REQUIRED 方法可以有多层逻辑事务,但底层只有一个物理事务,所以内层失败可能让整个物理事务只能回滚。 | 逻辑事务和物理事务 |
REQUIRED 和 REQUIRES_NEW 区别 | REQUIRED 有外层事务就加入,没有就新建,内外层一起提交或回滚;REQUIRES_NEW 会挂起外层事务并开启独立新事务,常用于审计日志、失败补偿记录等需要独立提交的场景。 | Spring事务、Spring事务 |
REQUIRES_NEW 有什么风险 | 它会挂起外层事务但外层连接通常还占着,内层新事务还要再申请一个连接。高并发下如果大量使用,容易造成连接池耗尽、线程等待连接、接口超时,所以只适合短小且确实需要独立提交的逻辑。 | REQUIRES_NEW的连接池风险 |
NESTED 和 REQUIRES_NEW 区别 | REQUIRES_NEW 是独立物理事务,内层提交后通常可以独立保留;NESTED 通常基于保存点,仍依附外层物理事务,内层可回滚到保存点,但外层最终回滚时内层也会一起回滚。 | NESTED和REQUIRES_NEW区别、NESTED保存点Demo |
| Spring 事务底层执行链路 | 外部调用进入代理对象后,TransactionInterceptor 读取事务注解配置,选择事务管理器,按传播行为获取或创建事务,把连接绑定到当前线程,执行业务方法,最后根据返回或异常执行提交、回滚、清理资源。 | Spring事务源码级执行链路 |
| MyBatis 为什么能加入 Spring 事务 | Spring 事务开启后,DataSourceTransactionManager 获取连接并绑定到当前线程。MyBatis 通过 Spring 集成获取连接时,会复用当前线程绑定的连接,所以多个 Mapper 操作能在同一个事务里统一提交或回滚。 | Repository为什么能拿到同一个连接、MyBatis中事务为什么能生效 |
UnexpectedRollbackException 是什么 | 内层 REQUIRED 方法加入外层事务后抛异常,事务被标记为 rollback-only;外层 catch 异常继续执行并尝试提交,Spring 发现事务只能回滚,于是回滚并抛 UnexpectedRollbackException。 | UnexpectedRollbackException是什么、commit不一定真的提交 |
afterCommit 能保证消息可靠吗 | 不能。afterCommit 只是在数据库提交成功后执行内存回调,提交后如果应用宕机或 MQ 发送失败,消息仍可能丢。核心链路要用本地消息表、事务消息、CDC 或补偿任务。 | TransactionSynchronization提交后回调、本地消息表和afterCommit的区别 |
| 事务为什么会失效 | 常见原因包括同类自调用没有经过代理、方法不是 public、异常被吞、异常类型不触发回滚、多线程导致事务上下文丢失、数据库不支持事务等。 | Spring事务 |
| 事务不回滚怎么排查 | 先看方法是否经过 Spring 代理,再看方法是否 public、异常是否抛到代理层、异常类型是否匹配回滚规则;然后排查跨线程、多数据源事务管理器、数据库表引擎、传播行为和 rollback-only 状态。 | 事务不回滚排查流程 |
| catch 异常后为什么没回滚 | 事务代理主要根据目标方法最终是否向外抛出需回滚异常来判断。异常被 catch 且没有重新抛出,也没有 setRollbackOnly(),代理会认为方法正常返回,于是提交事务。 | Spring事务 |
| Spring 事务怎么落到项目 | 订单创建和库存扣减要放在同一个事务;失败日志、补偿记录常用 REQUIRES_NEW 独立提交;大批量导入适合 TransactionTemplate 分批提交;事务提交后发 MQ 要注意 afterCommit 仍可能丢消息,可靠场景用本地消息表。 | Spring事务商业场景训练营 |
项目话术:
text
采集落库通常要用事务保证一批数据和批次状态的一致性。失败日志、补偿记录这类数据有时会用 REQUIRES_NEW 单独提交,避免主事务回滚后排查信息也丢失。事务失效重点排查是否经过代理、异常是否抛出、是否跨线程。常见追问
| 追问 | 回答方向 | 跳转 |
|---|---|---|
| IOC 是不是一个 Map | 说明 BeanDefinition、BeanFactory、后置处理器、单例池的完整链路。 | Spring IOC |
| FactoryBean 和 BeanFactory 区别 | BeanFactory 是 Spring 容器接口;FactoryBean 是一个特殊 Bean,用来生产真正要暴露的对象。getBean("name") 得到产品,getBean("&name") 才得到工厂本身。 | Spring核心扩展点 |
| Spring Boot 自动配置和 Spring 扩展点有什么关系 | Boot 自动配置通过延迟导入配置类、条件注解和 BeanDefinition 注册进入 Spring IOC 流程,最终仍靠 Spring 容器创建 Bean。 | Spring核心扩展点、Spring Boot自动配置 |
| 为什么构造器循环依赖解决不了 | 构造对象前就必须拿到对方,无法提前暴露实例。 | 循环依赖 |
| AOP 自调用为什么失效 | 外部调用先进入Proxy再进入Target;Target方法里的this.inner()是目标对象内部普通调用,不会重新回到外层Proxy,因此inner独有的事务、缓存、异步或重试Advisor没有新的执行机会,但outer已有增强仍然存在。 | AOP自调用全过程 |
| 自调用失效怎么解决 | 优先拆到职责独立的另一个Bean,或把增强边界放在真正外部入口;其次才考虑注入自身代理、AopContext或AspectJ编织。AopContext依赖exposeProxy和ThreadLocal,异步线程无上下文且业务耦合Spring。 | AOP自调用全过程、AopContext边界 |
| private、static、final方法为什么常不能被增强 | private不能被子类重写且接口不暴露,static不通过实例虚调用,CGLIB不能重写final方法/继承final类;JDK代理只能代理接口方法。构造器发生在代理完整建立前,手工new也不会经过AutoProxyCreator。 | AOP方法可见性限制 |
| Bean明明有注解为什么不是代理 | 先确认对象是否由Spring创建、实际class和AopUtils结果;再查Aspect是否注册、Pointcut是否匹配、Bean是否在AutoProxyCreator注册前被过早实例化、是否拿到Target而非Proxy,以及final/private和代理类型。 | AOP失效排查、AOP生产排查Runbook |
| Spring AOP和AspectJ是什么关系 | Spring可使用AspectJ注解风格和切点解析,但默认执行机制仍是运行时代理,只拦截从代理进入的方法调用;AspectJ ajc或加载期编织能修改字节码并覆盖构造器、字段等更多Join Point,但部署调试更复杂。 | Spring与AspectJ版本边界 |
| 事务、缓存、异步、重试叠加为什么要看顺序 | 它们都是Advisor。顺序决定重试是否每次新开事务、缓存值何时可见、异步是否在线程切换前开启事务。只叠注解不验证拦截器链,可能得到与业务预期完全不同的边界。 | AOP与Spring基础设施、AOP顺序全过程 |
| 事务跨线程为什么失效 | 事务连接和状态绑定当前线程,新线程拿不到外层事务上下文。 | Spring事务 |
| 编程式事务比声明式事务强在哪里 | 可以把事务精确控制在方法内部某一段,不依赖代理,能捕获异常后返回失败结果并手动 setRollbackOnly,适合批处理分批提交和框架组件封装。 | 声明式事务和编程式事务对比 |
| 批量导入为什么不建议一个大事务 | 大事务会长时间占用连接、行锁和 undo log,失败回滚成本高,也无法保留部分成功结果。商业项目通常按批使用 TransactionTemplate 提交,失败批次记录原因并允许重试。 | 编程式事务如何手动回滚、商业场景二:批量导入用编程式事务 |
| 为什么事务内不建议调远程接口 | 远程接口慢或失败会让数据库连接和锁长时间不释放,吞吐下降;远程调用成功但本地事务回滚还会产生跨系统不一致。应该把本地短事务、可靠消息、状态机和补偿任务拆清楚。 | 事务范围设计 |
| 怎么确认当前事务到底有没有开启 | 可以打开 org.springframework.transaction 的 TRACE 日志,观察 Creating new transaction、Participating in existing transaction、Suspending current transaction、Initiating transaction rollback/commit 等关键词。 | 事务不回滚排查流程 |
面试回答模板
text
Spring 这块我会围绕容器和代理回答。IOC 负责对象创建、依赖注入和生命周期管理;AOP 基于代理处理事务、日志、审计这类横切逻辑。比如采集平台落库时,@Transactional 本质就是事务代理,只有调用经过代理对象,Spring 才能开启、提交或回滚事务。具体 Bean 生命周期、三级缓存和事务失效原因可以看对应知识点。深度验收跳转
如果面试官继续追问“你说的这些到底怎么发生”,不要只在本页背短答案,回到 Spring 从零到精通验收清单 按阶段复盘:
- IOC:为什么需要容器,BeanDefinition 为什么是图纸。
- 生命周期:实例化、依赖注入、Aware、BeanPostProcessor、初始化、代理、销毁。
- 循环依赖:三级缓存、早期引用、构造器循环为什么不行。
- AOP:代理对象、拦截器链、JDK 动态代理、CGLIB、自调用失效。
- 事务:声明式事务、编程式事务、传播行为、连接绑定、MyBatis 加入事务。
- 扩展点:BDRPP、BFPP、BPP、FactoryBean、ImportSelector、事件。
- 排查:注入失败、Bean 创建慢、事务不回滚、切面不生效。
本章小结
Spring 面试页只帮助你快速组织答案。想真正理解为什么 IOC 能管理 Bean、为什么 AOP 会自调用失效、为什么事务会失效,要跳到对应知识点页看原理和流程图。
