上周刚陪一位朋友复盘完他在某大厂的后端面试全程,回家路上他问我一句话把我问住了——“面试官问的问题我全在八股文里见过,但实际答起来才知道完全是两回事。”这让我想把这轮面试的完整问答记录整理出来。面试一共三轮,每轮40到60分钟,第一轮抠Java基础和并发,第二轮钻进Spring源码,第三轮是一道完整的微服务业务场景设计题。没有一道题是背完八股文就能直接答好的,但在复盘每一轮问题的答法之后,你会发现这些题目背后有一条很清晰的考察逻辑。这篇文章不追求教你怎么背题,而是把面试官追问的链路、候选人的回答思路、以及每个问题背后的真实考察点完整还原出来。无论你是准备校招、跳槽,还是自学Java想进大厂,这份实录都值得收藏。
1. 面试全貌与考察逻辑
1.1 这场面试的基本盘
岗位是某电商中台部门的Java后端研发,面试官配置是小组Leader加两位资深工程师,三个人的分工很明确:小组Leader负责整体技术判断和场景设计,两位资深工程师一个主攻Java基础和并发,一个专盯Spring和微服务。面试采用的是典型的“对话式追问”风格,不是让你背完一个知识点就换下一道,而是你每答完一层,面试官紧接着向下挖一层,直到挖到你知识的边界为止。
这种风格在大厂非常常见,它考察的并不是某个孤立的知识点,而是你对技术栈有没有形成完整的认知闭环。举个例子,如果你说“我知道HashMap的结构”,面试官不会就此打住,他会连续问“那红黑树和链表的分界线在哪”“为什么阈值是8”“扩容时JDK8怎么解决死循环问题”“头插法和尾插法的影响是什么”,一连串问题走下来,你有没有真正读过源码、有没有在实践中碰到过坑,基本半小时内就能暴露无遗。
1.2 三轮面试各自考察什么
第一轮以Java核心为主,覆盖集合、并发、JVM三个模块,考察的是基本功和代码功底。第二轮是Spring框架深水区,重点放在Spring源码级别的问题上,比如三级缓存、Bean生命周期、动态代理,这一轮能直接用源码展开回答的候选人,往往会被判定为“有源码阅读习惯”。第三轮则是一道完整的微服务业务场景设计题,需要候选人自己画方案、做技术选型、回答“为什么这么选”,面试官还会不断往里加约束条件,直到把方案“压垮”为止。
三轮考察的重心其实可以归纳成一句话:基础决定你是否合格,源码决定你是否优秀,场景题决定你能否扛起实际业务。很多人只刷八股文,能过第一轮却倒在了第二轮和第三轮,原因就是八股文只覆盖了“是什么”,没有覆盖“为什么”和“怎么办”。
2. 第一轮:Java核心与并发基础实录
2.1 开胃菜——ArrayList与HashMap的细节追问
面试官的第一题很常规:ArrayList和LinkedList有什么区别?候选人轻松回答完数组和链表的区别之后,面试官立刻追问了一句,平时在什么场景下你会选择LinkedList?
这个问题开始有味道了。候选人如果只说“查询多选ArrayList,插入多选LinkedList”,那就踩坑了,因为实际上在绝大多数业务场景里,LinkedList并没有明显的插入优势,它每个节点还要额外存储前后指针,内存开销更大。真正的选择逻辑应该是基于数据规模和访问模式来做基准测试,而不是凭概念贴标签。候选人当时答到“在大数据量随机插入的场景下LinkedList仍然要遍历到指定位置,所以未必快”这一层时,面试官明显点了头。
第二个问题是HashMap的连环追问,这是Java面试当之无愧的必考之王。面试官问:JDK8中HashMap做了哪些改动?候选人的回答拆成了四点:第一,底层结构从数组加链表变成数组加链表加红黑树;第二,修改了扩容时的插入逻辑,从头插法改成尾插法;第三,引入了延迟初始化,构造时不再初始化数组;第四,红黑树在节点数降回6时退化为链表。
面试官显然不满意这个“背课文”式的回答,紧接着追问:为什么链表转红黑树的阈值是8而不是10或者6?这个问题把候选人卡住了十几秒。实际上,源码注释里给出的解释是,基于泊松分布,负载因子为0.75的情况下,链表长度达到8的概率已经极其低,约是千万分之六,所以把8作为转树阈值是时间和空间上的一个平衡点。同时6作为退化阈值,是为了避免元素在7和8之间频繁增减时反复进行树化和退化,留出缓冲区间。候选人把这条逻辑答出来之后,面试官才放过了他。
2.2 线程池的五个参数背后的设计考量
线程池是并发模块的常客。面试官直接抛出一个场景:我有个接口,平均耗时200毫秒,峰值QPS是800,你会怎么设置线程池参数?
这其实是一道典型的“动态规划”题,候选人需要先理解线程池参数之间的关系。核心线程数、最大线程数、阻塞队列容量、空闲线程存活时间、拒绝策略,这五个参数不是孤立配置的,它们共同决定了线程池在面对突发流量时的表现。
候选人当时的计算逻辑是,先估算单线程吞吐量,1秒除以0.2秒等于每秒5个请求,要达到800QPS就需要至少160个线程同时工作。但这个数字显然过大,于是候选人补充说,实际场景中线程数还要结合CPU核数和任务类型来调整,IO密集型任务通常设置为CPU核数的两倍左右,然后利用有界队列缓冲峰值,再配合CallerRunsPolicy拒绝策略来兜底。
面试官对计算过程表示认可,但追问了一个更深的点:如果核心线程数是10,最大线程数是20,队列容量是100,现在同时来了150个请求,会发生什么?这个问题的考点在于线程池的工作原理顺序:先创建核心线程执行任务,核心线程满后任务进入队列,队列满后才创建非核心线程直到最大线程数,再超过就触发拒绝策略。所以150个请求中,前10个被核心线程处理,接下来100个进入队列,此时队列已满,剩余40个请求会触发非核心线程创建,但最大线程数只有20,意味着最多再创建10个非核心线程,最终还有30个请求被拒绝策略处理。这个链条答清楚了,面试官才确认候选人不是只会背参数。
2.3 一个被反复追问的并发题:如何等待所有线程执行完成
面试官在这里出了一道非常日常的题:业务里有三个任务需要并发执行,全部完成之后才能继续后续流程,你会怎么实现?
候选人第一反应回答CountDownLatch,面试官紧接着问:那如果其中一个任务执行异常抛出了RuntimeException,你的主线程会怎样?
这个问题直接把问题从“会用工具”拔高到“理解工具边界”。CountDownLatch的countDown方法在finally块里执行时,可以保证计数减到零,主线程await返回,但子线程的异常并不会自动传递回主线程,如果没做处理,异常就被吞掉了。候选人此时补充了两种改进方案:第一种是使用Future,通过Future.get()获取结果,异常会被包装成ExecutionException抛出,主线程能感知失败;第二种是CompletableFuture,用allOf加exceptionally组合,可以支持更复杂的异步编排。
面试官最后加了一个要求:所有子线程必须“同时”开始执行,而不是一个接一个启动,你会怎么做?这就涉及CyclicBarrier或者自定义门闩了。候选人给出了一个简单实现,用CyclicBarrier让三个线程在启动后先互相等待,等三个线程全部就绪后再同时开始执行任务,这道题才算完整答完。整条追问链条下来,实际上覆盖了CountDownLatch、CyclicBarrier、Future、CompletableFuture四个并发工具的使用边界和底层差异。
2.4 一轮结束前的加试题:冒泡排序的变体
在Java基础部分收尾时,面试官问了一道看似简单的算法题:手写一个冒泡排序。候选人很快写完了,但面试官紧接着加了个限制条件:如果这一轮遍历中没有发生任何交换,能不能提前结束排序?
这个问题的本质是让候选人理解冒泡排序的优化空间。通过增加一个boolean标记位,如果某轮遍历未发生任何交换,说明序列已经有序,可以提前终止外层循环,将最好情况的时间复杂度从O(n²)优化到O(n)。候选人补上了这段优化代码后,面试官又问了一句:为什么实际业务中没人用冒泡排序?这就回到了算法选型的问题,因为数据量稍大时O(n²)的复杂度完全不可接受,工程上排序都会走JDK的Arrays.sort或Collection.sort,底层是Dual-Pivot快速排序和TimSort的混合实现,工程师更多要理解的是排序稳定性、时间复杂度和不同数据分布下的表现,而不是重复造轮子。
第一轮结束后的整体感觉是:题目本身都不偏门,但每一道题都有连环追问,面试官会在你回答的每个关键词语上打一个问号,然后一路追下去。这种考察方式对基础扎实的候选人很友好,因为你能通过回答把知识体系整个铺开;但对只背结论的候选人来说则是一场灾难。
3. 第二轮:Spring框架深水区实录
3.1 三级缓存与循环依赖:面试官最爱的连环追问
第二轮一上来,面试官就没留任何缓冲,直接问:Spring容器是如何解决循环依赖的?请结合三级缓存的结构说明。
这个问题在大厂后端岗的出场率极高,因为它能同时考察候选人对Spring源码的熟悉程度、对设计模式的敏感度,以及对“Bean创建流程”的整体理解。候选人的回答需要先把三级缓存的结构讲清楚:一级缓存singletonObjects保存已经完整初始化的单例Bean,二级缓存earlySingletonObjects保存已经实例化但尚未完成属性填充的Bean,三级缓存singletonFactories保存的是ObjectFactory工厂对象,用于提前暴露Bean的引用。
最关键的问题是:为什么需要三级缓存,而不是两级?如果只有一级缓存,Bean在实例化后直接放入缓存,那其他Bean依赖它时拿到的是一个尚未初始化完毕的半成品,后续属性填充和初始化操作就无法继续。如果只有二级缓存,则必须在实例化后立刻创建代理对象放进去,问题在于这样无法确定这个Bean是否需要代理,因为AOP的通知器此时通常还没有完全应用。三级缓存的最大价值在于“延迟决策”,ObjectFactory本身不直接返回对象,而是等到真正出现循环依赖时,才通过getEarlyBeanReference方法去判断是否需要生成代理,这样就避免了无脑提前代理带来的性能浪费和逻辑错误。
候选人当时就着这个问题把流程完整讲了一遍:A创建时发现依赖B,于是先去三级缓存找B对应的ObjectFactory,触发B的创建;B创建时发现依赖A,这时A已经实例化完成并把自己的ObjectFactory放进了三级缓存,B通过getEarlyBeanReference拿到了A的早期引用,完成属性填充后B走完初始化流程并进入一级缓存;之后A拿到B的完整引用,继续走自己的属性填充逻辑。这个双向依赖的完成过程,实际上依赖的是三级缓存提前暴露引用和ObjectFactory延迟代理两个机制的组合。
面试官显然不会停在“标准答案”上,他马上追问:如果A使用构造器注入,循环依赖还能解决吗?候选人回答不能,因为构造器注入在实例化阶段就会触发依赖查找,此时对象还没创建出来,三级缓存里根本没有对应的ObjectFactory。然后面试官又问:如果是多例Bean(Prototype)会怎样?多例Bean不缓存,自然也谈不上循环依赖解决方案,容器会直接抛出异常。这两个追问点回答清楚后,面试官的表情才放松下来。
3.2 Bean生命周期:从实例化到销毁的完整链路
第二个问题来了:描述一下Spring Bean的生命周期,我想听细节。
候选人从“实例化”说起:Spring容器首先通过构造器或工厂方法创建Bean实例,此时Bean还是一个“光秃秃”的对象,所有属性都是默认值。接着进入属性填充阶段,也就是populateBean方法,Spring会遍历BeanDefinition中的属性值,执行依赖注入,把@Autowired、@Resource等注解标注的依赖通过字段反射或setter方法注入进来。注入完成后,Spring会检查Bean是否实现了各种Aware接口,比如BeanNameAware、BeanFactoryAware、ApplicationContextAware,逐个回调相应的接口方法,把BeanName、BeanFactory、ApplicationContext等信息注入给Bean。
之后的环节是初始化前的BeanPostProcessor调用。Spring的BeanPostProcessor接口有postProcessBeforeInitialization和postProcessAfterInitialization两个方法,前者在init方法之前执行,后者在init方法之后执行。我们常用的@Autowired注解就是AutowiredAnnotationBeanPostProcessor在后置处理方法里完成字段注入的,而AOP的代理对象生成也发生在这里。再往后是InitializingBean接口的afterPropertiesSet方法和XML或注解上配置的init-method方法,这两个属于Bean自身的初始化逻辑。
整个生命周期里最容易被忽略的其实是“销毁”阶段,DisposableBean接口的destroy方法、@PreDestroy注解以及destroy-method配置都会在容器关闭时被调用。候选人最后补充了一句“实际工作中最常接触的是各种BeanPostProcessor的实现,因为Spring Boot的自动配置大量依赖这个机制做增强”,面试官点头表示认可。这个问题的完整回答需要包括实例化、属性填充、Aware回调、BeanPostProcessor前置处理、初始化方法、BeanPostProcessor后置处理、Bean就绪、销毁回调这几个关键步骤,缺一个都不完整。
3.3 Java动态代理:JDK代理与CGLIB的取舍
面试官顺势切入AOP底层:Spring的AOP默认使用哪种代理方式?候选人回答,如果目标类实现了接口,Spring默认使用JDK动态代理;如果没有实现接口,则使用CGLIB代理。Spring Boot 2.x之后,默认代理方式已经被改成强制使用CGLIB。
面试官追问的原因来了:JDK动态代理和CGLIB的区别在哪里?候选人需要从底层机制来分析。JDK动态代理通过InvocationHandler和Proxy类在运行时为接口生成代理类,使用的是反射机制,目标类必须实现至少一个接口。CGLIB则是通过继承目标类,生成目标类的子类,并重写其中的方法实现增强,所以不需要接口,但也意味着final类或final方法无法被CGLIB代理。从性能上看,JDK代理在创建代理对象时更快,因为生成逻辑简单;但在方法调用时,由于走反射链路,性能略逊于CGLIB生成子类后的直接方法调用。不过现代JVM的反射优化效果已经相当好,这个性能差距在绝大多数业务场景里可以忽略。
面试官最后不按常理出牌,问了一个“工程选择问题”:在微服务架构里,Feign客户端默认使用哪种动态代理?这其实是在考察候选人是否真正理解了自己日常使用的框架。Feign底层默认使用JDK动态代理,因为它代理的是FeignClient接口,天然满足接口代理的条件。候选人答到这里时,面试官才开始转向Spring家族的下一个话题。
3.4 Spring Security抢答题:认证与授权的边界
第三个问题转到了Spring Security,面试官的问题是:一个请求从进入系统到完成权限校验,经过了哪些过滤器?
这是一个典型的“全链路”问题,候选人需要从OncePerRequestFilter机制开始回答。Spring Security基于Servlet过滤器链设计,核心链路是:请求先通过SecurityContextPersistenceFilter恢复或创建安全上下文,然后经过LogoutFilter、UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter等认证过滤器,接着是RequestCacheAwareFilter、AnonymousAuthenticationFilter,最后经过ExceptionTranslationFilter和FilterSecurityInterceptor完成授权判断。FilterSecurityInterceptor内部会调用AccessDecisionManager进行投票决策,比较当前用户的Authentication权限和当前请求所需的配置权限,只有通过授权后才真正进入业务控制器。
面试官这里没有继续往深挖,而是换了个场景:微服务场景下服务间调用时的认证怎么做?这就引出了无状态JWT认证和网关统一认证授权的架构设计,也是后面第三轮微服务场景题的一个伏笔。候选人答出“网关负责认证,服务间通过传递令牌或在可信网络内部使用Service Account进行认证”后,面试官很自然地切入了下一个环节。
3.5 第二轮复盘:源码级回答要“点到为止”
第二轮结束后的体会是,Spring源码问题的关键不在于把每行源码背下来,而在于把核心机制讲清楚,同时能够回答出每个设计背后的“为什么”。比如三级缓存解决的是什么问题、为什么需要延迟代理、AOP代理在哪一步织入、BeanPostProcessor在整个生命周期中扮演什么角色。这些点答到了,面试官就能判断你真的读过源码。反过来,如果只是背《Spring源码深度解析》的目录,一被追问到细节就会磕磕巴巴,反而暴露准备不足。
4. 第三轮:微服务业务场景设计实录
4.1 场景题设定:下单链路如何保证数据一致性
第三轮换成了小组Leader亲自来面,他给了个非常具体的业务场景。用户在一个电商App里下单,下单后需要同时完成三件事:扣减库存、给用户增加积分、发送一条站内通知。这三个动作分别由三个独立部署的微服务负责:库存服务、积分服务、通知服务。面试官问:你会怎么设计这个链路,保证数据最终一致?
这道题难在它不是问单个技术点,而是要求你给出一个完整的方案,并且要说明每个取舍的原因。候选人先给出了一个最简单的同步调用方案:订单服务下单成功后,按顺序调用库存服务、积分服务、通知服务,全部成功则订单成功,任何一步失败则订单失败。面试官立即反问:那如果扣库存成功、返积分失败怎么办,用户的钱已经被扣了,但积分没到账,这个账怎么算?
4.2 候选人连续回答的四个递进方案
候选人开始给出第一个优化方向:引入消息队列。下单成功后,订单服务把“扣库存”和“加积分”这两个指令作为消息发送到MQ,由库存服务和积分服务各自消费。消息队列的ACK机制保证了消息至少被消费一次,消费失败会重试,最终达到最终一致。面试官这时插了一句:消息重复消费怎么办?这就要求候选人答出幂等性设计,比如用消息唯一ID做去重表,或让下游服务记录消费状态,保证同一条消息处理多次的结果一致。
候选人继续补充第二个层级:如果库存服务扣减失败了怎么办?这里需要用到事务消息或者本地消息表。以本地消息表为例,订单服务和库存服务在同一个数据库里创建一张消息记录表,扣库存操作和写消息记录在同一个本地事务中完成,然后通过一个定时任务扫描未发送的消息记录,把消息投递到MQ。下游库存服务消费成功后回调确认,订单服务定期清理已确认的消息记录。这套方案虽然多了一张表和一次定时扫描的开销,但换来了很强的可靠性保障。
面试官接着又增加了一个细节:在秒杀场景下,QPS会瞬间冲到几万,你刚才的同步调用和消息表方案都扛得住吗?候选人这时需要调整方案,把“同步扣减”改成“预扣加异步对账”模式。用户秒杀成功后,订单状态先变成“待支付”,同时库存服务先做一次预占,真正支付成功后才进行实际扣减。如果用户超时未支付,预占的库存自动释放。这个方案减少了同步调用的时间窗口,订单服务和库存服务解耦,库存以缓存为主、数据库异步落盘,从而支撑高并发瞬时流量。
最后面试官让候选人画一张完整的架构图,把订单服务、库存服务、积分服务、通知服务、消息队列、数据存储在图上标出来,并说明每个链路上的数据流动方向。这个要求测试的是候选人的全局视野,能不能把方案真正可视化、闭环化。候选人把“同步转异步”“缓存预热”“幂等去重”“对账补偿”四个模块全部画出来后,面试官开始抛出最后一个压轴题。
4.3 压轴题:单节点K8s上的微服务如何做到不停服迁移
面试官此时抛出了一个非常贴近实战的场景:假设我们现在有一套若依微服务架构,部署在一个单节点的Kubernetes集群上,MySQL、Redis、Nacos、Gateway、各个业务服务全部在里面。现在要把它迁移到云平台ECS集群上,要求不停服、不丢数据,迁移完成后还要用JMeter脚本做高并发压测验证承载能力。你会怎么制定迁移方案?
这个问题融合了热词里出现的“单节点K8s”“若依微服务迁移”“不停服不丢数据”“JMeter压测”这些关键词,直接考察候选人的迁移实操经验。候选人的回答拆成了五个阶段。
第一阶段是环境准备。在云平台ECS上准备一套新的Kubernetes集群或重新编排Docker Compose环境,把Nacos、Redis、MySQL、Gateway等基础组件先拉起来,业务服务暂且不启动,只保证基础设施可用。这个阶段的核心是“新环境先独立跑通”,不能一上来就动数据。
第二阶段是数据同步。单节点K8s里的MySQL数据通过binlog增量同步到云上MySQL,Redis通过持久化文件加增量同步的方式进行镜像。数据同步要保证不丢数据,就需要从全量导出加增量追平开始做,等两边数据追平后进入短暂的业务暂停窗口,但要求不停服,所以实际操作里更多是采用双写或者短时只读切换。
第三阶段是流量切换。在域名解析或网关层做灰度切换,先把少量测试流量指向云上新环境,验证Nacos服务注册、配置读取、鉴权链路是否正常。确认没问题后,逐步放量,先从10%切到50%,观察监控指标,再切到100%。这个过程里,如果新环境出现异常,立即通过网关路由把流量切回旧环境,相当于用网关做了一个流量开关。
第四阶段是数据校验。全量切换后,需要对比新旧环境的数据,尤其是订单、用户余额、积分这类强一致数据,确认新环境写入的数据和旧环境完全一致。这里需要写对账脚本,定时比对两边的数据总量和关键业务字段,不一致的再单独补偿。
第五阶段是压测验证。迁移完成后,按面试官的要求,由压测人员使用配套JMeter脚本做高并发测试。候选人这时需要回答出压测的核心指标:QPS、响应时间P95/P99、错误率、CPU和内存使用率。压测前要设计好线程组、Ramp-Up时间、循环次数和断言规则,压测中要盯监控曲线,如果出现错误率上升或响应时间恶化,要能快速定位是新环境资源不足、代码性能问题还是中间件配置问题。
这五阶段方案讲完后,面试官的表情已经说明一切。他没有继续追问技术细节,只是补充了一句:你这个方案把迁移和压测闭环了,这正是我们团队最近在推进的事情。
4.4 微服务间调用方式的选型对比
在这个环节里,面试官插了一个相对基础的选型题:微服务之间,你会选择什么方式做远程调用?候选人需要把常见的几种方式梳理清楚。传统Spring Cloud项目里,Feign是基于HTTP协议的声明式REST调用,优点是简单直观、适合跨语言异构系统,缺点是HTTP协议开销相对较高;Dubbo是RPC框架,基于TCP长连接和二进制定制协议,性能更好,适合内部服务间高吞吐调用,但引入了服务框架的强约束;如果使用响应式编程,WebClient则是不错的选择,通过线程模型优化和背压机制提升资源利用率。
候选人最终的结论是,选型要看场景,没有绝对最优。对外暴露的API网关统一走HTTP协议,内部服务间如果追求性能且统一使用Java技术栈,可以考虑Dubbo;如果团队以快速迭代为主、对性能不是极致敏感,Feign加服务熔断降级方案则更实用。这种“按场景选型”的回答方式,通常比直接说“我用Feign”更能给面试官留下好印象。
4.5 第三轮复盘:业务场景题考的是“边界感”
三轮面试结束后,我帮朋友做了一次复盘。第三轮真正要考察的其实不是你会不会用某个组件,而是你对一个复杂业务“边界感”的把握。你要知道自己设计的方案在什么条件下会失效,要在方案里主动加入幂等、补偿、对账、监控这些兜底能力,而不是寄希望于每个调用都百分百成功。这种能力只能通过在实际项目里踩坑积累出来,八股文里不会告诉你:真正到了线上,消费者会重复消费、RPC会超时、事务消息会发送失败、缓存会穿透,你要做的不是回避这些问题,而是给它们设计兜底方案。
5. 面试后必须做的复盘动作
5.1 大厂Java面试的高频失分点
复盘完这三轮面试后,我总结出几个大厂面试里最容易失分的点。
第一个失分点是“只背结论不解释过程”。比如被问到HashMap为什么阈值是8时,直接说“源码是这么写的”,却说不清概率分布和空间时间平衡,面试官会判定你只知道表面。第二个失分点是“掌握了组件但不了解边界”,比如知道用消息队列最终一致性,但说不清消息丢失、重复消费、消息堆积分别怎么处理,这就是没有经历过真实生产环境。第三个失分点是“有一套方案但不知道为什么要这么选”,比如在方案里用了Redis做缓存,但被问到为什么不加缓存而要用本地缓存时答不上来,说明技术选型没有经过深思熟虑。
针对这些问题,我的建议是准备阶段不要把精力全部花在背题上,而是围绕一个核心业务场景,把从接入层到数据层的完整链路亲自搭建一遍。你可以用Spring Boot加Spring Cloud写一个最简单的下单服务,把Feign调用、MQ消息、Redis缓存、MySQL事务全部串起来,然后模拟各种异常场景,看看你的服务会怎么表现。这个过程里踩到的每个坑,都比背十道面试题有价值。
5.2 从八股文到源码阅读的准备路径
最后分享一条我自己的准备路径。第一阶段先掌握Java并发和集合的核心类,重点读HashMap、ConcurrentHashMap、ThreadPoolExecutor、CompletableFuture的源码,把关键方法的实现逻辑画成流程图。第二阶段再读Spring源码,重点看refresh方法、Bean生命周期、AOP代理生成、事务传播机制这几个模块,读的时候不要逐行看,而是先看方法调用链,理解主流程。第三阶段是微服务,Nacos服务注册与发现、Feign调用链路、Gateway路由转发、Sentinel限流降级、Seata分布式事务,每一样都要动手搭一个小项目验证。等到你能把一台电脑上从Nacos到MySQL这条链路完全跑通,你再去面试的时候就会发现,面试官很多问题你都能从自己搭建的项目里找到真实的答案。
我个人在实际操作中有一个习惯:每次面试前,会把所有可能被问到的问题整理成一份按链路串起来的思维导图,比如从“一个HTTP请求到达Spring Boot应用后会经历哪些Filter”开始,一路延伸到Controller、Service、DAO、MyBatis、MySQL,再延伸到Redis缓存、MQ异步、分布式事务。这样面试官无论从哪个点切入,都能顺着链路往上下游扩展。这个方法也推荐给你们,比死记硬背一页页题目要有效得多。