尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Spring面试核心考点解析:IOC、AOP、事务与自动配置

Spring面试核心考点解析:IOC、AOP、事务与自动配置 1. 面试前的整体规划Spring考察的底层逻辑我做了这么多年技术面试官也陪跑过不少候选人准备面试先说一个最核心的观察Spring相关的题表面上考的是“知识点”实际上考的是“有没有真的用Spring写过东西”。背答案的人遇到稍微变通的问题就会露馅真正写过代码的人哪怕记不住源码细节也能说出个所以然来。所以这篇分享我会把Spring面试里最高频、最容易区分水平的考点串起来讲并且告诉你每一类题目面试官究竟想听什么。Spring面试题覆盖的范围很广从IOC、AOP、事务到Spring Boot自动配置、Spring Cloud微服务再到Spring Security、Spring AI这类新方向每一个都能展开聊很久。但绝大多数公司的面试不会每个方向都问一遍而是根据岗位职级和项目经历聚焦在若干个核心模块。我梳理了近两年的面试反馈和数据发现考察频率最高的几个模块是IOC与Bean生命周期、循环依赖与三级缓存、AOP与动态代理、事务机制、Spring Boot自动配置原理。这五个模块大约覆盖了Spring相关面试问题的七成以上所以我们先从它们开始逐个拆透。这套内容适合谁如果你正在准备Java后端岗位面试不管是校招还是社招这篇都有参考价值如果你是刚学完Spring想查漏补缺的开发者里面每个章节也都能当成自测清单用。我会尽量把每个核心问题的“标准答案框架”“面试官追问方向”“加分回答姿势”都讲清楚因为面试的本质不是背答案而是让对方相信你理解了这个东西。2. IOC与Bean生命周期面试官最爱问的地基问题2.1 控制反转到底反转了什么IOC这个话题几乎每一场Java面试都会碰到但很多人回答得特别空。最常见的说法是“IOC就是把对象的创建交给Spring容器管理”这句话没错但只说了一半。面试官真正想听的是你理解了这个设计解决的是什么问题。传统开发里对象之间的依赖关系是通过new关键字硬编码在代码里的。比如OrderService要用UserService就在OrderService里面直接new UserService()。这听起来没什么问题但一旦系统变大这种写法会带来两个麻烦一是对象创建逻辑散落在各处改一个依赖的构造方式所有相关位置都要跟着动二是耦合度高想替换实现类做测试非常麻烦因为代码里写死了具体的类名。IOC反转的就是这个“控制权”。本来由业务代码决定“我要创建谁”反转之后变成了“我需要什么容器给我什么”。你只需要在配置里声明依赖关系容器负责把对象创建好、组装好在合适的时机交给你。控制权从代码转移到了容器所以叫控制反转。而DI依赖注入是IOC的一种实现手段——容器通过构造器、Setter或者字段注入把依赖传递进来而不是让对象自己去创建依赖。在面试中回答这个问题我建议你先说设计思想再举一个具体的例子说明硬编码依赖的痛点最后点一下Spring中IOC容器的落地形态BeanFactory和ApplicationContext。这样既有理论高度又有实战感面试官想继续追问你也能接得住。2.2 手写一个mini版IOC理解就到位了很多候选人背熟了IOC定义但一问到“如果让你自己实现一个Spring容器你会怎么做”就卡住了。其实这个问题的答案没那么玄乎核心就三步扫描类、存定义、反射创建。第一步扫描指定包下的所有类把带有Component这类注解的类找出来第二步把这些类的元信息存到一个Map里key是beanNamevalue是类的Class对象或者BeanDefinition第三步根据类的构造器用反射newInstance创建实例然后扫描字段上的Autowired或Resource注解递归注入依赖。这个过程就是最简版IOC容器。我把核心逻辑写出来你一看就明白public class SimpleIocContainer { private MapString, Object singletonObjects new ConcurrentHashMap(); private MapString, Class? beanDefinitions new ConcurrentHashMap(); public void register(String beanName, Class? clazz) { beanDefinitions.put(beanName, clazz); } public Object getBean(String beanName) throws Exception { Object bean singletonObjects.get(beanName); if (bean ! null) { return bean; } Class? clazz beanDefinitions.get(beanName); if (clazz null) { throw new IllegalArgumentException(No bean defined: beanName); } // 第1步反射创建实例 bean clazz.getDeclaredConstructor().newInstance(); // 第2步处理依赖注入 for (Field field : clazz.getDeclaredFields()) { if (field.isAnnotationPresent(Autowired.class)) { field.setAccessible(true); Object dependency getBean(field.getName()); // 模拟按名称查找 field.set(bean, dependency); } } singletonObjects.put(beanName, bean); return bean; } }这段代码虽然简陋但已经把Spring容器最核心的两个机制体现出来了单例缓存和依赖注入。真实Spring容器比这个复杂得多因为还要处理BeanDefinition的解析、属性填充、初始化回调、代理生成、作用域管理、销毁逻辑等等但底层思路是一致的。面试时如果能用这样的代码框架讲清楚IOC的本质比单纯背概念有说服力得多。2.3 Bean完整生命周期从定义到销毁的全程拆解Bean生命周期是Spring面试中的重头戏几乎十个面试官里有八个会问。有些人能背出“实例化、属性填充、初始化、销毁”四个阶段但真要问“BeanPostProcessor在哪个阶段起作用”“循环依赖发生在哪一步”就又含糊了。我建议你在面试中把生命周期拆成四个大阶段来讲实例化阶段、属性填充阶段、初始化阶段、销毁阶段。每个阶段内部还有细分我用一条时间线给你捋清楚。实例化阶段容器根据BeanDefinition通过反射调用构造器创建Bean的实例。这里有个细节Spring默认使用无参构造器如果类里只有有参构造器而你又没有通过配置指定构造参数Spring会报错——除非它找到了可以匹配的候选Bean做自动装配比如只有一个构造器时Spring会自动用它的参数去容器里找依赖。属性填充阶段实例创建完之后Spring会处理各种依赖注入。Autowired、Resource、Value这些注解的解析以及XML里property标签的配置都在这个阶段完成。这是循环依赖发生的关键阶段当A填充依赖时需要B而B填充依赖时又需要A就形成了循环引用。初始化阶段属性填充完毕Spring会调用一系列初始化回调。顺序是这样的先是BeanNameAware、BeanClassLoaderAware、BeanFactoryAware这些Aware接口回调然后是BeanPostProcessor的postProcessBeforeInitialization方法接着是PostConstruct标注的方法再接着是InitializingBean接口的afterPropertiesSet方法最后是配置的init-method方法。这里最容易记混的是PostConstruct和afterPropertiesSet的执行顺序记住一个口诀Aware最早Before前置处理器其次PostConstruct第三afterPropertiesSet第四init-method最后。销毁阶段容器关闭时单例Bean会依次执行PreDestroy标注方法、DisposableBean接口的destroy方法以及配置的destroy-method方法。这里要特别注意PreDestroy只对单例Bean有效原型作用域的Bean默认不会被容器销毁管理。除了这四个阶段还要提一下BeanPostProcessor的妙用。Spring的AOP自动代理就是通过AbstractAutoProxyCreator这个BeanPostProcessor在初始化阶段介入的——Bean初始化完之后这个处理器会判断是否满足切点条件如果满足就生成一个代理对象返回。所以你在容器里拿到的可能不是原始Bean而是它的代理。我建议你把下面这张时序列表记下来面试的时候按顺序讲基本就是标准满分答案阶段关键动作扩展点实例化反射创建对象构造器构造器选择、实例化策略属性填充依赖注入、属性赋值Autowired、Value、Resource初始化-前置各种回调准备Aware接口、BeanPostProcessor.before初始化-核心业务初始化逻辑PostConstruct、InitializingBean、init-method初始化-后置代理生成等扩展BeanPostProcessor.after销毁资源释放PreDestroy、DisposableBean、destroy-method2.4 一个容易踩坑的细节原型Bean与单例Bean的差异很多人在面试时会提到Bean的作用域但往往只背了singleton和prototype的名字并没有真正理解它们在容器中的行为差异。这里我展开聊一下因为面试官很喜欢在这个话题上做文章。单例Bean是Spring默认的作用域整个容器里同一个Bean定义只创建一个实例缓存在singletonObjects这个Map里。所以如果你在单例Bean里放了一个可变的状态字段并发访问时就要自己考虑线程安全问题——容器不负责多线程同步。原型Bean则每次获取都创建一个新实例不进单例缓存。但有个大坑如果某个单例Bean依赖了一个原型Bean那这个依赖在容器启动时就被注入进单例Bean了之后不管从容器里获取多少次拿到的都是同一个原型Bean实例。也就是说原型作用域只对“每次从容器直接获取Bean”生效对“单例Bean里注入的属性字段”是失效的。如果面试官追到这里你还可以补充一个解决方案通过ObjectFactory或者ApplicationContext.getBean()来延迟获取原型实例或者用Lookup注解标注一个方法让Spring在每次调用时动态生成原型Bean的返回值。能说到这个层次说明你不是光背理论而是真的在项目里遇到过这种场景。3. 循环依赖与三级缓存Spring源码的必考科目3.1 循环依赖是什么为什么会发生循环依赖指的是两个或多个Bean互相持有对方的引用形成一个闭环。最简单的例子A依赖BB依赖A。在Spring容器加载时创建A时需要注入B创建B时需要注入A如果没有特殊机制就会无限循环下去最终抛出一个BeanCurrentlyInCreationException。有个问题是用构造器注入的循环依赖是解决不了的只有基于属性或Setter的循环依赖才能被Spring处理。原因是构造器注入要求对象在创建时就完成依赖传递而这时候A和B都还没实例化完成没有一个可以提前暴露的半成品对象来做中间过渡。但是属性注入不一样——A可以先用无参构造器创建一个实例再把A的“早期引用”暴露出去让B先拿到这个半成品等B创建完成后再把属性值补回A。这个区别面试时一定要主动讲出来它往往也是面试官设下的陷阱题他可能先问“Spring如何解决循环依赖”你回答完三级缓存之后他再问“那构造器循环依赖呢”如果你之前没讲这时候就很容易卡壳。先发制人把这个坑点透露出来会显得你考虑问题很全面。3.2 三级缓存的设计与执行时序Spring容器里为了解决属性注入循环依赖设计了三个缓存Map源码中定义在DefaultSingletonBeanRegistry里MapString, Object singletonObjects; // 一级缓存完整成品 MapString, Object earlySingletonObjects; // 二级缓存早期暴露的引用 MapString, ObjectFactory? singletonFactories; // 三级缓存对象工厂一级缓存放的是创建完成、属性填充完毕、初始化结束的成品Bean二级缓存放的是已经实例化但还没完成属性填充和初始化的早期Bean三级缓存放的是ObjectFactory它是一个工厂接口调用它的getObject()方法可以生成一个早期Bean对象。为什么要分三级而不是直接用二级缓存这是面试里最核心的追问点。答案是为了处理AOP代理的场景。想象一下如果最终需要的是一个代理对象而循环依赖发生时A只是半成品这时候如果直接把原始A对象放进二级缓存B拿到的就是原始类实例后续AOP生成的代理对象就不会替代它——B持有的引用和容器最终暴露的引用不一致。三级缓存里的ObjectFactory可以在早期引用阶段就调用getEarlyBeanReference()方法提前判断这个Bean是否需要代理如果需要就生成代理对象暴露出去。这样一来B拿到的就已经是最终的代理形态之后容器里的成品也是同一个代理引用一致逻辑正确。时序可以这么理创建A - 实例化完成 - 把A的ObjectFactory放入三级缓存 - 填充A属性发现需要B - 去容器找B - B不存在则创建B - B实例化完成 - 填充B属性发现需要A - 从一级缓存找不到A - 从二级缓存找不到A - 从三级缓存拿到A的ObjectFactory - 调用getObject()得到早期A引用 - 放入二级缓存 - B拿到A引用完成属性填充 - B继续初始化和后续流程 - B创建完成后回到A - A从缓存中拿到B引用完成属性填充 - A执行初始化 - 最后A也放进一级缓存同时清除二三级缓存中的A。面试时如果能把这个流程流畅地讲完基本就把这个考点吃透了。不过我要提醒一句Java多线程环境下三级缓存还存在一个细微的可见性问题Spring是通过synchronized关键字配合双重检查锁来保证缓存的原子性的。这个话题太细面试中谈不谈到都不影响大局但有精力的话可以作为加分点。3.3 面试追问三级缓存都能被二级缓存替代吗这是近几年非常流行的一个追问点面试官想考察你是不是真的理解了三级的必要性而不是只记住了“三级缓存”这个名字。网上有不少说法是“为了AOP才需要三级”但从Spring源码的角度更精确地讲三级缓存存在的核心理由之一是让AOP代理能够在早期引用阶段就暴露正确的类型。如果没有AOP只有普通的属性注入循环依赖那两级缓存是足够的——实例化完成后直接把半成品放到暴露Map里后续填充属性时直接取用即可。但如果引入了AOP事情就复杂了。在循环依赖发生的时候A可能是一个需要生成代理的Bean。如果只有二级缓存我们在A实例化完成后只能把原始对象放进去。等到A初始化完Spring在initializeBean步骤之后调用AbstractAutoProxyCreator生成代理时B那边持有的还是原始对象的引用。这个B拿到的A和最终容器提供的A就不是同一个引用行为会出现偏差。三级缓存的ObjectFactory就是为了解决“代理需要尽早暴露”的问题。它提供一个延迟计算的入口在A还没完成初始化时如果有其他Bean提前引用A可以先通过工厂触发getEarlyBeanReference()此时提前创建代理如果没有循环依赖那工厂不会被提前调用一切照旧直到常规的后置处理阶段才生成代理。这种“延迟到需要时才决定是否代理”的设计才是三级缓存的精髓。3.4 实际业务中怎么避免循环依赖虽然Spring有三级缓存兜底但在我实际的项目经验里循环依赖通常不是什么好味道它往往意味着模块间职责划分不够清晰。所以面试里如果被问到“你们项目里有循环依赖吗怎么解决的”不要只答“靠三级缓存”更要说出设计层面的规避手法。常用的方案有三个重构拆分——把A和B互相依赖的逻辑抽到第三个类C里让A和B都只依赖C改构造器注入——Spring对构造器循环依赖不支持所以如果你改成构造器注入编译期就能暴露循环问题强制你重构使用延迟加载——在字段上配合Lazy注解让Spring在注入时生成一个代理对象真正使用时才从容器中获取目标Bean的引用。这三种方案里Lazy最快但只是权宜之计重构才是长久之策。我见过一些项目搞到后来Bean之间的依赖网纠缠得跟蜘蛛网似的连Spring容器启动都要花很长时间。原因就是早期图方便到处用Autowired注入互相引用的Bean没有维护好依赖方向。面试时如果能从“设计合理性”的视角来讲循环依赖问题而不是仅仅停留在“Spring能解决它”会显得你对架构问题有自己的判断力。4. AOP与动态代理看透代理生成的底层逻辑4.1 AOP解决什么问题核心概念怎么串起来AOP面向切面编程解决的是业务代码中横切关注点日志、权限、事务、性能统计等难以复用、容易侵入业务逻辑的问题。比如你有一个订单Service里面十几个方法都要打日志、都要开启事务如果每个方法里手动写代码重复度极高还容易漏写。AOP的做法是把这些横切逻辑抽到“切面”里通过“切点”描述“在哪些方法上生效”通过“通知”描述“在这些方法执行的哪个时机做什么事”。常见的通知类型有五种Before方法执行前、AfterReturning(正常返回后、AfterThrowing异常抛出后、After最终执行后、Around环绕通知最灵活可以控制目标方法是否执行。面试里描述这些概念时不要只念定义最好用一个例子串起来。比如权限校验场景切点是execution(* com.example.service.*.*(..))切面里用Before通知检查当前用户是否登录又比如事务场景切点是Transactional标注的方法切面在Around通知中开启事务、执行目标方法、提交或回滚。这样回答既完整又贴近实际。我还想提醒一点AOP的本质是动态代理。Spring AOP默认在目标类实现了接口时使用JDK动态代理如果目标类没有实现接口则使用CGLIB生成子类代理。很多候选人只记得“JDK代理要接口CGLIB不用”但不知道Spring Boot 2.x之后默认的行为是什么。实际上Spring Boot 2.x开始默认优先使用CGLIB动态代理不再强迫依赖接口。这一点经常在面试中作为细节题被问到。4.2 JDK动态代理与CGLIB的差异对比深入讲一下两种代理的区别这是AOP面试题的延伸细节点。JDK动态代理基于Java的反射机制核心类是java.lang.reflect.Proxy和InvocationHandler。它要求目标类必须实现至少一个接口代理对象和目标对象实现相同的接口调用方看到的是一个接口类型的对象。因为额外引入了一层接口调用开销性能上没有CGLIB直接操作字节码快但它的创建过程不依赖额外的字节码库天生更“轻”。从维护角度看基于接口编程也更容易保持整洁。CGLIB则是通过生成目标类的子类来实现代理所以要求目标类不能被final修饰被代理的方法也不能是final的否则无法重写。CGLIB生成代理时直接操作字节码动态生成一个目标类的子类重写非final的方法在方法调用前后织入增强逻辑。它的调用效率和灵活性在某些场景下更高代价是最终类无法代理、方法重写要满足访问控制条件。在Spring Boot 2.x中默认的proxy-target-class已经被设为true也就是优先用CGLIB。所以你在项目中会发现哪怕Service实现了接口默认注入的也是CGLIB代理。如果面试官问起“为什么Spring Boot默认用CGLIB”你可以回答避免因为没实现接口而无法生成代理的坑同时CGLIB的性能在现代JVM和字节码优化下已经很接近JDK代理默认选择它更省心。4.3 代理失效的经典场景自调用问题AOP面试里还有一道非常经典的高频追问同一个类内部调用另一个方法AOP会不会生效答案是不会。原因是Spring AOP的代理机制建立在“调用方持有的是代理对象”这个前提下。当你从容器里拿到Service时实际上拿到的是代理对象但代理对象的内部this指向的还是原始对象。如果你在methodA()里直接this.methodB()这个调用走的是原始对象的方法地址绕过了代理AOP当然不会拦截。这在事务场景中最常见。比如一个Service类里有方法A和方法B方法A没有事务方法B标注了TransactionalA内部调用了B。看起来B被事务保护了实际上Spring生成的代理只会对容器外的调用生效A调B时并没有经过代理对象所以B上的事务注解完全不会起作用。第一次遇到这个坑的人排查事务不生效问题时往往会耗费很多时间。推荐的解决方案有三个一是把B抽到另一个Service类中让A通过注入的Bean来调用B这样走的就是代理对象二是把事务切点放到方法A上让A直接开启事务让B作为内部调用的普通方法执行三是在A内部通过ApplicationContext.getBean(Class)拿到代理对象再调B或者用AopContext.currentProxy()获取当前代理前提是要开启exposeProxy属性。我个人最推荐第一种职责拆分清晰也符合领域设计。遇到这种问题时面试官想考察的是一个开发者在实际调参过程中对代理机制的理解程度。你如果能直接说出“内部方法调用不会走代理”的核心原因并举出事务失效的例子就非常加分。5. Spring事务管理从隔离级别到失效场景全解5.1 声明式事务的原理代理AOPSpring的事务管理分为编程式事务和声明式事务。编程式事务就是自己用TransactionTemplate或者PlatformTransactionManager的API在代码里显式地begin、commit、rollback。声明式事务则是用Transactional注解让Spring通过AOP在目标方法执行前后自动处理事务的开闭回滚。面试中一般不会要求你手写编程式事务代码但一定要理解声明式事务的原理Spring通过TransactionInterceptor这个AOP增强器在Transactional标注的方法上生成代理。方法调用前开启事务方法执行正常返回则提交事务方法抛出运行时异常或者特定受检异常可通过rollbackFor指定则回滚事务。这个事务增强器就是AOP的一个具体应用场景所以回答这个问题的思路和AOP那部分是相通的。有个容易混淆的细节是Transactional只对RuntimeException和Error默认回滚对受检异常默认不回滚。很多新人写代码时在方法里抛了一个IOException或自定义受检异常结果事务提交了都不知道为什么。记住解决办法在Transactional注解上显式写rollbackFor Exception.class让它对所有异常都回滚。5.2 事务传播行为什么时候用哪个事务传播行为解决的是“当方法B在方法A的事务中被调用时B应该怎么处理事务”的问题。Spring定义了七种传播行为但面试频率最高的只有三种REQUIRED、REQUIRES_NEW、NESTED。REQUIRED是默认值如果当前没有事务就新建一个事务如果当前已经有事务就加入当前事务。这是最常用的行为比如一个业务编排方法调用多个Service方法期望它们都在同一个事务里任意一个失败整体回滚。REQUIRES_NEW不管当前有没有事务都开启一个新事务。如果当前已有事务先挂起外层事务新事务执行完后恢复外层事务。这个适合的场景是“内部操作必须独立成功或失败不能被外部事务牵连”。比如下单后要记录一条独立的审计日志即使订单失败了日志也要留下这时日志记录方法用REQUIRES_NEW。NESTED如果当前有事务在嵌套事务中执行可以独立回滚到保存点但最终提交还要看外层事务。它和REQUIRES_NEW的区别在于NESTED不是真正独立的事务它依赖于外层事务只是内部出错时可以回滚内部已经执行的SQL不影响外层的整体方向。面试中能把这个差异说清楚绝对是一个亮点。此外还有SUPPORTS支持当前事务没有就以非事务方式执行、MANDATORY强制要求已有事务、NOT_SUPPORTED以非事务方式执行挂起当前事务、NEVER以非事务方式执行如果存在事务则抛异常。这些了解即可实际用得很少。5.3 事务失效的十大坑点事务失效是真实项目里最容易踩的坑面试官也很喜欢拿这些场景来考察候选人的项目经验。我整理了几个最高频的失效场景你可以对照自查。Transactional注解加在非public方法上。Spring的代理机制决定了它只能拦截public方法如果标注在protected、default或者private方法上注解会被忽略且不会报错。这属于静默失败特别难排查。同类内部方法自调用。前面讲AOP自调用问题时已经提到方法A调用同类方法B时B上的Transactional不会生效。解决办法要么把B拆到别的Bean里要么把事务加到A上。方法被final修饰。CGLIB代理通过生成子类来覆盖方法如果方法是final的子类无法重写代理自然无法织入事务逻辑。这种情况即便使用接口的JDK代理也不太行因为增强逻辑仍然依赖代理链。方法抛出的异常被内部catch掉了。事务代理只能在异常抛到切面时才触发回滚如果方法内部把异常吞掉Spring根本感知不到自然不会回滚。这也是最常见的“事务没生效”背后的真实原因。方法抛出受检异常且rollbackFor没设置。Spring默认只对RuntimeException和Error回滚受检异常需要显式指定rollbackFor。实际情况中很多人用自定义业务异常但继承的是Exception直接把默认行为踩中。数据库存储引擎不支持事务。比如MySQL的MyISAM引擎不支持事务无论你怎么配置都没用。排查问题时先确认表引擎是InnoDB。这个坑在老旧项目里会碰到。事务管理器没有正确配置。Spring Boot默认基于DataSource自动配置事务管理器但如果你用了多数据源默认的事务管理器可能绑定到了错误的DataSource上导致事务不生效或者管错了库。Bean没有被Spring管理。如果类没有加Service、Component之类的注解或者通过new关键字直接创建了对象Spring根本不认识它Transactional自然也不会有任何效果。Transactional加到了接口或实现类上但方法签名不匹配。Spring支持注解加在接口方法或实现类方法上但要注意如果只在接口上标注而实现类又重写了方法且没有加注解部分情况下注解会失效或产生混淆。建议直接标注在实现类方法上最直观。使用了TransactionTemplate又把业务逻辑写在回调之外。少数人混用编程式事务和声明式事务导致事务边界混乱。这种情况不常见但真出现了排查起来也很头疼。上面这十条并不是让你在面试时全部背出来而是挑两三个结合自己的项目经历讲效果最好。比如你说“之前我们线上遇到过一次事务不生效排查发现是方法内部自己catch了异常后来我们统一规范了异常处理”这种带有真实故事感的回答比罗列知识点强得多。5.4 隔离级别与锁的配合事务隔离级别在面试中出现的频率也不低但相比失效场景这个问题更偏数据库理论。Spring通过Transactional的isolation属性可以指定五种隔离级别DEFAULT用数据库默认、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE。面试时除了要能说出四种隔离级别对应的脏读、不可重复读、幻读问题最好还能结合实际项目说一下为什么MySQL默认用REPEATABLE_READ而很多互联网公司却在业务层选用READ_COMMITTED。这个问题不要求标准答案只要你能说出两种方案的取舍就可以MySQL默认的REPEATABLE_READ是基于MVCC实现的对读多写少的业务很友好但READ_COMMITTED可以降低某些间隙锁引发的死锁概率在更新频繁的高并发场景中反而更稳。事务和锁是密不可分的面试中还可能被问到“悲观锁和乐观锁各适合什么场景”。悲观锁适合并发冲突概率高的场景比如库存扣减乐观锁通过版本号或CAS实现适合并发冲突概率低、追求性能的场景。能把这个话题和事务隔离级别串起来讲又比单纯背定义有层次感。6. Spring Boot自动配置与启动流程从“约定大于配置”说起6.1 为什么Spring Boot能省掉那么多配置Spring Boot最大的卖点是“约定大于配置”。传统Spring项目要配置一大堆XMLSpring Boot让大部分配置自动化完成。让我解释它的实现原理SpringBootApplication注解由三个注解组合而来——SpringBootConfiguration、EnableAutoConfiguration、ComponentScan。其中EnableAutoConfiguration是灵魂它内部通过Import引入了AutoConfigurationImportSelector这个Selector会扫描整个classpath下所有jar包里的META-INF/spring.factories文件把所有标注了自动配置的类加载进来再根据条件注解判断哪些配置真正生效。所谓“根据条件注解判断”指的是ConditionalOnClassclasspath里有没有某个类、ConditionalOnMissingBean容器里是否缺少某个Bean、ConditionalOnProperty配置项是否匹配等。比如你引入了Redis的starterclasspath里有了RedisTemplate相关的类Spring就自动帮你创建RedisTemplate的Bean你引入了Web的starter内置的Tomcat和DispatcherServlet就自动就位。这套机制让“引入依赖即获得能力”成为可能。面试里如果只答到这里算及格但如果你想讲得更深可以补充一句spring.factories在Spring Boot 2.7开始部分被AutoConfiguration.imports文件替代在Spring Boot 3.x中自动配置的注册改用了META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。这个细节能让面试官意识到你关注过新版本的变化而非停留在古老的教程上。6.2 自动配置的条件注解与顺序问题条件注解是自动配置的核心面试官很喜欢问“你想让某个自动配置只在特定条件下生效应该怎么做”。这类题考的其实就是对Conditional系列注解的掌握。常用的条件注解有ConditionalOnClassclasspath中存在指定的类才生效。ConditionalOnMissingClassclasspath中不存在指定的类才生效。ConditionalOnBean容器中存在指定的Bean才生效。ConditionalOnMissingBean容器中不存在指定Bean才生效常用于允许用户覆盖默认配置。ConditionalOnProperty配置文件中存在指定的属性且值匹配才生效。ConditionalOnWebApplication当前应用是Web应用才生效。这里有个面试问题经常出现当ConditionalOnBean和ConditionalOnMissingBean出现时为什么有时候自动配置的顺序会影响结果答案在于条件注解的评估时机。部分条件是在容器已经注册了大量Bean之后才评估的所以自动配置类的特定顺序通过AutoConfigureBefore、AutoConfigureAfter、AutoConfigureOrder控制会影响最终Bean是否创建。这条知识比较深但确实属于“源码级理解”的加分点。6.3 手写一个自定义Starter的思路面试中偶尔会冒出一个题如何写一个自定义的Spring Boot Starter。这道题考的是对自动配置机制的综合理解如果你有相关经验讲起来会非常加分。一个Starter本质上就是一个普通的Maven模块里面包含两部分一是自动配置类二是AutoConfiguration.imports文件或者老版本的spring.factories。自动配置类里定义各种Bean方法用条件注解控制生效范围。此外还要提供一个属性类比如ConfigurationProperties(prefix my.starter)来绑定配置项让使用方可以在application.yml里自定义行为。我举一个最简的例子。假设你要做一个短信发送Starter核心类大概是AutoConfiguration EnableConfigurationProperties(SmsProperties.class) ConditionalOnClass(SmsSender.class) public class SmsAutoConfiguration { Bean ConditionalOnMissingBean public SmsSender smsSender(SmsProperties properties) { return new SmsSender(properties.getAk(), properties.getSk()); } }然后把这个类注册到META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件里写上类的全限定名即可。使用者引入starter坐标再在application.yml里配置my.starter.ak和my.starter.sk就自动拥有了发送短信的能力。能把这个讲清楚说明你对Spring Boot的自动配置有第一手的实操经验而不是只会在网上看别人写的总结。6.4 Spring Boot启动流程的完整时间线另一个高频面试题是“Spring Boot启动时都做了什么”这题考察的是对ApplicationContext启动过程的理解回答时可以按启动顺序逐步拆解。整体流程可以分成三个阶段准备阶段、刷新阶段、收尾阶段。准备阶段SpringApplication.run()被调用后先确定应用类型Servlet Web、Reactive Web、非Web然后加载所有ApplicationContextInitializer和ApplicationListener准备环境变量Environment最后创建ApplicationContext默认是AnnotationConfigServletWebServerApplicationContext。刷新阶段调用context.refresh()这个方法是Spring容器启动的核心里面包含十几个步骤。大家常听到的invokeBeanFactoryPostProcessors处理BeanFactoryPostProcessor、registerBeanPostProcessors注册BeanPostProcessor、initMessageSource初始化国际化资源、onRefresh创建内嵌Web服务器并启动都在这阶段执行。收尾阶段容器刷新完成之后Spring Boot会触发ApplicationRunner和CommandLineRunner可以做数据初始化然后对外发布ApplicationReadyEvent标志应用启动成功。面试时可以加一句自己曾经排查过启动慢问题用到--debug模式查看自动配置报告这种经验细节会让回答更生动。7. Spring Cloud Alibaba微服务体系不会也要知道方向7.1 从大厂需求看微服务的考察趋势现在很多Java岗位的JD里会写着熟悉Spring Cloud而国内公司多数采用Spring Cloud Alibaba体系所以Spring Cloud Alibaba的面试题占比也在上升。代表性的组件包括Nacos注册中心与配置中心、OpenFeign声明式HTTP客户端、Sentinel流量治理、Seata分布式事务、GatewayAPI网关、RocketMQ消息队列等。如果你没有真实落地经验但被问到相关话题可以重点讲对几个核心问题的理解注册中心是什么、为什么需要服务发现与配置管理、服务之间如何调用Feign、熔断降级怎么做Sentinel、分布式事务怎么处理Seata。这些属于微服务领域的公共知识不会因为没踩过坑就完全不能聊。7.2 Nacos和Feign的高频考点Nacos往往被问到两个点一是和Eureka、Consul等注册中心的对比二是配置中心的使用场景与动态刷新机制。动态刷新这块可以讲RefreshScope刷新Bean的作用原理它会将Bean缓存清除并在下一次获取时重新创建从PropertySource中读取最新的配置。Feign则是“声明式服务调用”的代表考的是它的工作流程通过动态代理生成接口的实现把方法调用转换成HTTP请求再由底层HTTP客户端如OkHttp或HttpClient发送出去对接服务端负载均衡内置Ribbon或者Spring Cloud LoadBalancer。面试中常问的一个细节是Feign的日志级别配置与超时设置这些操作可以直接照着配置文档写出来。7.3 从单体到微服务的演进思考面试官有时会问“你们项目为什么拆微服务拆了之后遇到什么问题”。这类开放题没有标准答案但你要能表述清楚拆分的动机和代价。比如原来的单体架构维护成本上升团队协作互相阻塞发布频率受限引入微服务后每个团队可以独立开发部署技术栈选型更自由。但代价是分布式带来的新问题网络延迟、服务间调用失败、数据一致性、链路追踪、配置管理复杂化。如果你的项目恰好经历过这种演进把真实踩过的坑讲出来最好如果没有就讲理论知识框架态度要诚恳不要编造项目经历。8. Spring Security与Spring AI面试中的加分方向8.1 Spring Security的常见问题Spring Security在面试中不算必考但如果你简历里写了认证授权相关的功能那一问一个准。常考的点是认证流程登录请求经过UsernamePasswordAuthenticationFilter提取用户名密码封装成Authentication对象交给AuthenticationManager由AuthenticationProvider做校验通常会调用UserDetailsService加载用户信息用PasswordEncoder比对密码成功后生成认证信息写入SecurityContextHolder后续请求通过过滤器链识别认证状态。授权相关的考点集中在方法级别的PreAuthorize和Secured注解以及常见的配置主题。在Spring Boot 3里基于SecurityFilterChain的Lambda配置风格已经取代了旧的WebSecurityConfigurerAdapter如果你还在用老教程里的写法面试时容易露怯。8.2 Spring AI和AI Agent方向该怎么答随着AI应用爆发Spring AI在面试中也越来越常见相关热搜词里就能看到大量关于spring ai的内容。Spring AI的目标是把AI能力集成到Java应用里提供统一的接口对接各类大模型供应商帮助你快速开发RAG、Agent等应用。面试中常见的问题包括Spring AI的核心概念Model、EmbeddingModel、VectorStore、ChatClient等、RAG检索增强生成的实现思路、如何用Spring AI开发简单的Agent、以及Spring AI Alibaba这个国内分支的进展。如果你面试岗位明确要求AI相关能力至少要能讲清楚RAG的架构文档切分Splitter- 向量化EmbeddingModel- 存储与检索VectorStore- 提示词组装Prompt- 模型生成ChatClient并回答如何缓解大模型幻觉问题通过检索增强、Prompt约束、答案引用来源等方式。在Spring Boot项目中引入Spring AI的starter然后配置模型Key5分钟就能跑起一个最小的对话接口上手成本比想象中低。能现场演示这种最小实现面试官通常会很满意。8.3 我建议准备的AI方向面试话术如果你确实没做过Spring AI不要硬装。可以把话题引向自己熟悉的领域比如“我了解Spring AI的基本思路也看过官方示例目前在项目中主要负责某块核心链路对AI侧的集成可以快速上手”。这种坦诚加学习能力的表达往往比背几个名词更可信。面试官更看重的是你的基础功底和快速学习能力AI方向本身迭代极快岗位匹配度往往可以通过短期冲刺弥补。9. 面试实战问答速查表我把Spring面试中最常见的问题连同标准回答框架整理成一个速查表方便你临考前快速过一遍。注意这不是让你背答案是让你当作“自测清单”看到问题先自己在心里回答再看我给的要点。问题回答要点加分细节什么是IOC控制权反转容器管理对象的创建与依赖对比硬编码new的痛点说明DI是IOC实现方式Bean生命周期实例化、属性填充、初始化、销毁按顺序说出Aware、BeanPostProcessor、PostConstruct、InitializingBean循环依赖如何解决三级缓存能区分构造器注入与属性注入能解释三级缓存和AOP的关系AOP底层原理JDK动态代理与CGLIB能说明Spring Boot 2.x默认CGLIB能讲代理失效场景事务为何失效代理失效、异常被吞、rollbackFor未配置等结合真实项目例子讲别光背书事务传播行为REQUIRED、REQUIRES_NEW、NESTED每个传播行为配合一个业务场景例子Spring Boot自动配置原理EnableAutoConfiguration 条件注解了解Spring Boot 3.x中AutoConfiguration.imports机制如何自定义Starter自动配置类 注册文件能现场说清楚核心代码结构Spring Security认证流程过滤器链 AuthenticationManager能说出Spring Boot 3迁移要点Spring AI怎么用统一模型接口 RAG Agent能讲清楚RAG流程和向量库选型10. 终极建议面试是“讲道理”不是“背答案”Spring面试题准备到最后我想强调一个核心心法面试官考核的不是你背诵了多少知识点而是你在遇到一个具体问题时能不能讲清楚它背后的原理并给出对应的处理思路。所以我的建议是每个高频考点都试着用“是什么 - 为什么需要 - 底层怎么实现 - 实际项目中怎么用 - 遇到问题怎么排查”这个五段式结构去组织你的回答。这比单纯背一堆定义更有说服力也更符合“资深从业者”的身份画像。面试的时候安安心心把常规问题吃透比押偏门怪题更稳妥。这里没有捷径就是一遍遍对着问题训练自己的组织能力。最后再分享一个小技巧面试前可以自己开一个空项目把提到的核心机制比如三级缓存、动态代理、事务传播行为用代码去验证。我这几年来带过的人里凡是亲手验证过这些机制的面试表现普遍比只看文档的人扎实得多。毕竟Spring这类框架看懂源码是技术能讲明白才是本事。
返回列表