小菜鸟

java菜鸟号正在起航

Java Web 中多个请求共享同一个 Servlet 的实现方案

在 Java Web 开发中,多个请求共用同一个 Servlet 是常见需求(如一个模块的增删改查操作)。这种方式可避免创建过多 Servlet 类,简化代码结构。本文将详细介绍两种主流实现方案:基于请求参数的方法分发和基于 URL 路径的反射调用。

方案一:基于请求参数的方法分发

通过在 URL 中添加 method 参数(如 ?method=add?method=delete),在 Servlet 中根据参数值分发到不同处理方法。

实现步骤:

  1. 前端请求携带 method 参数
<!-- 列表页面 -->
<a href="/user?method=list">用户列表</a>
<!-- 添加页面 -->
<a href="/user?method=toAdd">添加用户</a>
<!-- 表单提交 -->
<form action="/user?method=save" method="post">
    <!-- 表单内容 -->
</form>
  1. Servlet 中根据 method 分发处理
阅读全文 »

Java Web 表单重复提交问题:原因分析与解决方案

表单重复提交是 Web 开发中常见的问题,可能导致数据重复入库、订单重复创建等严重后果。本文将详细分析重复提交的场景、成因,并提供基于 Session 的解决方案,确保表单数据仅被处理一次。

表单重复提交的常见场景

1. 场景一:提交后刷新响应页面

  • 操作流程:用户提交表单 → 服务器通过 request.getRequestDispatcher().forward() 转发到结果页 → 用户刷新结果页。
  • 原因:刷新时浏览器会重新发送最后一次请求(即表单提交请求),而地址栏仍显示 Servlet 路径,导致重复提交。

2. 场景二:快速重复点击提交按钮

  • 操作流程:用户在短时间内多次点击 “提交” 按钮。
  • 原因:服务器尚未处理完第一次请求,第二次请求已到达,导致重复处理。

3. 场景三:后退后再次提交

  • 操作流程:用户提交表单 → 点击浏览器 “后退” 按钮 → 再次点击 “提交” 按钮。
  • 原因:后退后表单数据仍保存在浏览器中,再次提交会重新发送相同请求。

注意:不属于重复提交的情况

点击 “后退”→ 刷新页面 → 再次提交,不属于重复提交。因为刷新页面会重新加载表单页,此时提交的是新的请求(原表单数据可能已清空或重新生成)。

解决方案:基于 Session 的令牌验证机制

核心思路是生成唯一令牌(Token),通过 Session 跟踪令牌状态,确保每个表单请求仅被处理一次。

实现步骤:

阅读全文 »

POST 请求体无法重复读取的原因与解决方案

在 Java Web 开发中,经常会遇到 “POST 请求体只能读取一次” 的问题:当过滤器(Filter)读取请求体后,Servlet 中再次读取时会返回空数据。这一现象与请求流的底层实现密切相关,本文将解析其原因,并提供基于请求包装类的解决方案。

为什么 POST 请求体无法重复读取?

POST 请求体的内容通过输入流(ServletInputStream)读取,而输入流具有一次性读取的特性,核心原因如下:

1. 输入流的 “消费性”

输入流(如 CoyoteInputStream,Tomcat 的实现)本质上是对底层字节流的封装,其读取过程是 “消费性” 的:

  • 读取数据时,流的指针会向后移动;
  • 当流读取完毕(指针到达末尾),再次读取会返回 -1(表示已读完);
  • 流关闭后(如 close() 被调用),无法再次打开或重置。

2. Tomcat 中的具体实现

以 Tomcat 为例,getInputStream() 返回的 CoyoteInputStream 内部依赖 InputBuffer 存储请求体数据。当数据被读取后,InputBuffer 中的缓存会被清空,且流的 closed 状态会被标记为 true

// Tomcat 的 CoyoteInputStream 部分源码
public int read(byte[] b, int off, int len) throws IOException {
    if (closed) { // 若流已关闭,直接抛出异常
        throw new IOException("Stream closed");
    }
    if (checkByteBufferEof()) { // 若数据已读完,返回 -1
        return -1;
    }
    // 读取剩余数据并移动指针
    int n = Math.min(len, bb.remaining());
    bb.get(b, off, n);
    return n;
}
阅读全文 »

Hibernate NonUniqueObjectException 异常深度解析:原因、解决方案与最佳实践

org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session 是 Hibernate 开发中常见的缓存与对象状态冲突异常,核心原因是同一 Session 的一级缓存中,存在两个 OID(主键)相同但内存地址不同的持久化对象,导致 Hibernate 无法判断哪个对象的状态应同步到数据库。本文将从异常根源、复现场景、解决方案及预防措施四个维度,彻底解决该异常。

异常核心原因:Session 缓存的 OID 唯一性约束

Hibernate 一级缓存(Session 缓存)有一个核心规则:同一 Session 中,数据库表的每条记录(对应唯一 OID)只能对应一个持久化对象实例

当出现以下情况时,会触发该异常:

  1. Session 缓存中已存在 OID 为 X 的对象 A(持久化状态);
  2. 程序试图将另一个 OID 也为 X 的对象 B(可能是游离状态或新创建的对象)纳入该 Session 管理(如执行 update()saveOrUpdate()merge() 等操作);
  3. Hibernate 检测到 AB 的 OID 相同但内存地址不同,无法确定以哪个对象的状态同步数据库,从而抛出 NonUniqueObjectException

异常复现场景:常见触发案例

通过具体代码场景复现异常,帮助理解问题本质。

场景 1:重复加载同一 OID 对象后执行更新

阅读全文 »

Tomcat 部署残留问题解决方案:彻底清理旧项目配置

在 Tomcat 部署项目时,有时会遇到 “新项目无法正常启动”“访问路径冲突”“类加载异常” 等问题,这往往是由于旧项目残留配置或文件未清理干净导致的。本文将详细介绍 Tomcat 中可能残留项目的位置及彻底清理的步骤,确保新项目正常部署。

残留文件 / 配置的常见位置

Tomcat 部署项目后,会在多个目录生成缓存文件或配置,旧项目的残留主要集中在以下位置:

webapps 目录:项目部署文件

webapps 是 Tomcat 默认的应用部署目录,若旧项目未彻底删除,可能导致:

  • 路径冲突(如旧项目与新项目上下文路径相同);
  • 类文件残留(导致类加载异常)。

清理步骤

  • 进入 Tomcat 安装目录下的 webapps 文件夹;
  • 删除所有无关的旧项目文件夹(如 oldappdemo 等);
  • 若使用 WAR 包部署,同时删除对应的 .war 文件(避免 Tomcat 自动重新部署)。

work 目录:编译缓存文件

Tomcat 会将 JSP 编译为 Servlet(.class 文件),并在 work 目录中缓存,旧项目的缓存可能导致:

阅读全文 »
0%