1. Servlet请求处理机制解析
Servlet作为Java EE的核心组件,承担着Web应用请求处理的第一道防线。当HTTP请求到达服务器时,Servlet容器会创建HttpServletRequest和HttpServletResponse对象,这两个对象贯穿整个请求生命周期。
1.1 HttpServletRequest深度剖析
HttpServletRequest对象封装了客户端请求的所有信息,开发中最常用的方法包括:
java复制// 获取请求参数
String username = request.getParameter("username");
// 获取请求头信息
String userAgent = request.getHeader("User-Agent");
// 获取请求路径
String requestURI = request.getRequestURI();
// 获取会话对象
HttpSession session = request.getSession();
实际开发中需要注意的几个关键点:
- getParameter()方法只能获取URL参数和表单数据,对于JSON格式的请求体需要使用getInputStream()读取
- 请求参数编码问题:建议在Filter中统一设置request.setCharacterEncoding("UTF-8")
- 对于文件上传请求,需要检查contentType是否以"multipart/"开头
1.2 HttpServletResponse核心功能
HttpServletResponse对象用于构建服务器响应,常见操作包括:
java复制// 设置响应内容类型
response.setContentType("text/html;charset=UTF-8");
// 设置响应头
response.setHeader("Cache-Control", "no-cache");
// 获取输出流
PrintWriter out = response.getWriter();
out.println("<h1>Hello World</h1>");
// 设置状态码
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
重要提示:在获取Writer或OutputStream之前必须先设置contentType,否则编码设置可能失效。这两个输出流也不能同时使用。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 请求转发机制详解
请求转发(Forward)是Servlet协作的重要方式,整个过程在服务器内部完成:
java复制RequestDispatcher dispatcher = request.getRequestDispatcher("/targetServlet");
dispatcher.forward(request, response);
2.1 转发的工作原理
- 客户端发送请求到Servlet A
- Servlet A通过RequestDispatcher将请求转发给Servlet B
- Servlet B处理请求并生成响应
- 响应返回给客户端
整个过程对客户端透明,浏览器地址栏不会变化。转发时请求对象和响应对象会传递给目标Servlet,所有请求参数和属性保持不变。
2.2 实际应用场景
- MVC模式中的控制器转发:
java复制// 在Controller中
request.setAttribute("userList", userService.getAllUsers());
request.getRequestDispatcher("/WEB-INF/views/userList.jsp").forward(request, response);
- 权限检查链:
java复制if(!checkPermission(request)) {
request.getRequestDispatcher("/noPermission").forward(request, response);
return;
}
- 业务逻辑拆分:
java复制// 第一阶段处理
processStage1(request, response);
// 转发到第二阶段
if(request.getAttribute("needStage2")) {
request.getRequestDispatcher("/stage2").forward(request, response);
}
3. 重定向机制深度解析
重定向(Redirect)是客户端跳转的典型实现:
java复制response.sendRedirect("/newLocation");
3.1 重定向技术细节
服务器返回302状态码和Location头,浏览器会自动发起新请求。与转发不同,重定向具有以下特点:
- 浏览器地址栏会变化
- 会产生两次HTTP请求
- 请求参数不会自动传递
- 可以跳转到外部URL
3.2 参数传递方案
由于重定向会丢失原始请求参数,常用解决方案有:
- URL参数拼接:
java复制String username = request.getParameter("username");
response.sendRedirect("/welcome?username=" + URLEncoder.encode(username, "UTF-8"));
- 使用Session临时存储:
java复制request.getSession().setAttribute("tempParams", params);
response.sendRedirect("/nextStep");
- 使用Flash作用域(需要框架支持)
3.3 永久重定向实现
301永久重定向需要手动设置:
java复制response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
response.setHeader("Location", "/newUrl");
4. 转发与重定向的抉择
4.1 核心区别对比
| 特性 | 转发(Forward) | 重定向(Redirect) |
|---|---|---|
| 请求次数 | 1次 | 至少2次 |
| URL变化 | 不变 | 变化 |
| 目标限制 | 只能站内 | 可以外部 |
| 请求属性 | 保留 | 丢失 |
| 性能 | 更高 | 较低 |
4.2 选择策略建议
使用转发的场景:
- 需要保持请求属性的MVC流程
- 服务器内部组件协作
- 需要隐藏实际资源路径
使用重定向的场景:
- 表单提交后防止重复提交
- 需要跳转到外部网站
- 登录后跳转到原始请求页面
- 应用重构时的URL迁移
5. 实战中的常见问题
5.1 典型错误排查
- 转发后再调用response.getWriter()
java复制request.getRequestDispatcher("/A").forward(request, response);
response.getWriter().println("extra content"); // 会抛IllegalStateException
- 重定向前未清除缓冲区
java复制response.getWriter().write("some content");
response.sendRedirect("/newUrl"); // 可能失败
- 路径问题:
- 转发使用服务器路径(/开头表示应用上下文根)
- 重定向需要包含应用上下文路径
5.2 性能优化技巧
- 对于静态资源跳转使用301重定向
- 避免重定向链条(A→B→C)
- 转发链不宜过长(Tomcat默认限制20次)
- 合理使用缓存头减少重定向请求
5.3 安全注意事项
- 重定向URL校验:
java复制String url = request.getParameter("redirectUrl");
if(!url.startsWith("/") || url.contains("//")) {
throw new SecurityException("Invalid redirect URL");
}
- 敏感操作后必须重定向(防重复提交)
- 避免将用户输入直接用于重定向(防钓鱼)
6. 高级应用场景
6.1 动态转发策略
java复制String target = calculateTarget(request);
RequestDispatcher dispatcher = request.getRequestDispatcher(target);
if(dispatcher != null) {
dispatcher.forward(request, response);
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
6.2 条件重定向逻辑
java复制if(isMobile(request)) {
response.sendRedirect("/mobile"+request.getRequestURI());
} else if(needsLogin(request)) {
response.sendRedirect("/login?returnUrl="+URLEncoder.encode(request.getRequestURI(), "UTF-8"));
} else {
request.getRequestDispatcher("/defaultHandler").forward(request, response);
}
6.3 混合使用模式
java复制// 第一阶段:权限检查
if(!checkPermission(request)) {
response.sendRedirect("/login");
return;
}
// 第二阶段:业务处理
processRequest(request, response);
// 第三阶段:视图渲染
request.getRequestDispatcher("/view/"+getViewName(request)).forward(request, response);
在实际项目中,我曾遇到一个典型的URL迁移场景:当我们需要将/user/profile改为/account/profile时,正确的做法是:
- 实现新的/account/profile处理逻辑
- 在原有/user/profile处设置301重定向
- 监控日志确保所有客户端都迁移到新URL
- 经过足够长时间后移除旧URL处理
这种渐进式迁移方案既能保证用户体验,又能最终完成架构演进。
