☰
Java面试实战:Spring Boot、微服务与SSE流式输出全攻略
2026/9/30 10:44:12 网站建设 项目流程

最近帮几位朋友做了一轮大厂Java面试的模拟复盘,发现一个明显变化:面试官已经不满足于“背八股”了。Spring Boot、微服务依然是必考底盘,但AI技术相关的追问越来越多,经常一开口就是“你项目里大模型回答是怎么流式渲染的”“客户端点了停止生成你后端怎么处理”。这其实是一件好事,说明市场想要的不是会背题的,而是真在自己项目里把Spring Boot、微服务、AI集成打通过的人。这篇文章是我把这几个月准备面试和帮人复盘时的高频考点,结合具体实操经验整理成的一份实战笔记。内容覆盖Spring Boot启动原理与Bean注入、微服务拆分与分布式事务、基于SSE流式输出封装大模型交互逻辑,最后附一份面试现场的高频问题与排查速查表。无论你是在准备跳槽,还是做业务系统想往AI方向靠,都可以直接拿来当复习主线。

1. Spring Boot:从第一个程序到Bean生命周期,面试官到底在考什么

1.1 “第一个Spring Boot程序”不只是Hello World,而是启动原理

很多人在准备面试时觉得“第一个Spring Boot程序”太简单,随手就能跑起来。但恰恰是这个问题,能最快速地区分“用过”和“懂原理”两类候选人。面试官不会只问“你怎么创建项目”,而是会顺着往下追问:为什么一个main方法就能把Web服务拉起来?内嵌Tomcat是谁启动的?自动配置到底是怎么发生的?

这些问题的根都在@SpringBootApplication这个复合注解上。它等价于@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个注解的组合。@ComponentScan负责扫描当前包及子包下的Bean;@EnableAutoConfiguration通过AutoConfigurationImportSelector加载spring.factories或者AutoConfiguration.imports文件里声明的配置类,按条件注解(@ConditionalOnClass、@ConditionalOnMissingBean)决定是否生效;内嵌Tomcat就是ServletWebServerFactoryAutoConfiguration这个自动配置类在工作,它创建TomcatServletWebServerFactory再启动Servlet容器。整个链路其实和以前Spring手动拼XML配置没有本质区别,只不过把“程序员决定建什么Bean”变成了“框架按条件猜测你需要什么Bean”。

所以我建议每个准备面试的人,一定要把“第一个Spring Boot程序”完整地在源码层面走一遍。最直观的验证方式是:打开META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件看看列表,再打断点看AutoConfigurationImportSelector的getCandidateConfigurations方法。一旦你看过这些类,面试官再问“starter的工作原理是什么”“为什么引入spring-boot-starter-web就有Web环境”,你都能答到点子上。

1.2 Bean注入的“控制权”与循环依赖,别只会用@Autowired

Spring Boot核心考点里,Bean的注入方式几乎必考。很多人的第一反应是@Autowired,但面试官真正想听的,是你对“控制反转”的理解有多深。注入方式本质上有三种:字段注入、Setter注入、构造器注入。字段注入代码写起来最爽,尤其在Controller里一行@Autowired private UserService userService;就完事,但它的问题也很明显:依赖隐藏、不可final、单元测试不友好、类无法真正脱离容器运行。构造器注入能明确表达“这个Bean没有这些依赖就创建不了”,测试时可以手动new并传入mock依赖,所以Spring官方也推荐构造器注入。

比注入方式更常见的是循环依赖问题。A依赖B、B依赖A,在Spring里可以通过三级缓存解决:一级缓存放完整Bean,二级缓存放提前暴露的早期Bean,三级缓存放ObjectFactory。但如果你在项目里遇到过Spring Boot 2.6以后的报错,会发现默认已经禁止循环依赖了,spring.main.allow-circular-references这个配置默认关闭。Spring Boot 3和Spring Framework 6上,缓存策略也在收紧,网上那套“三级缓存包治百病”的旧知识点需要更新。

这里有一个很实的建议:面试时不要上来就背三级缓存,而是先解释“Spring为什么不能直接解决构造器循环依赖”——因为构造器注入要求Bean实例化时就必须保证依赖齐全,A要创建B、B又要创建A,谁都没法先实例化,这是“鸡生蛋”问题。能说出这一层,面试官会立刻觉得你不只是背了面经。

1.3 Spring Boot 3的Security迁移、WebSocket与日志配置

Spring Boot 3和Spring Security 6出来后,网上很多老教程已经不能直接用了,这也成了一个高频率面试点。最典型的是原先继承WebSecurityConfigurerAdapter的写法被彻底移除,现在的标准写法是直接定义SecurityFilterChainBean。配置上从http.authorizeRequests()迁移到http.authorizeHttpRequests(),方法链从and()改成Lambda DSL写法。如果你在项目里迁移过,大概率会遇到权限规则不生效或默认登录页还在的情况,根因往往是忘了csrf和session策略也要跟着配,或者静态资源路径没有放行。

WebSocket也是Spring Boot面试的高频延伸点。面试官常问的不只是“怎么加一个WebSocket端点”,而是“握手拦截器怎么配置、token在哪里校验、STOMP消息代理怎么连RabbitMQ”。如果项目里只用原生WebSocket,yml里配置其实很简单,设置端点路径、设置允许跨域来源、配置SockJS兜底。真正容易踩坑的地方是:WebSocket握手请求不会经过普通Spring MVC拦截器,所以JWT校验必须在HandshakeInterceptor里手动做;另外,用了STOMP后,心跳和断线重连是最容易被忽略的,线上经常是“看起来连着但消息发不出去”。

日志配置在面试中虽然占比不大,但做项目时很实。Spring Boot默认用Logback,你需要知道logging.level、logging.file.name这些基础配置,也要知道自定义logback-spring.xml时用springProfile标签做多环境格式区分。我建议把日志体系单独整理成一篇笔记,因为线上排查大模型接口超时、SSE断连时,日志打得好不好直接决定你定位问题的速度。

1.4 本地缓存Caffeine与缓存设计,Spring Boot里的“加速器”

热词里出现了spring boot caffeine,这也是我近期在面试中被问到较多的小题。Caffeine是高性能本地缓存库,Spring Boot 2.x以后官方推荐用它替代Guava Cache。配置上就一行spring.cache.type=caffeine,再配一个CaffeineCacheManager就能用。但面试问到缓存时,别只答“用Caffeine做本地缓存”。更合理的思路是讨论多级缓存:本地Caffeine存热点数据,Redis存分布式共享缓存,DB兜底。为什么不能全用Redis?因为每次查询都走网络IO,性能不如本地内存快;但本地缓存又存在多实例数据不一致问题。所以常规做法是:一级Caffeine的过期时间设短一点(比如30秒),二级Redis的过期时间设长一点(比如5分钟),再通过MQ广播清理本地缓存。这套方案在商品列表、白名单配置、模型回复缓存这类场景非常实用。

2. 微服务架构:拆分逻辑、分布式事务与框架选型

2.1 Spring、Spring Boot、Spring Cloud,到底有什么区别

微服务面试有一个绕不开的基础问题:Spring、Spring Boot、Spring Cloud到底是什么关系。很多初级开发容易混为一谈。我的讲法是用一个类比:Spring是“地基和建材”,提供IoC容器、AOP、事务管理等基础设施;Spring Boot是“精装修”,通过自动配置和starter直接给你一套能跑起来的房子;Spring Cloud则是“小区管理系统”,负责多栋房子之间的通信、安全、路由和监控。没有Boot,搭建Cloud全家桶会非常痛苦;没有Spring,Boot的底层能力无从谈起。

如果面试官继续追问“微服务架构里Spring Cloud的核心组件有哪些”,你需要能列举:注册中心Nacos/Eureka、配置中心Nacos Config/Spring Cloud Config、网关Gateway、负载均衡LoadBalancer、熔断和限流Sentinel/Resilience4j、OpenFeign声明式HTTP调用、链路追踪Micrometer Tracing/SkyWalking。注意,旧版网课里常用的Zuul和Hystrix在Spring Cloud 2021以后基本处于维护状态,面试中可以说“了解但不用在项目里”,避免暴露技术栈陈旧。

2.2 微服务拆分实战:商城系统的边界到底怎么划

面试里最考验真实水平的场景题,就是“给你一个单体商城系统,你怎么拆微服务”。这个题没有标准答案,但可以约成三步走:先找业务边界,再定接口契约,最后做数据隔离。

第一步,按业务域划分。典型的商城可以拆成用户、商品、库存、订单、支付、购物车、营销、评价等几个中心。划分时有一个判断标准很实用:两个功能模块如果频繁跨库联查,就说明边界划错了;如果某个模块的改动经常牵扯另一个模块一起上线,说明它们应该合并。第二步,定接口契约。每个服务对外暴露API时要有明确的入参出参、限流阈值和版本号,服务之间不允许直接访问对方的数据库表。第三步,数据隔离是拆分中最痛的一步,订单服务不能随便查用户表的全部字段,只能通过用户服务提供的“用户摘要”接口拿数据,这就是服务自治。

拆分时还要警惕“分布式单体”陷阱。很多团队把服务拆出来了,但一次业务操作还是要同步调用七八个服务,最后整出一个“慢速单体”。正确做法是先画清楚“一次下单需要几次服务间调用”,能合并的服务间调用就通过聚合接口合并,能异步的就上MQ,不要迷信服务数量越多越微服务化。

2.3 数据一致性:分布式事务的“常规解法”和面试话术

微服务面试里如果只能押一个深度题,我会押“Java怎么保证数据一致性”。因为单体数据库事务好聊,但拆了服务后,一个订单创建要同时扣库存、加积分、发优惠券,这些分散在不同服务的本地事务里,怎么保证整体要么全成功要么全失败?市面上有几种主流方案,面试时必须清楚各自的代价。

最常用于业务系统的是最终一致性:本地事务 + 消息表 + MQ。下单时先把订单数据和待发送消息写到同一个本地数据库事务里,事务成功后再异步投递MQ,下游订阅消息执行扣库存、加积分。如果MQ投递失败,定时任务扫消息表重投。这个方案思路简单、可用性强,但存在消息重复问题,所以下游消费逻辑必须“幂等”。

更严格的场景用TCC(Try、Confirm、Cancel)或Saga。TCC需要对每个参与方实现三段接口,开发和协调成本很高;Saga适合长流程,通过编排服务依次调各个子任务,失败时反向补偿。在面试话术上,我建议这样组织:先反问业务是不是真的需要强一致性,再给出“默认最终一致性、特殊账户场景才用TCC或Saga”的原则,最后强调“幂等是分布式一致性的基石,接口必须能对同一个请求重复执行不产生副作用”。答出这套逻辑,面试官会认为你有真实架构判断力,而不是只会背方案名。

2.4 开源框架与通信协议:若依微服务Plus、gRPC与服务联调

最近很多人在问若依微服务Plus能不能写进简历。它是一个基于Spring Cloud的快速开发平台,架构上用了Nacos做注册和配置中心、GateWay做网关、Sentinel做流控、Seata做分布式事务、Redis和Mysql存储。如果你是在小公司做完项目没有太多架构设计机会,拿若依这类开源框架作为“二次开发学习样板”完全没问题,但简历上不要写“架构由我设计”,要写“基于若依微服务Plus搭建了XX管理系统,实现了动态权限和XX业务模块”,重点突出业务交付落地的能力。

服务间通信协议也是面试常客。Feign/OpenFeign是同步HTTP调用,开发心智成本低,接口直接按Controller风格写;但大流量、对性能敏感的场景更适合gRPC,它基于HTTP/2和Protobuf二进制序列化,建立连接后长连接复用,性能比JSON + HTTP/1.1好不少。Spring Boot集成gRPC时需要注意版本兼容性,spring-boot-starter和grpc-spring-boot-starter的版本要匹配,否则会碰到服务注册不上、MethodDescriptor找不到之类的问题。

还有一个很实际的细节,就是微服务和系统怎么启动与联调。本地排查问题时不要一次把七八个服务全拉起来,先起Nacos、Mysql、Redis这些基础设施,再按依赖顺序起基础服务、中间层服务、网关服务。联调时建议给每个服务配置独立的bootstrap.yml,用--spring.profiles.active=dev区分环境,并且开Spring Cloud OpenFeign的日志级别为DEBUG,能省下非常多排查时间。

3. AI技术栈:大模型交互逻辑封装与SSE流式输出

3.1 基于什么技术栈封装AI交互逻辑

这两年Java后端接到最多的新需求,就是把大模型能力集成进业务系统。面试里高频出现的措辞是“基于什么技术栈封装AI交互逻辑”,这其实是在考察你有没有完整做过“后端接模型、模型流式返回、前端实时渲染”这条链路。

我的建议是从简到繁分两层。简单方案是直接在Spring Boot项目里引入HTTP客户端(OkHttp、WebClient或RestTemplate),调用模型服务商提供的对话补全接口,把整段回复拿回来后一次性返回给前端。这个方案适合内部工具、一次性问答,但用户体验差,大模型生成可能要几十秒,前端按钮一直转圈,用户流失严重。

进阶方案是封装一个独立的LLMProxy模块,统一管理API密钥、模型名称、温度参数、上下文组装、限流和审计日志。Spring生态里可以直接用Spring AI框架,它提供统一的ChatClient接口,底层可以切换OpenAI、Ollama、Azure OpenAI等,还能用@MessageMapping做流式输出。这块技术栈选型的核心逻辑是:把“与模型服务商的交互细节”隔离在业务代码之外,业务侧只关心“给我一个Prompt、返回一串流”,这样模型厂商换了、接口协议变了,业务代码不用动。

3.2 SSE流式输出原理与实操:从HTTP到EventSource

大模型回答实时渲染,业界标准方案是SSE(Server-Sent Events)。SSE本质上还是HTTP,只是服务端响应头设置Content-Type: text/event-stream,连接保持不关闭,服务端可以多次推送data:开头的文本块。前端通过EventSource对象接收,实现“打字机”效果。

有些人会问既然WebSocket也能做流式输出,为什么大模型场景选SSE。我的答案是:WebSocket是双向全双工协议,适合聊天室、实时协作这类“两端都要频繁发消息”的场景;而大模型场景里,用户提问是一次发完的,之后就是服务端单方向持续吐数据,用SSE更简单,还能自动重连、有标准协议栈支持,不需要额外维护WebSocket连接状态。SSE的缺点是浏览器原生的EventSource只支持GET,如果你想带Authorization请求头,得改用fetch+ReadableStream自己解析流。

Spring Boot实现SSE最简单的方式是返回SseEmitter。流程是这样的:Controller创建SseEmitter,把实例交给一个异步线程,在线程里调用大模型接口拿到流式响应,每收到一段内容就通过emitter.send()推给前端,全部完成后emitter.complete()。不要直接在主请求线程里同步阻塞调用模型,因为这会占满Tomcat线程,高并发下接口很快雪崩。更好的做法是用@Async或者注入业务线程池处理模型调用。核心代码如下:

@RestController @RequestMapping("/api/ai") public class ChatController { private final ChatService chatService; public ChatController(ChatService chatService) { this.chatService = chatService; } @GetMapping(value = "/stream", produces = "text/event-stream") public SseEmitter stream(@RequestParam String prompt) { // 0L表示不超时,Saas项目里也可以设置为60秒或120秒 SseEmitter emitter = new SseEmitter(0L); // 注册回调:客户端断开或超时时释放资源 emitter.onCompletion(() -> chatService.cancelUpstream()); emitter.onTimeout(() -> { chatService.cancelUpstream(); emitter.complete(); }); emitter.onError(e -> chatService.cancelUpstream()); chatService.streamChat(prompt, emitter); return emitter; } }

对应服务层里调用大模型API时,用WebClient的非阻塞方式流式接收,并逐行解析data:内容并推送。要特别注意:SSE中间不能经过会缓冲响应的Nginx或网关,必须关闭缓冲(X-Accel-Buffering: no),否则前端看到的内容会一次性拥过来,根本起不到“流式渲染”的效果。

3.3 abort机制:客户端断连时如何及时取消大模型请求

热词里那句“配合abort”,我觉得是SSE面试里最容易被追问的点。场景是:用户在界面上点了“停止生成”,前端应该取消渲染,后端也必须同步中断大模型请求,否则模型还会继续生成答案,Token照扣,这就是浪费成本。

前端侧用AbortController可以做到。把signal传给fetch,点击停止时调用controller.abort(),浏览器会终止当前请求,连接被关闭。这里有个细节:很多开发者只在前端停了渲染,没有通知后端,后端的SseEmitter可能还在傻等模型返回,或者模型返回后往一个已经断开的连接里写数据。所以后端要注册断连监听,SseEmitter.onCompletion并不是只在正常结束时回调,客户端断开也会触发,你要在回调里调用模型SDK的cancel方法断开与模型服务的连接。如果是自定义HTTP客户端,可以保存当前请求的Future或Disposable,然后在回调里cancel()。

实现abort还有一个常见坑:如果用的是Spring WebClient,流的取消需要拿到Disposable句柄;如果用的是OkHttp3,取消时调用Call.cancel();如果用的是OpenAI SDK,底层请求对象往往提供了cancel()方法。我项目中会专门做一个StreamTaskManager组件,用ConcurrentHashMap<requestId, Disposable>记录所有在途流式请求,停止生成时通过requestId精准取消,同时记录日志观察取消链路耗时。面试官听到这个地方一般会很感兴趣,因为这说明你真的为“Token计费”考虑过。

3.4 线上化避坑:超时、并发、Token消耗

把AI交互逻辑封装好后,线上运行还会踩到几个实际问题。第一是超时问题。默认HTTP客户端的连接超时一般只有几秒,但大模型首字返回可能要等10秒以上,所以必须区分“连接超时”和“读超时”,读超时建议拉到60秒,首字超时单独判断。如果请求被负载均衡到多个后端实例,Nginx的proxy_read_timeout也要同步调大,否则你后端还没推完第一块数据,网关就给你掐断连接了。

第二是并发控制。大模型接口是慢接口,线程池很容易被打满,所以需要信号量或者限流器控制总并发数。我习惯用Resilience4j的Bulkhead做隔离,给AI接口单独开一个线程池,配置最大并发和队列容量。一定不要把AI请求放进Tomcat默认线程池,否则一个高并发AI功能就能把整个系统拖垮。第三是Token成本控制,接入层要按用户维度做频控,并在响应里返回“本次消耗Token数”,方便运营核算成本。

4. 高频面试题速查与现场复盘

4.1 Java基础与并发:面向对象、容器、AQS

大厂面试的“第一面”往往还是Java基础,但问法越来越场景化。比如“面向对象三大特性”不会直接让你背定义,而是让你对比“封装和抽象的区别”“继承和组合怎么选”。Java容器的重点还是HashMap,强烈建议把JDK 1.7多线程扩容成环、1.8引入红黑树条件(链表长度 >= 8 且数组长度 >= 64)、为什么阈值为0.75往死里背熟。

并发部分,AQS是一个高权重考点。AbstractQueuedSynchronizer几乎撑起了ReentrantLock、Semaphore、CountDownLatch这些同步工具。你至少要能讲清楚三件事:内部用volatile int state记录锁状态;获取锁失败的线程包装成Node节点进入CLH双向队列排队;acquire方法里做“尝试获取 + 入队 + 阻塞”的循环。如果再深入一点,可以提到公平锁和非公平锁在tryAcquire上的差异,非公平锁会在入队前先抢一次锁,所以吞吐更高,但可能造成线程饥饿。

4.2 微服务与AI场景的追问套路

到了第二轮项目面试,面试官通常不背题,而是围绕你的项目简历连环追问。微服务方向常问“你这个服务拆分的依据是什么”“如果订单服务挂了怎么保证用户还能浏览商品”“分布式锁用Redis还是Zookeeper,为什么”。AI方向则常问“你的Prompt是写死在代码里还是可配置”“大模型返回的JSON格式不稳定你怎么处理”“多轮对话的上下文怎么存”。

我自己的经验是,AI场景题不要把话说太满,加一句“我们当前的实现是……但考虑到……后续会调整为……”反而更可信。比如“上下文管理”这个问题,可以如实回答“先存Redis,设置30分钟过期,每次把最近10条历史组装后发给模型”,然后补一句“现在用户量上来后,我正在考虑用向量数据库做长期记忆”。这种“已经做过+还在演进”的表达,面试官非常吃这一套。

4.3 常见问题排查表与避坑经验

我把自己在项目中踩过、也帮人排查过的高频问题整理成一张表,面试前过一遍很有用。

现象可能原因排查思路
Bean注入报NoSuchBeanDefinitionException服务类没加注解、包扫描路径没覆盖看启动类包路径,确认@ComponentScan范围;MyBatis的Mapper要加@MapperScan
Spring Security登录后仍是403老写法迁移后权限规则没放开,或静态资源被拦截检查SecurityFilterChain里的permitAll路径;用Postman带token复现
SSE接口等待很久才一次性返回响应在网关或Servlet容器被缓冲了Nginx关缓冲;Spring Boot里设置produces=text/event-stream;确认已正常flush
SseEmitter频繁报超时默认30秒超时,大模型生成太久改为new SseEmitter(0L)或设置足够长的超时;配好onTimeout回调
前端abort后模型还在生成后端没有同步取消上游请求在emitter的onCompletion/onError里cancel WebClient的Disposable
Feign调用失败但本地无日志日志级别未开启,或服务未注册到Nacos开启logging.level包名=DEBUG;检查Nacos服务列表
分布式事务里补偿逻辑不生效本地消息表重复投递导致幂等没做好先检查消费方是否按业务主键做幂等,再查消息表状态流转

最后再分享一个我个人的体会:最近几次面试模拟中,真正能拿到高评价的人,不是把答案背得最全的人,而是会主动讲“当时为什么这么做、踩了什么坑、后来怎么改”的人。比如SSE加abort机制,一讲出“客户端断开后Token还要继续计费”这个真实痛点,面试官的眼睛会亮。希望这份实战笔记能帮你在面试里把每个技术点讲得像“亲手解决过”的样子。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询