Java Web 常见面试题(2026 最新整理)

1. 引言

Java Web 是后端开发面试中的核心考察方向,覆盖 Servlet、JSP、Spring、Spring MVC、MyBatis、数据库、分布式、缓存、消息队列等众多知识点。本文整理了 Java Web 面试中最常见的高频题目,并给出简洁清晰的参考答案,帮助你在面试前快速回顾核心要点。

2. Servlet 与 JSP 基础

2.1 什么是 Servlet?Servlet 的生命周期是什么?

Servlet 是运行在 Web 容器(如 Tomcat)中的 Java 类,用于处理客户端请求并生成响应。其生命周期由容器管理,分为四个阶段:

  • 加载与实例化:容器加载 Servlet 类并创建实例。
  • 初始化(init) :容器调用 init() 方法,只执行一次,用于加载配置、初始化资源。
  • 服务(service) :容器调用 service() 方法处理请求,根据请求类型分发到 doGet() / doPost() 等方法,可被多次调用。
  • 销毁(destroy) :容器卸载 Servlet 前调用 destroy() 方法释放资源,只执行一次。

2.2 什么是 JSP?JSP 与 Servlet 的区别是什么?

JSP(Java Server Pages)本质上是 Servlet 的一种简化形式,在 HTML 中嵌入 Java 代码,由容器编译为 Servlet 后执行。

对比项 Servlet JSP
定位 适合处理业务逻辑 适合展示页面
编写方式 纯 Java 类 HTML + Java 片段
编译时机 部署时编译 首次访问时编译
职责 控制层 视图层

2.3 转发(forward)与重定向(redirect)的区别?

  • 转发(forward) :发生在服务器内部,浏览器地址栏不变,只发起一次请求,可以携带 request 域中的数据。
  • 重定向(redirect) :服务器返回 302 状态码,浏览器重新发起新请求,地址栏变化,无法直接共享 request 域数据。

3. Spring 核心

3.1 Spring 的 IOC 和 AOP 是什么?

  • IOC(控制反转):将对象的创建和依赖管理交给 Spring 容器,由容器负责实例化、装配和管理 Bean 的生命周期,降低耦合。
  • AOP(面向切面编程):将日志、事务、权限等横切逻辑从业务代码中抽取出来,通过动态代理在运行时织入,实现无侵入式扩展。

3.2 Spring Bean 的作用域有哪些?

  • singleton:默认作用域,容器中只存在一个实例。
  • prototype:每次获取都创建新实例。
  • request:每次 HTTP 请求创建一个实例,仅 Web 环境有效。
  • session:每个会话创建一个实例,仅 Web 环境有效。
  • application:整个 ServletContext 共享一个实例。

3.3 Spring 如何解决循环依赖?

Spring 通过三级缓存解决单例 Bean 的循环依赖:

  1. 一级缓存:存放完整创建好的 Bean。
  2. 二级缓存:存放早期暴露的 Bean(未完成属性填充)。
  3. 三级缓存:存放 Bean 的工厂对象(ObjectFactory)。

下面通过一个配置类示例展示两个相互依赖的 Bean 如何通过 @Autowired 注入,以及三级缓存如何解决该循环依赖:

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Autowired;

@Configuration
public class CircularDependencyConfig {

    // 创建 BeanA,并注入 BeanB
    @Bean
    public BeanA beanA(@Autowired BeanB beanB) {
        return new BeanA(beanB);
    }

    // 创建 BeanB,并注入 BeanA
    @Bean
    public BeanB beanB(@Autowired BeanA beanA) {
        return new BeanB(beanA);
    }
}

class BeanA {
    private final BeanB beanB;

    public BeanA(BeanB beanB) {
        this.beanB = beanB;
    }
}

class BeanB {
    private final BeanA beanA;

    public BeanB(BeanA beanA) {
        this.beanA = beanA;
    }
}

三级缓存解决循环依赖的流程说明:

  1. 实例化 BeanA :容器开始创建 beanA,此时 beanA 尚未完成属性填充,Spring 将其早期引用 (一个 ObjectFactory 工厂对象)放入三级缓存 (singletonFactories)。
  2. 填充 BeanA 依赖 :beanA 需要注入 beanB,容器转而创建 beanB。
  3. 实例化 BeanB :beanB 创建后同样被放入三级缓存,随后填充依赖时发现需要注入 beanA。
  4. 从缓存获取 BeanA 早期引用 :容器在三级缓存中找到 beanA 的 ObjectFactory,调用其 getObject() 得到 beanA 的早期引用(此时 beanA 尚未完全初始化),将其注入 beanB。beanB 完成创建后放入一级缓存 (singletonObjects)。
  5. 完成 BeanA 创建 :beanB 创建完成后,容器继续完成 beanA 的属性填充和初始化,最终将完整的 beanA 放入一级缓存。

关键点 :三级缓存中的 ObjectFactory 是解决循环依赖的核心------它允许在 Bean 未完全初始化时提前暴露一个可用的引用,从而打破 A、B 之间的创建死锁。注意:该机制仅对单例(singleton)且非构造器注入 的循环依赖有效;若使用构造器注入,则无法通过三级缓存解决,会抛出 BeanCurrentlyInCreationException。

当 A 依赖 B、B 依赖 A 时,A 创建后提前暴露到三级缓存,B 创建时从缓存中获取 A 的早期引用完成注入,从而打破循环。

4. Spring MVC

4.1 Spring MVC 的执行流程是什么?

  1. 客户端发送请求,由 DispatcherServlet 接收。
  2. DispatcherServlet 调用 HandlerMapping 找到对应的 Handler(Controller 方法)。
  3. 通过 HandlerAdapter 执行处理器方法。
  4. 方法返回 ModelAndView 或直接返回数据。
  5. ViewResolver 解析视图,渲染后响应给客户端。

#mermaid-svg-LLrlZYkJ4BSURifx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LLrlZYkJ4BSURifx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LLrlZYkJ4BSURifx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LLrlZYkJ4BSURifx .error-icon{fill:#552222;}#mermaid-svg-LLrlZYkJ4BSURifx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LLrlZYkJ4BSURifx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LLrlZYkJ4BSURifx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LLrlZYkJ4BSURifx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LLrlZYkJ4BSURifx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LLrlZYkJ4BSURifx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LLrlZYkJ4BSURifx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LLrlZYkJ4BSURifx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LLrlZYkJ4BSURifx .marker.cross{stroke:#333333;}#mermaid-svg-LLrlZYkJ4BSURifx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LLrlZYkJ4BSURifx p{margin:0;}#mermaid-svg-LLrlZYkJ4BSURifx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LLrlZYkJ4BSURifx .cluster-label text{fill:#333;}#mermaid-svg-LLrlZYkJ4BSURifx .cluster-label span{color:#333;}#mermaid-svg-LLrlZYkJ4BSURifx .cluster-label span p{background-color:transparent;}#mermaid-svg-LLrlZYkJ4BSURifx .label text,#mermaid-svg-LLrlZYkJ4BSURifx span{fill:#333;color:#333;}#mermaid-svg-LLrlZYkJ4BSURifx .node rect,#mermaid-svg-LLrlZYkJ4BSURifx .node circle,#mermaid-svg-LLrlZYkJ4BSURifx .node ellipse,#mermaid-svg-LLrlZYkJ4BSURifx .node polygon,#mermaid-svg-LLrlZYkJ4BSURifx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LLrlZYkJ4BSURifx .rough-node .label text,#mermaid-svg-LLrlZYkJ4BSURifx .node .label text,#mermaid-svg-LLrlZYkJ4BSURifx .image-shape .label,#mermaid-svg-LLrlZYkJ4BSURifx .icon-shape .label{text-anchor:middle;}#mermaid-svg-LLrlZYkJ4BSURifx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LLrlZYkJ4BSURifx .rough-node .label,#mermaid-svg-LLrlZYkJ4BSURifx .node .label,#mermaid-svg-LLrlZYkJ4BSURifx .image-shape .label,#mermaid-svg-LLrlZYkJ4BSURifx .icon-shape .label{text-align:center;}#mermaid-svg-LLrlZYkJ4BSURifx .node.clickable{cursor:pointer;}#mermaid-svg-LLrlZYkJ4BSURifx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LLrlZYkJ4BSURifx .arrowheadPath{fill:#333333;}#mermaid-svg-LLrlZYkJ4BSURifx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LLrlZYkJ4BSURifx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LLrlZYkJ4BSURifx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LLrlZYkJ4BSURifx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LLrlZYkJ4BSURifx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LLrlZYkJ4BSURifx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LLrlZYkJ4BSURifx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LLrlZYkJ4BSURifx .cluster text{fill:#333;}#mermaid-svg-LLrlZYkJ4BSURifx .cluster span{color:#333;}#mermaid-svg-LLrlZYkJ4BSURifx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LLrlZYkJ4BSURifx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LLrlZYkJ4BSURifx rect.text{fill:none;stroke-width:0;}#mermaid-svg-LLrlZYkJ4BSURifx .icon-shape,#mermaid-svg-LLrlZYkJ4BSURifx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LLrlZYkJ4BSURifx .icon-shape p,#mermaid-svg-LLrlZYkJ4BSURifx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LLrlZYkJ4BSURifx .icon-shape .label rect,#mermaid-svg-LLrlZYkJ4BSURifx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LLrlZYkJ4BSURifx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LLrlZYkJ4BSURifx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LLrlZYkJ4BSURifx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 客户端请求
DispatcherServlet
HandlerMapping 查找处理器
HandlerAdapter 执行方法
返回 ModelAndView
ViewResolver 解析视图
渲染响应

4.2 @Controller 和 @RestController 的区别?

  • @Controller:返回视图名称,配合 ViewResolver 渲染页面;如需返回 JSON 需加 @ResponseBody。
  • @RestController:是 @Controller + @ResponseBody 的组合注解,方法返回值直接以 JSON/XML 形式写入响应体,适合前后端分离项目。

5. MyBatis 与数据库

5.1 #{} 和 ${} 的区别?

  • #{}:预编译占位符,底层使用 PreparedStatement,能有效防止 SQL 注入。
  • ${}:字符串拼接,直接替换 SQL 片段,存在 SQL 注入风险,一般仅用于动态表名、排序字段等不可控场景。

5.2 什么是数据库事务?事务的四大特性是什么?

事务是一组要么全部成功、要么全部回滚的数据库操作。四大特性(ACID):

  • 原子性(Atomicity):事务内操作不可分割,全部成功或全部失败。
  • 一致性(Consistency):事务执行前后数据保持一致状态。
  • 隔离性(Isolation):并发事务之间相互隔离。
  • 持久性(Durability):事务提交后对数据的修改永久保存。

5.3 什么是索引?索引失效的常见场景有哪些?

索引是帮助数据库高效获取数据的数据结构(如 B+ 树),能显著提升查询速度。常见失效场景:

  • 对索引列使用函数或计算,如 WHERE YEAR(create_time) = 2026。
  • 使用 LIKE '%关键字' 前置模糊查询。
  • 隐式类型转换导致索引失效。
  • 使用 OR 连接非索引列。
  • 索引列参与 !=、NOT IN 等操作。

6. 分布式与微服务

6.1 什么是 CAP 定理?

CAP 定理指出分布式系统无法同时满足以下三点,最多只能满足其中两个:

  • 一致性(Consistency):所有节点在同一时刻看到相同的数据。
  • 可用性(Availability):每个请求都能在合理时间内获得响应。
  • 分区容错性(Partition tolerance):网络分区时系统仍能继续运行。

实际分布式系统中,分区容错性(P)是必须保证的,因此通常在 C 和 A 之间权衡。

6.2 什么是分布式事务?常见解决方案有哪些?

分布式事务指跨多个服务或数据库的事务操作。常见解决方案:

  • 两阶段提交(2PC):协调者统一协调提交或回滚,实现强一致性。
  • TCC(Try-Confirm-Cancel):通过预留资源、确认、补偿三个阶段实现最终一致性。
  • 本地消息表 + 消息队列:通过 MQ 异步通知下游,配合本地消息表保证最终一致性。
  • Seata AT 模式:基于全局事务管理器,对业务侵入小。

7. 缓存与消息队列

7.1 Redis 常见的数据结构有哪些?

  • String:字符串,可做缓存、计数器。
  • Hash:哈希表,适合存储对象。
  • List:列表,适合消息队列、最新列表。
  • Set:无序集合,适合去重、共同好友。

7.2 Redis 持久化机制(RDB 与 AOF)的区别

Redis 默认将数据保存在内存中,重启后数据会丢失,因此需要通过持久化机制将数据落盘。Redis 提供了 RDB(快照) 和 AOF(追加日志) 两种持久化方式,二者对比如下:

对比项 RDB(快照) AOF(追加日志)
持久化方式 将内存数据以二进制快照形式写入磁盘 将每次写操作命令以日志形式追加到文件
触发机制 手动触发(save/bgsave)或按配置自动触发(如 save 900 1) 按 appendfsync 策略触发(always/everysec/no)
恢复速度 快,直接加载快照文件 慢,需逐条重放日志命令
数据安全性 较低,两次快照之间可能丢失数据 较高,可配置为每秒甚至每次写操作同步
文件大小 较小,快照为压缩后的二进制数据 较大,日志会持续累积,需定期 rewrite 压缩
性能影响 主进程 fork 子进程写盘,对主进程影响小 写操作需同步写日志,always 模式性能开销大

适用场景说明:

  • RDB 适用场景:对数据丢失容忍度较高、追求快速恢复的场景,如缓存数据、非关键业务数据;也适合做数据备份和灾难恢复,快照文件便于迁移。

  • AOF 适用场景 :对数据安全性要求高、不允许丢失过多数据的场景,如订单、账户等关键业务数据;可通过 appendfsync everysec 在性能与安全之间取得平衡。

  • 生产实践:通常同时开启 RDB 与 AOF,RDB 用于快速恢复和备份,AOF 用于保证数据完整性;Redis 重启时会优先加载 AOF 文件(数据更完整),若 AOF 关闭则加载 RDB。

  • ZSet(有序集合):带分数的有序集合,适合排行榜。

7.2 什么是缓存穿透、缓存击穿、缓存雪崩?

  • 缓存穿透:查询不存在的数据,缓存和数据库都没有,导致请求直接打到数据库。解决:布隆过滤器、缓存空值。
  • 缓存击穿:某个热点 key 过期瞬间,大量请求同时打到数据库。解决:互斥锁、逻辑过期。
  • 缓存雪崩:大量 key 同时过期或 Redis 宕机,导致数据库压力骤增。解决:过期时间加随机值、集群高可用。

7.3 消息队列的作用是什么?

  • 异步处理:将耗时操作异步化,提升接口响应速度。
  • 削峰填谷:应对瞬时高并发流量,平滑请求压力。
  • 解耦:生产者和消费者互不依赖,降低系统耦合度。

8. 常见面试追问与总结

面试中除了考察知识点本身,还会追问底层原理和实际场景。建议重点准备:

详细解答:Spring 源码中 Bean 的创建流程

Spring 中单例 Bean 的创建核心入口是 AbstractBeanFactory.doGetBean(),最终由 AbstractAutowireCapableBeanFactory.createBean() 完成实例化。整体流程如下:

  1. 实例化(Instantiation) :通过构造器或工厂方法创建 Bean 的原始实例,对应 createBeanInstance(),默认使用无参构造器,也可通过 @Autowired 构造器注入。
  2. 属性填充(Populate) :调用 populateBean(),将 @Autowired、@Resource、@Value 等注解标记的依赖注入到 Bean 中,同时处理循环依赖(提前暴露到三级缓存)。
  3. 初始化(Initialization) :
    • 调用 BeanNameAware、BeanFactoryAware、ApplicationContextAware 等 Aware 接口回调。
    • 执行 BeanPostProcessor.postProcessBeforeInitialization()。
    • 调用 InitializingBean.afterPropertiesSet() 或自定义 init-method。
    • 执行 BeanPostProcessor.postProcessAfterInitialization()(此处是 AOP 动态代理生成的关键节点)。
  4. 注册与使用 :初始化完成后,单例 Bean 被放入一级缓存(singletonObjects),供后续依赖注入和 getBean() 获取。

关键点 :AOP 代理正是在 postProcessAfterInitialization() 阶段通过 AbstractAutoProxyCreator 生成,因此最终放入容器的可能是代理对象而非原始 Bean。

  • Spring 源码中 Bean 的创建流程。

详细解答:MyBatis 一级缓存与二级缓存的区别

对比项 一级缓存(Local Cache) 二级缓存(Global Cache)
作用范围 SqlSession 级别,默认开启 Mapper(Namespace)级别,需手动开启
生命周期 随 SqlSession 创建而存在,关闭或提交时清空 跨 SqlSession 共享,随应用存活
存储结构 本地 HashMap(PerpetualCache) 可配置多种缓存实现(如 Ehcache、Redis)
失效时机 执行增删改(insert/update/delete)或调用 clearCache() 时清空 执行增删改时按命名空间清空对应缓存
并发安全 单会话内使用,无需考虑并发 多会话共享,需考虑并发与序列化

原理说明:

  • 一级缓存 :MyBatis 默认开启,基于 SqlSession 中的 Executor 维护。同一个 SqlSession 内执行相同 SQL(相同参数)时,第二次查询直接命中缓存,不再访问数据库。注意:Spring 整合 MyBatis 时,每次请求通常新建 SqlSession,因此一级缓存作用有限。
  • 二级缓存 :在 mybatis-config.xml 中设置 <setting name="cacheEnabled" value="true"/>,并在 Mapper XML 中添加 <cache/> 标签开启。多个 SqlSession 共享同一命名空间的二级缓存,查询结果以序列化形式存储,因此实体类需实现 Serializable。开启后,一级缓存的结果在 SqlSession 提交时同步到二级缓存。

面试加分点 :二级缓存存在脏读风险------当多表关联查询时,若另一张表的数据被修改,相关命名空间的缓存不会自动失效,因此复杂业务场景下需谨慎使用,或通过 <cache-ref> 关联命名空间。

  • MyBatis 一级缓存与二级缓存的区别。
  • 数据库索引底层 B+ 树的结构与优势。
  • Redis 持久化机制(RDB 与 AOF)的区别。
  • 微服务中服务注册与发现、负载均衡的实现原理。

掌握以上核心知识点,并能够结合项目实践讲清楚「为什么这样做」,是拿下 Java Web 面试的关键。

相关推荐
开开心心就好2 小时前
一元二次方程求解工具,答案带详细步骤
java·服务器·开发语言·算法·pdf·ocr·perl
cc5725026532 小时前
2027校招:大数据管理与应用专业做数据治理,技术之外还要准备什么?
java·开发语言
威哥爱编程2 小时前
【AI全栈12-04】Spring Boot 用结构化输出自动解析简历:让模型按你的 POJO 输出
java·spring boot·后端
Data analyse4562 小时前
数据埋点设计的事件命名规则校验
服务器·前端·数据分析
弈栈录2 小时前
从 JVM 内存模型到 GC 调优:Java 服务性能优化实战
java
用户6222884048192 小时前
Redis 内存管理与过期淘汰策略:从 maxmemory 到 LRU / LFU,一篇讲透
java·redis
shuoshuohaohao2 小时前
Spring-boot-3 -注解 yaml配置 -日志
java·数据库·mysql
青春逝如流水2 小时前
Java 线程池的创建方式、注意要点与任务类型判断
java·开发语言·线程池·多线程·ai文章