JavaEE 面试题
本页只放 JavaEE 面试标准回答、项目话术、常见追问和知识点跳转。Servlet、Filter、Listener、Session、Cookie、JDBC、连接池和 Spring MVC 请求链路原理放在知识点页。
使用方式
mermaid
flowchart TD
A["面试页:回答请求链路"] --> B["知识点页:理解 Web 容器原理"]
B --> C["项目页:登录、过滤、连接池、接口排查"]高频问题
| 面试题 | 标准回答 | 原理知识点 |
|---|---|---|
| JavaEE 请求完整链路是什么 | 浏览器发起 HTTP 请求后,Tomcat 等 Web 容器接收连接并解析 HTTP,创建 Request/Response,请求先经过 Filter 链,放行后进入 Servlet。Spring MVC 中入口 Servlet 是 DispatcherServlet,它再分发到 Controller。业务访问数据库时通过 JDBC、连接池和事务执行 SQL,最后写 Response 返回浏览器。 | JavaEE从零到生产级掌握、商业场景训练营 |
| JavaEE 面试为什么常问请求链路 | 因为 Web 问题大多能落到请求是否进入容器、Filter 是否放行、Servlet/Controller 是否匹配、Session/Cookie 是否携带、数据库连接是否释放。 | JavaEE基础 |
| Servlet 生命周期 | 容器按 load-on-startup 或首次请求实例化 Servlet,注入 ServletConfig 后调用一次 init;初始化成功后多个容器线程可并发调用同一实例的 service,HttpServlet 再按方法分派到 doGet/doPost;卸载时调用 destroy,但进程崩溃时不保证执行。 | Servlet生命周期全过程 |
| Servlet 是否线程安全 | Servlet 通常是单实例并由多个容器工作线程并发调用。局部变量属于本次方法调用,成员字段由请求共享;当前用户、订单、可变缓冲放成员字段会产生覆盖和串数据。正确做法是无请求状态设计,而不是同步整个 service 把请求串行化。 | Servlet单实例多线程 |
| Servlet 2.5、3.0、3.1 和 Jakarta 包名有什么区别 | Servlet 2.5 主要依赖 web.xml;3.0 引入注解、异步、文件上传和容器初始化扩展;3.1 增加非阻塞 I/O API。Jakarta Servlet 5+ 把包名从 javax.servlet 改为 jakarta.servlet,Tomcat 10+ 与旧 javax 应用不能只靠改一个 import 混用。 | Servlet版本与包名 |
| Servlet URL 映射优先级是什么 | 容器通常先找精确映射,再找最长路径前缀映射,然后找扩展名映射,最后使用默认映射。Context Path 先确定 Web 应用,应用内路径再参与 Servlet 映射;路径映射还会影响 servletPath 和 pathInfo。 | Servlet URL映射全过程 |
| load-on-startup 有什么作用 | 非负值通常让容器在应用部署阶段创建并 init,数值用于相对启动顺序;未配置或为负值通常首次请求懒加载。启动加载能尽早暴露配置错误,但 init 中无超时远程调用会让整个应用部署卡住。 | Servlet生命周期全过程 |
| Request 参数、属性和请求体有什么区别 | parameter 通常来自查询串或表单且是字符串;attribute 是服务端在当前 Request 上保存的对象,forward 可共享;JSON body 是一次性输入流,不会自动成为 parameter。Filter 提前读完 body 后不包装,下游再读会为空。 | Request读取全过程 |
| 为什么请求体经常只能读一次 | HTTP body 在容器中以流暴露,读取会推进游标;getInputStream 与 getReader 也不能混用。需要日志和下游重复读取时,要用有大小上限的包装器缓存,且不能记录密码、token 等敏感内容。 | Request读取全过程 |
| Response committed 是什么意思 | 表示状态行和响应头已经开始发送。缓冲写满、flush、close、sendRedirect 或请求结束都可能提交;提交后不能可靠修改状态、编码和头,也不能再 forward 或用统一错误页重写已经发送的响应。 | Response提交全过程 |
| getWriter 和 getOutputStream 为什么不能一起用 | Writer 负责字符编码后输出文本,OutputStream 输出原始字节。容器必须选择一种响应输出模式,同一响应混用通常抛 IllegalStateException。编码和 Content-Type 还应在创建 Writer 前设置。 | Response提交全过程 |
| Filter 前置和后置顺序为什么相反 | chain.doFilter 是对下一节点的嵌套调用。A 前置进入 B,B 前置进入 Servlet;Servlet 返回后先回到 B 后置,再回到 A 后置,因此调用栈天然形成正向进入、反向退出。 | FilterChain调用栈全过程 |
| Filter 不调用或调用两次 chain 会怎样 | 不调用会短路后续 Filter 和 Servlet,当前 Filter 必须自行生成完整响应;调用两次可能让 Controller 和业务执行两次、重复写响应。Filter 不是重试器,每次分派只能按设计调用一次链。 | FilterChain调用栈全过程 |
| 多个 Filter 的顺序怎么确定 | web.xml 可按映射规则和声明控制;仅依赖多个 @WebFilter 的类名或扫描顺序不可移植。ServletContext 动态注册可指定映射前后,Spring Boot 可用 FilterRegistrationBean 的 order。还要同时明确 URL、Servlet Name 和 DispatcherType。 | Filter映射与顺序 |
| Filter 为什么可能对同一浏览器请求执行多次 | 一次客户端请求可能发生 REQUEST、FORWARD、ERROR、ASYNC 等多次容器分派,Filter 若映射多个 DispatcherType 会再次执行;也可能被注解、web.xml 和 Spring 重复注册。应记录 DispatcherType、traceId 和注册来源。 | DispatcherType全过程 |
| Filter 是线程安全的吗 | Filter 通常单实例,由多个请求线程并发调用 doFilter。开始时间、当前用户、路径等请求状态必须是局部变量;成员字段只保存不可变配置或线程安全组件。同步整个 doFilter 会把请求串行化。 | Filter生命周期与线程安全 |
| Filter 能否读取请求体 | 可以,但原 body 是一次性流,直接读取后下游再读会为空。需要重复读取要用有大小上限的 HttpServletRequestWrapper,同时正确实现 Reader/InputStream、字符编码和异步接口,并避开 multipart 和敏感数据。 | RequestWrapper请求体缓存 |
| Filter 怎样记录最终响应状态和 body | 可用 HttpServletResponseWrapper 记录 setStatus/sendError/sendRedirect;若缓存 body,链结束后必须复制回原 Response。大文件、SSE 和流式响应不能全量缓存,响应提交后也不能可靠再追加 Header。 | ResponseWrapper全过程 |
| CORS Filter 为什么经常被认证 Filter 拦截 | 浏览器复杂跨域请求先发送 OPTIONS 预检,通常不携带业务凭证。如果认证 Filter 先要求 token,会返回 401,真实请求不会发送。CORS 应先按白名单处理预检,但 CORS 本身不能代替认证授权。 | CORS过滤器全过程 |
| 异步请求中 Filter finally 是否代表请求结束 | 不一定。startAsync 后初始 REQUEST 分派可以返回,Filter finally 此时执行,但异步业务和响应仍未完成;准确总耗时要在 AsyncListener 的 complete/timeout/error 统计。AsyncContext.dispatch 还会产生新的 ASYNC 分派。 | Filter异步分派 |
| Filter 和 Interceptor 区别 | Filter 属于 Servlet 规范,在 Servlet 前工作,可处理静态资源和包装 Request/Response;Interceptor 属于 Spring MVC,在 DispatcherServlet 找到 Handler 后工作,能取得 HandlerMethod。底层协议/CORS适合Filter,Controller语义审计适合Interceptor。 | Filter、Interceptor与AOP对比、Spring MVC |
| Listener 有哪些类型 | 除 Context、Request、Session 生命周期监听器外,还有三类 AttributeListener、HttpSessionBindingListener 和 HttpSessionActivationListener。它们由容器在事件关键路径回调,不应执行无超时远程调用。 | Listener体系 |
| ServletContextListener 为什么会造成重部署内存泄漏 | Listener 创建的线程、调度器、Driver、ThreadLocal 或回调若未注销,会通过 Runnable 或线程 ContextClassLoader 持有旧 WebAppClassLoader。重部署后旧类元数据无法回收,反复累积可导致 Metaspace OOM。 | ContextListener资源管理 |
| HttpSessionListener 统计的在线人数准确吗 | 通常不准确。关闭浏览器不会立即销毁 Session,一个用户可有多个 Session,多实例只看到本地会话,超时和集中 Session 事件还有延迟。商业在线用户应结合心跳、最后活跃时间、用户去重和集中存储。 | SessionListener在线统计 |
| Filter 与 Listener 线上怎样排查 | 请求没进 Controller 查每个 Filter 是否进入、短路和 committed;重复执行查 DispatcherType 与重复注册;body 为空查提前读取和 Wrapper;CORS 查 OPTIONS;重部署泄漏查未停止线程和旧 WebAppClassLoader GC Root。 | Filter与Listener生产排查Runbook |
| Cookie 和 Session 是什么关系 | Cookie 是浏览器按域、路径和安全属性管理并随请求发送的字符串;Session 是服务端状态。常见组合是 Cookie 保存随机 SessionId,服务端按 ID 查 HttpSession,但 Cookie 也可保存偏好/token,二者不是同一个概念。 | Cookie收发全过程、Session内部模型 |
| Domain、Path、Secure、HttpOnly、SameSite 分别做什么 | Domain和Path决定发送范围但Path不是安全隔离;Secure要求安全传输;HttpOnly阻止普通JS读取但不能阻止XSS代用户发请求;SameSite限制部分跨站携带,是CSRF纵深防线但不是唯一防护。 | Cookie属性全过程 |
| Cookie 为什么删除不掉 | 删除本质是设置同名 Cookie 立即过期,必须匹配原 Cookie 的 Path,必要时还要匹配 Domain。若属性不同,浏览器可能保留原 Cookie,并同时出现多个同名不同作用域 Cookie。 | Cookie属性全过程 |
| getSession() 和 getSession(false) 区别 | getSession() 等价于 true,没有有效会话会创建新 Session 并准备 Set-Cookie;false 只查找,不存在返回 null。认证检查应使用 false,避免匿名流量、爬虫和探活创建大量空 Session。 | Session创建与查找全过程 |
| Session 是什么时候创建的 | 不是访问网站就必然创建,而是在代码/框架请求创建会话时生成随机 ID、建立服务端记录并通过 Cookie 或 URL 重写传回客户端。调用 getSession(false) 不会创建,isNew 也不等于用户刚登录。 | Session创建与查找全过程 |
| Session 固定攻击是什么,怎样防 | 攻击者让受害者先使用攻击者已知的 SessionId,受害者登录后若继续沿用,攻击者可复用身份。认证成功后应 changeSessionId 或重建会话,并使用 HTTPS、Secure、HttpOnly、合理超时和禁用 URL 会话跟踪。 | Session安全攻击、登录全过程 |
| HttpOnly 能完全防止 XSS 或会话攻击吗 | 不能。它阻止普通脚本直接读取 Cookie,但 XSS 仍可在用户浏览器中发起携带 Cookie 的同源请求和读取页面数据。仍需输出编码、CSP、依赖治理、HTTPS、短会话和敏感操作再认证。 | Session安全攻击 |
| SameSite 能完全防 CSRF 吗 | 不能。它能减少部分跨站 Cookie 携带,但受浏览器、同站不同源、顶级导航和业务集成影响。敏感写操作仍应配合 CSRF Token、Origin/Referer 校验、正确HTTP方法和二次认证。 | Session安全攻击 |
| Session 超时是什么语义 | maxInactiveInterval 是最大不活跃秒数,不是从创建起绝对寿命;有效访问可能刷新时间。高安全系统还要设置绝对登录时长、权限版本和近期认证,不能只依赖不活跃超时。 | Session超时与失效 |
| Session 是否线程安全 | 同一用户的多个标签页和并行请求可同时访问一个 Session。attribute 单次存取不意味着 value 或 get-修改-set 原子,关键购物车、流程和权限状态应存数据库/状态服务并用事务或版本控制。 | Session并发访问 |
| 多实例为什么会随机掉登录 | 登录 Session 若只在实例A内存,下一请求被负载均衡到实例B就查不到同一 ID。可用粘性会话、Session复制、Redis集中Session或Token,但各自有故障、序列化、一致性和撤销成本。 | 分布式Session全过程 |
| Spring Session + Redis 大致原理是什么 | SessionRepositoryFilter 包装 Request,把 HttpSession API 委托给 SessionRepository;Redis实现按SessionId加载、保存并维护TTL。它解除实例绑定,但每请求可能增加Redis I/O,并引入序列化、并发覆盖、故障和TTL一致性问题。 | 分布式Session全过程、Redis Session边界 |
| Session、Opaque Token、JWT 怎么选 | Session和Opaque Token在服务端保存状态,便于主动撤销;JWT可本地验签、减少查询,但权限实时变更和主动撤销更复杂。Cookie是传输/存储机制,不等于Session;JWT默认签名不加密,payload不能放敏感信息。 | Session、Token与JWT选型 |
| 退出登录为什么不能只删 Cookie | 只删浏览器 Cookie 不会销毁服务端 Session,窃取到的旧 ID 仍可能使用。应 invalidate 服务端会话、删除匹配 Path/Domain 的 Cookie、清理安全上下文;全设备退出还要批量撤销其他 Session/refresh token。 | Session退出与全局撤销 |
| 登录响应有 Set-Cookie 但下一请求未登录怎么查 | 先看 Set-Cookie 是否被浏览器接受,再看 Cookie 是否存储、下一请求是否因Domain/Path/Secure/SameSite/credentials未发送;若已发送,再查目标实例、Redis中的ID/TTL/value、Session是否失效和身份attribute。 | Session与Cookie生产排查Runbook |
| forward、include 和 redirect 区别 | forward/include 都由容器在同一请求中调用内部资源,可共享 Request;forward 转交主处理,include 只包含输出。redirect 返回 3xx 和 Location,由客户端新发请求,地址变化且原 Request attribute 丢失,还要防用户输入造成开放重定向。 | 转发、包含与重定向 |
| Servlet 异步是否等于非阻塞 | 不等于。Servlet 3.0 AsyncContext 释放原容器线程后仍需其他线程完成任务,慢 SQL 仍占连接;Servlet 3.1 ReadListener/WriteListener 才是流的非阻塞通知。异步必须设置超时、处理错误并最终 complete。 | AsyncContext异步全过程 |
| DispatcherServlet 和 Servlet 是什么关系 | DispatcherServlet 本身就是 HttpServlet。Tomcat 先完成 HTTP 解析和 Filter 链,再进入 DispatcherServlet;它通过 HandlerMapping、HandlerAdapter、参数解析、Controller 调用和返回值处理把 Servlet 模型封装成 Spring MVC。 | DispatcherServlet调用链 |
| Servlet 规范有哪些常见扩展点 | 可用 web.xml/@WebServlet 声明映射,ServletContainerInitializer 在容器启动时发现框架,ServletContext 动态注册组件,Filter 拦截请求,Listener 监听生命周期,AsyncListener 监听异步完成、超时和错误。 | Servlet扩展点 |
| Servlet 线上问题怎么排查 | 404 要逐层查域名端口、Context、部署状态、代理重写和映射;405 查 HTTP 方法;body 为空查 Content-Type 和提前读取;响应异常查 committed;大量卡住要看容器线程指标、连续 jstack、连接池和下游超时。 | Servlet生产排查Runbook |
| JDBC 是数据库通信协议吗 | 不是。JDBC 是 Java 标准 API,应用面向 Connection、PreparedStatement、ResultSet;数据库厂商 Driver 把调用编码成 MySQL、PostgreSQL、Oracle 等各自协议。统一 API 不代表 SQL 方言、批处理和隔离行为完全相同。 | JDBC完整链路、物理连接全过程 |
| JDBC Driver 怎样自动加载 | JDBC 4 驱动 JAR 在 META-INF/services/java.sql.Driver 声明实现,DriverManager 初始化时通过 SPI 发现并注册,再按 JDBC URL 匹配 acceptsURL。老代码 Class.forName 依赖类初始化显式注册。 | JDBC Driver加载全过程 |
| DriverManager 和 DataSource 区别 | DriverManager 按URL直接选择驱动创建连接,适合工具和教学;DataSource 是连接工厂抽象,可由连接池、JNDI或XA实现,便于集中配置、监控和事务集成,生产业务通常注入DataSource。 | DriverManager与DataSource |
| JDBC 执行流程 | 驱动通过SPI发现,DataSource借出Connection;创建PreparedStatement并绑定类型参数,驱动编码数据库协议;数据库执行后返回ResultSet或影响行数;应用在同一Connection上提交/回滚,最后逆序关闭并归还代理连接。 | JDBC数据库访问全过程 |
| PreparedStatement 一定在服务端预编译吗 | 不一定,取决于驱动与配置,可能服务端预处理,也可能客户端正确编码后发送。但参数绑定仍把值与SQL结构分离,可防参数逃逸成语法。表名、列名和排序方向不能用问号,必须服务端白名单。 | Statement体系 |
| executeQuery、executeUpdate、execute 区别 | executeQuery预期返回ResultSet;executeUpdate用于更新/DDL并返回影响行数;execute处理结果类型未知或多结果,返回首个结果是否为ResultSet。影响行数0不必然代表驱动失败,乐观锁要明确校验等于1。 | JDBC execute方法 |
| ResultSet 为什么必须先 next | 初始游标位于第一行之前,next将游标移动到下一行并返回是否存在。getInt/getLong读取SQL NULL会返回0,要紧接wasNull区分NULL和业务0;显式列和别名比select *稳定。 | ResultSet全过程 |
| JDBC 大结果集怎样处理 | 不要select *后全部装List。使用稳定索引、Keyset分页或经驱动验证的流式ResultSet,设置fetchSize和超时,每行即时处理。fetchSize只是提示,MySQL等驱动还可能需要特定游标配置。 | JDBC大结果集 |
| JDBC 事务为什么必须使用同一 Connection | 本地事务绑定数据库会话/Connection。两条SQL若分别借不同Connection,即使代码相邻也属于不同事务。应在同一Connection关闭autoCommit,成功commit,异常rollback,最后归还。 | Connection与事务绑定、JDBC事务全过程 |
| autoCommit=true 有什么风险 | 默认自动提交时每条SQL通常是独立事务。订单更新成功自动提交后,库存更新失败无法用后续rollback撤销订单;多语句业务必须显式事务或由Spring事务统一绑定Connection。 | JDBC事务全过程 |
| Savepoint 是嵌套事务吗 | 不是。它只是同一个事务内的回滚标记,rollback(savepoint)保留标记之前的未提交修改,外层最终rollback仍可撤销全部。数据库对DDL和保存点支持存在差异。 | JDBC Savepoint |
| 批处理是不是批次越大越好 | 不是。批处理减少网络往返和解析,但批次过大会增加参数内存、包大小、锁持有、事务日志、复制延迟和失败重试范围。应按驱动和数据库压测,并在异常时结合事务与幂等处理。 | JDBC批处理 |
| 连接池中的 Connection.close 做什么 | 池化DataSource通常返回代理Connection,close把代理标记关闭并归还物理连接,池再回滚未结束事务、重置autoCommit/readOnly/isolation等。非池化连接close才通常关闭物理Socket。业务仍要显式提交或回滚。 | 连接池代理与归还全过程 |
| 为什么要用连接池 | 建立物理连接涉及TCP、可选TLS、认证和数据库会话,成本高。连接池复用连接、限制并发并提供等待、健康检查和泄漏监控;但池不是越大越好,所有实例总连接不能超过数据库承载能力。 | 物理连接全过程、连接池容量与排队 |
| JDBC 有哪些不同超时 | 至少要区分驱动建连、池借连接、登录、Statement查询、Connection网络读写、socket读取和整个事务deadline。只设置queryTimeout不能替代网络与事务超时,取消也不保证数据库瞬时停止。 | JDBC超时体系 |
| SQLException 应该看什么 | 同时看SQLState、数据库vendorCode、message、nextException和suppressed异常;SQLState 08类常提示连接问题,但驱动映射有差异。日志记录SQL模板、耗时和错误码,不记录密码、完整连接串及敏感参数。 | SQLException诊断 |
| COMMIT时断网能否直接重试 | 不能。数据库可能已经持久化提交,只是成功响应丢失,应用看到的是未知结果;直接重试可能重复下单或扣款。必须用业务请求号、唯一索引、状态查询和幂等状态机处理。 | JDBC未知提交结果 |
| 连接池耗尽怎么排查 | 看active、idle、pending、acquire timeout和max,连续jstack找等待getConnection和持有者;持有者在SQL则查慢日志/锁,在HTTP则说明事务内慢外调。结合泄漏借出栈和数据库容量,不能只调大池。 | JDBC生产排查Runbook |
| Spring MVC 和 Servlet 关系 | Spring MVC 的入口本质是 DispatcherServlet。请求先进 Web 容器,再经过 Filter,最后由 DispatcherServlet 分发到 Controller。 | Spring MVC |
| 乱码问题怎么排查 | 看请求编码、响应编码、过滤器设置、数据库字符集和页面编码是否一致。 | Filter与Listener、Servlet |
项目话术
text
在医疗数据采集平台里,后台接口本质仍然跑在 Servlet Web 容器中。登录、鉴权、跨域、日志 traceId 这类通用逻辑适合放在 Filter 或 Spring Security 过滤器链;业务接口交给 Controller;数据库访问通过连接池控制并发,避免采集任务把数据库连接打满。常见追问
| 追问 | 回答方向 | 跳转 |
|---|---|---|
| Filter 为什么能做登录校验 | 它在请求进入 Servlet 前执行,可以读取 Header/Cookie 并决定是否放行。 | Filter与Listener |
| Session 多实例为什么会丢 | 请求打到不同实例时,本地内存 Session 不共享,需要粘性会话、集中 Session 或无状态 Token。 | Session与Cookie |
| 连接泄漏怎么排查 | 看连接池活跃连接、等待线程、慢 SQL、是否 finally 关闭资源。 | JDBC |
| DispatcherServlet 做什么 | 它接收请求,查找 Handler,执行参数绑定、调用 Controller、处理返回值。 | Spring MVC |
面试回答模板
text
JavaEE 我会从一次请求链路回答。浏览器发起 HTTP 请求后,Web 容器接收请求,先经过 Filter 链,再进入 Servlet 或 Spring MVC 的 DispatcherServlet,最后调用业务 Controller。Session/Cookie 负责会话状态,JDBC 和连接池负责数据库访问。线上排查也按这条链路看:请求、过滤、路由、业务、数据库连接和响应。本章小结
JavaEE 面试页负责“怎么答”。Servlet 为什么单例多线程、Filter 为什么能拦截请求、Session 为什么多实例会有问题、连接池为什么必要,都要跳到知识点页理解。
