ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Servlet与JSP核心机制及请求转发与重定向详解

Servlet与JSP核心机制及请求转发与重定向详解

1. Servlet与JSP的核心机制解析

作为Java Web开发的基石技术,Servlet和JSP这对黄金组合已经服务了企业级应用开发二十余年。我在实际项目中最深刻的体会是:理解它们的协作机制,远比单纯记忆API重要得多。

Servlet本质是用Java编写的服务器端程序,通过实现javax.servlet.Servlet接口来处理HTTP请求。当Tomcat接收到请求时,会根据web.xml配置或注解找到对应的Servlet类,调用其service()方法。这个过程中最关键的三个对象是:

  • HttpServletRequest:封装所有请求信息(参数、头信息等)
  • HttpServletResponse:提供响应控制方法(设置状态码、头信息等)
  • ServletConfig:包含Servlet初始化参数

而JSP在运行时会被容器编译为Servlet(查看Tomcat的work目录可见生成的.java文件),这种设计使得它既能享受Servlet的所有能力,又能用类HTML的语法简化视图开发。例如一个简单的hello.jsp:

<%@ page contentType="text/html;charset=UTF-8" %> <html> <head><title>JSP示例</title></head> <body> <% String name = request.getParameter("name"); %> <h1>你好, <%= name != null ? name : "访客" %>!</h1> </body> </html>

实际上会被转换为类似这样的Servlet代码:

out.write("<html>"); String name = request.getParameter("name"); out.write("<h1>你好, " + (name != null ? name : "访客") + "!</h1>"); out.write("</html>");

关键经验:在Tomcat的conf/web.xml中,默认配置了JSPServlet来处理所有*.jsp请求。这也是为什么直接访问JSP文件能正常执行的原因。

2. 请求转发的实现原理与应用场景

请求转发(Forward)是服务器内部的行为,通过RequestDispatcher的forward()方法实现。我在电商项目中常用来实现MVC的控制器到视图的跳转:

// 在Servlet中获取商品数据 List<Product> products = productService.getFeaturedItems(); request.setAttribute("featuredProducts", products); // 转发到展示页面 RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/views/products.jsp"); dispatcher.forward(request, response);

转发过程有三个重要特性:

  1. 全程在服务器内部完成,客户端浏览器感知不到
  2. 共享同一个request对象(可通过setAttribute/getAttribute传递数据)
  3. URL地址栏不会变化(仍是原始请求的URL)

避坑指南:转发路径必须以斜杠开头,且是相对于当前Web应用的路径。使用WEB-INF目录保护JSP是常见安全实践,避免用户直接访问视图文件。

典型应用场景包括:

  • 权限检查不通过时转发到登录页
  • 控制器准备数据后转发到视图页
  • 多层Servlet之间的协作处理

3. 重定向的运行机制与实战技巧

重定向(Redirect)则是完全不同的机制,通过HttpServletResponse的sendRedirect()实现:

// 用户提交订单后重定向到结果页 response.sendRedirect(request.getContextPath() + "/order/success");

与转发相比,重定向的特点是:

  1. 服务端返回302状态码和Location头,由浏览器发起新请求
  2. 会产生两次完整的HTTP请求
  3. URL地址栏会显示最终地址
  4. 不共享request对象(可通过session或URL参数传递数据)

重要细节:sendRedirect()的路径如果以斜杠开头表示相对于服务器根路径,通常需要加上request.getContextPath()获取应用上下文路径。

实际开发中常见的应用场景:

  • 表单提交后防止重复提交(Post-Redirect-Get模式)
  • 登录成功后跳转到目标页面
  • 旧URL迁移到新URL时保持兼容

我曾遇到一个经典问题:在过滤器中处理编码设置时,如果先调用getParameter()再sendRedirect(),会导致Tomcat的GET参数乱码。这是因为:

  1. 第一次请求调用getParameter()触发了参数解析
  2. 此时若未设置request.setCharacterEncoding(),会使用默认编码
  3. 重定向后的第二次请求虽然设置了编码,但第一次解析已经出错

解决方案是:在过滤器中优先设置编码,或者将参数通过URL编码后放在重定向URL中。

4. 转发与重定向的深度对比

通过一个对比表格可以清晰看出两者的区别:

特性请求转发(Forward)重定向(Redirect)
发起方服务端内部服务端指令+客户端执行
请求次数1次2次
URL变化不变变为新URL
数据共享通过request属性需手动传递(session/参数)
速度更快(无额外网络开销)较慢(二次请求)
目标资源限制必须属于当前Web应用可以是任意URL
典型应用场景MVC中的视图渲染防止表单重复提交

在性能敏感的场景下,转发通常是更好的选择。比如在电商网站的商品详情页:

  • 使用转发可以保持原始URL(如/items/123)
  • 同时在后端灵活组合多个Servlet的处理结果
  • 避免暴露真实的JSP路径(如/WEB-INF/views/item_detail.jsp)

而重定向更适合需要改变URL的场景,比如用户登录流程:

  1. 提交登录表单到/login(POST)
  2. 验证成功后重定向到/home(防止刷新重复提交POST)
  3. 未登录访问受限页时重定向到/login?redirect=/target

5. 常见问题排查与调试技巧

在实际开发中,有几个高频出现的异常值得特别关注:

问题1:Tomcat multipart解析失败

failed to parse multipart servlet request

这通常发生在文件上传时,可能的原因包括:

  • 未正确配置multipart-config
  • 请求内容类型不是multipart/form-data
  • 临时目录不可写

解决方案示例:

@WebServlet(urlPatterns = "/upload", multipartConfig = @MultipartConfig( location = "/tmp", maxFileSize = 1024 * 1024 * 5)) public class UploadServlet extends HttpServlet { // servlet实现 }

问题2:JSP未生成Java文件当发现Tomcat的work目录没有生成对应的JSP编译文件时,可以检查:

  1. 确认JSP访问触发了编译(首次访问或有修改时)
  2. 检查Tomcat日志是否有编译错误
  3. 验证文件权限(Tomcat用户需要有work目录写权限)
  4. 检查JSP文件是否有语法错误

问题3:重定向循环

ERR_TOO_MANY_REDIRECTS

这是最让人头疼的问题之一,通常由于:

  • 登录检查逻辑缺陷
  • URL规则配置冲突
  • 浏览器缓存了错误的跳转

调试建议:

  1. 使用开发者工具查看Network面板中的重定向链
  2. 在过滤器中打印请求URL和跳转目标
  3. 清除浏览器Cookie和缓存测试

对于Linux系统日志重定向等运维需求,可以采用如下Shell脚本方案:

#!/bin/bash LOG_DIR=/var/log/myapp mkdir -p $LOG_DIR exec > >(tee -a "${LOG_DIR}/app.log") 2>&1

6. 高级应用:结合Spring框架的实践

在现代Java Web开发中,虽然直接使用Servlet/JSP的场景变少,但理解这些底层机制对使用Spring MVC等框架大有裨益。例如:

Spring中的转发与重定向

@Controller public class ProductController { // 转发到视图 @GetMapping("/products") public String listProducts(Model model) { model.addAttribute("products", productService.getAll()); return "productList"; // 对应/WEB-INF/views/productList.jsp } // 重定向 @PostMapping("/orders") public String createOrder(OrderForm form) { orderService.create(form); return "redirect:/orders/" + order.getId(); } }

RESTful设计中的状态码运用

  • 301/302:临时/永久重定向
  • 303:See Other(POST后推荐使用)
  • 307:Temporary Redirect(保持方法不变)

在微服务架构中,这些基础概念同样适用。比如API网关的重定向、服务间的请求转发等,本质上都是这些核心模式的变体。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进