什么是Servlet转发及其重要性
Servlet转发是Java Web开发中至关重要的请求处理机制,它允许一个Servlet将请求处理权完全交给另一个Web资源(可能是另一个Servlet、JSP页面或静态资源)。理解转发机制对于构建结构清晰、易于维护的Web应用至关重要。
转发与重定向的本质区别
在深入转发机制之前,我们必须明确转发(Forward)和重定向(Redirect)的根本区别:
特性
转发(Forward)
重定向(Redirect)
工作层面
服务器内部操作
客户端感知操作
请求次数
1次请求
2次请求
URL变化
原始URL保持不变
浏览器地址栏会更新
数据共享
Request范围属性自动共享
需要手动传递参数
性能
更高效(无网络往返)
稍慢(需要额外请求)
应用场景
页面控制器、视图解析
表单提交后跳转
转发的典型应用场景
MVC架构中的控制器-视图模式
:Controller处理业务逻辑后转发到JSP显示
权限验证
:验证失败时转发到登录页面
模块化处理
:将复杂请求分解到多个Servlet处理
视图组合
:将多个片段组合成完整页面
基础转发配置与实现
1. 使用RequestDispatcher进行转发
这是最基础的转发方式,通过
HttpServletRequest
获取
RequestDispatcher
:
@WebServlet(“/user/profile”)
public class UserProfileServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取用户ID参数
String userId = request.getParameter(“id”);
// 业务逻辑处理(示例:从数据库获取用户信息)
User user = getUserFromDatabase(userId);
// 将数据存入request属性中
request.setAttribute(“user”, user);
request.setAttribute(“pageTitle”, “用户资料”);
// 获取RequestDispatcher并转发
RequestDispatcher dispatcher = request.getRequestDispatcher(“/WEB-INF/views/user/profile.jsp”);
dispatcher.forward(request, response);
}
private User getUserFromDatabase(String userId) {
// 模拟数据库查询
User user = new User();
user.setId(userId);
user.setName(“张三”);
user.setEmail(“zhangsan@example.com”);
return user;
}
}
2. web.xml配置方式(传统方式)
对于不使用注解的项目,可以在web.xml中配置Servlet和映射:
3. 转发到JSP页面的最佳实践
重要安全提示
:JSP文件应放在
WEB-INF
目录下,防止直接访问:
项目结构示例:
src/main/webapp/
├── WEB-INF/
│ ├── views/
│ │ ├── user/
│ │ │ ├── profile.jsp
│ │ │ └── list.jsp
│ │ └── admin/
│ │ └── dashboard.jsp
│ └── web.xml
├── css/
├── js/
└── index.jsp
JSP页面示例
(
profile.jsp
):
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
用户资料
返回列表
高级转发技术
1. 包含式转发(Include)
除了完全转发,还可以使用包含方式,将多个资源的输出组合:
@WebServlet(“/composite/page”)
public class CompositePageServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 设置响应内容类型
response.setContentType(“text/html;charset=UTF-8”);
// 获取输出流
PrintWriter out = response.getWriter();
// 输出页面头部
out.println(“
out.println(“
页面头部
“);
// 包含导航栏
RequestDispatcher headerDispatcher = request.getRequestDispatcher(“/WEB-INF/views/fragments/header.jsp”);
headerDispatcher.include(request, response);
// 包含主要内容
RequestDispatcher contentDispatcher = request.getRequestDispatcher(“/WEB-INF/views/main/content.jsp”);
contentDispatcher.include(request, response);
// 输出页面尾部
out.println(“
页面底部 – ” + new Date() + “
“);
out.println(““);
}
}
2. 转发过滤器(Filter)实现预处理
使用过滤器可以在转发前进行预处理:
@WebFilter(“/*”)
public class LoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
long startTime = System.currentTimeMillis();
// 记录请求信息
System.out.println(“请求开始: ” + httpRequest.getRequestURI() +
” – ” + new Date(startTime));
// 继续执行过滤器链(最终会到达Servlet)
chain.doFilter(request, response);
long endTime = System.currentTimeMillis();
System.out.println(“请求结束: ” + httpRequest.getRequestURI() +
” – 耗时: ” + (endTime – startTime) + “ms”);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}
3. 异常处理与错误转发
优雅的异常处理机制:
@WebServlet(“/user/delete”)
public class UserDeleteServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
String userId = request.getParameter(“id”);
// 模拟可能抛出异常的操作
if (userId == null || userId.trim().isEmpty()) {
throw new IllegalArgumentException(“用户ID不能为空”);
}
// 执行删除操作
deleteUser(userId);
// 成功后转发到列表页面
request.setAttribute(“message”, “用户删除成功”);
request.getRequestDispatcher(“/user/list”).forward(request, response);
} catch (Exception e) {
// 异常处理:转发到错误页面
request.setAttribute(“errorMessage”, e.getMessage());
request.setAttribute(“errorTime”, new Date());
request.getRequestDispatcher(“/WEB-INF/views/error/error.jsp”).forward(request, response);
}
}
private void deleteUser(String userId) {
// 模拟删除操作
if (“admin”.equals(userId)) {
throw new SecurityException(“不能删除管理员账户”);
}
}
}
数据共享机制详解
1. Request作用域数据共享
Request作用域是最常用的共享机制,数据只在当前请求链中有效:
@WebServlet(“/request/scope/demo”)
public class RequestScopeDemoServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 设置各种类型的数据
request.setAttribute(“stringData”, “Hello World”);
request.setAttribute(“numberData”, 12345);
request.setAttribute(“objectData”, new User(“u001”, “Alice”));
request.setAttribute(“listData”, Arrays.asList(“A”, “B”, “C”));
// 转发到显示页面
request.getRequestDispatcher(“/WEB-INF/views/scope/show.jsp”).forward(request, response);
}
}
显示页面
(
show.jsp
):
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
Request作用域数据展示
- 字符串: ${stringData}
- 数字: ${numberData}
- 对象: ${objectData.name} (ID: ${objectData.id})
- 列表:
- ${item}
2. Session作用域数据共享
当需要跨多个请求保持数据时使用Session:
@WebServlet(“/session/login”)
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter(“username”);
String password = request.getParameter(“password”);
// 验证用户(简化示例)
if (“admin”.equals(username) && “123456”.equals(password)) {
// 登录成功,创建session
HttpSession session = request.getSession();
session.setAttribute(“currentUser”, new User(“u001”, username));
session.setAttribute(“loginTime”, new Date());
session.setMaxInactiveInterval(30 * 60); // 30分钟
// 转发到首页
request.getRequestDispatcher(“/home”).forward(request, response);
} else {
// 登录失败,返回登录页
request.setAttribute(“error”, “用户名或密码错误”);
request.getRequestDispatcher(“/login.jsp”).forward(request, response);
}
}
}
3. Application作用域数据共享
全局数据共享(整个Web应用):
@WebServlet(“/app/init”)
public class AppInitServlet extends HttpServlet {
@Override
public void init() throws ServletException {
// 在Servlet初始化时加载全局数据
ServletContext context = getServletContext();
// 设置应用级配置
context.setAttribute(“appVersion”, “1.0.0”);
context.setAttribute(“appStartTime”, new Date());
// 加载配置文件
Properties config = new Properties();
try {
config.load(getServletContext().getResourceAsStream(“/WEB-INF/config.properties”));
context.setAttribute(“appConfig”, config);
} catch (IOException e) {
throw new ServletException(“无法加载配置文件”, e);
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 显示应用信息
ServletContext context = getServletContext();
String version = (String) context.getAttribute(“appVersion”);
Date startTime = (Date) context.getAttribute(“appStartTime”);
request.setAttribute(“version”, version);
request.setAttribute(“startTime”, startTime);
request.getRequestDispatcher(“/WEB-INF/views/app/info.jsp”).forward(request, response);
}
}
4. 转发中的数据传递模式
模式1:Controller-Service-DAO分层转发
// Controller
@WebServlet(“/product/list”)
public class ProductListController extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 调用Service层
ProductService service = new ProductService();
List
// 设置数据并转发到视图
request.setAttribute(“products”, products);
request.setAttribute(“total”, products.size());
request.getRequestDispatcher(“/WEB-INF/views/product/list.jsp”).forward(request, response);
}
}
// Service层(简单示例)
public class ProductService {
public List
// 实际应该调用DAO层
List
list.add(new Product(“P001”, “笔记本电脑”, 5999.00));
list.add(new Product(“P002”, “智能手机”, 2999.00));
return list;
}
}
转发性能优化与最佳实践
1. 转发性能考虑
避免循环转发
:
// 错误示例:可能导致StackOverflowError
@WebServlet(“/bad/example”)
public class BadExampleServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 危险!如果转发目标还是自己,会导致无限循环
request.getRequestDispatcher(“/bad/example”).forward(request, response);
}
}
// 正确做法:设置检查机制
@WebServlet(“/safe/example”)
public class SafeExampleServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 检查转发计数器
Integer count = (Integer) request.getAttribute(“_forwardCount”);
if (count == null) count = 0;
if (count > 3) {
// 达到最大转发次数,直接输出错误
response.sendError(500, “转发次数过多”);
return;
}
request.setAttribute(“_forwardCount”, count + 1);
// 正常业务逻辑…
request.getRequestDispatcher(“/target”).forward(request, response);
}
}
2. 使用转发器模式(Dispatcher Pattern)
创建集中式转发管理器:
/**
* 转发管理器 – 统一处理转发逻辑
*/
public class ForwardManager {
private static final Map
static {
// 配置视图映射
VIEW_MAP.put(“user.list”, “/WEB-INF/views/user/list.jsp”);
VIEW_MAP.put(“user.detail”, “/WEB-INF/views/user/detail.jsp”);
VIEW_MAP.put(“error.page”, “/WEB-INF/views/error/error.jsp”);
}
public static void forward(HttpServletRequest request,
HttpServletResponse response,
String viewKey) throws ServletException, IOException {
String viewPath = VIEW_MAP.get(viewKey);
if (viewPath == null) {
throw new IllegalArgumentException(“未知视图: ” + viewKey);
}
// 可以在这里添加统一的逻辑,如日志、性能监控等
long start = System.currentTimeMillis();
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
long duration = System.currentTimeMillis() – start;
System.out.println(“转发到 ” + viewKey + ” 耗时: ” + duration + “ms”);
}
}
// 使用示例
@WebServlet(“/user/list”)
public class UserListServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 业务逻辑…
List
request.setAttribute(“users”, users);
// 使用管理器转发
ForwardManager.forward(request, response, “user.list”);
}
}
3. 转发与AJAX请求处理
注意
:AJAX请求不能直接转发到JSP,需要返回JSON数据:
@WebServlet(“/api/user/info”)
public class UserApiServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String userId = request.getParameter(“id”);
// 检查是否是AJAX请求
String requestedWith = request.getHeader(“X-Requested-With”);
boolean isAjax = “XMLHttpRequest”.equals(requestedWith);
if (isAjax) {
// AJAX请求:返回JSON
response.setContentType(“application/json;charset=UTF-8”);
PrintWriter out = response.getWriter();
User user = new User(userId, “张三”);
out.print(“{\”id\”:\”” + user.getId() + “\”,\”name\”:\”” + user.getName() + “\”}”);
out.flush();
} else {
// 普通请求:转发到JSP
request.setAttribute(“user”, new User(userId, “张三”));
request.getRequestDispatcher(“/WEB-INF/views/user/info.jsp”).forward(request, response);
}
}
}
完整示例:用户管理系统
1. 项目结构
UserManagementSystem/
├── src/
│ └── com/
│ └── example/
│ ├── model/
│ │ └── User.java
│ ├── servlet/
│ │ ├── UserListServlet.java
│ │ ├── UserDetailServlet.java
│ │ └── UserEditServlet.java
│ └── filter/
│ └── AuthFilter.java
└── web/
├── WEB-INF/
│ ├── views/
│ │ ├── user/
│ │ │ ├── list.jsp
│ │ │ ├── detail.jsp
│ │ │ └── edit.jsp
│ │ └── error/
│ │ └── 403.jsp
│ └── web.xml
└── index.jsp
2. 完整代码实现
User.java
(模型类)
package com.example.model;
import java.io.Serializable;
public class User implements Serializable {
private String id;
private String name;
private String email;
public User() {}
public User(String id, String name) {
this.id = id;
this.name = name;
}
// Getters and Setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
AuthFilter.java
(权限过滤器)
package com.example.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebFilter(“/user/*”)
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpSession session = httpRequest.getSession(false);
// 检查用户是否登录
if (session == null || session.getAttribute(“currentUser”) == null) {
// 未登录,转发到错误页面
request.setAttribute(“errorMessage”, “请先登录系统”);
request.getRequestDispatcher(“/WEB-INF/views/error/403.jsp”).forward(request, response);
return;
}
// 已登录,继续执行
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}
UserListServlet.java
(列表页面)
package com.example.servlet;
import com.example.model.User;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
@WebServlet(“/user/list”)
public class UserListServlet extends HttpServlet {
// 模拟数据库数据
private static final List
new User(“u001”, “张三”),
new User(“u002”, “李四”),
new User(“u003”, “王五”)
);
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取查询参数
String keyword = request.getParameter(“keyword”);
// 业务逻辑:搜索用户
List
if (keyword != null && !keyword.isEmpty()) {
filteredUsers = USERS.stream()
.filter(u -> u.getName().contains(keyword))
.collect(java.util.stream.Collectors.toList());
}
// 设置数据
request.setAttribute(“users”, filteredUsers);
request.setAttribute(“keyword”, keyword);
request.setAttribute(“totalCount”, filteredUsers.size());
// 转发到列表页面
request.getRequestDispatcher(“/WEB-INF/views/user/list.jsp”).forward(request, response);
}
}
UserDetailServlet.java
(详情页面)
package com.example.servlet;
import com.example.model.User;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
@WebServlet(“/user/detail”)
public class UserDetailServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String userId = request.getParameter(“id”);
// 查找用户
User user = findUserById(userId);
if (user == null) {
// 用户不存在,转发到错误页面
request.setAttribute(“errorMessage”, “用户不存在: ” + userId);
request.getRequestDispatcher(“/WEB-INF/views/error/404.jsp”).forward(request, response);
return;
}
// 设置数据并转发
request.setAttribute(“user”, user);
request.getRequestDispatcher(“/WEB-INF/views/user/detail.jsp”).forward(request, response);
}
private User findUserById(String id) {
// 模拟数据库查询
return UserListServlet.USERS.stream()
.filter(u -> u.getId().equals(id))
.findFirst()
.orElse(null);
}
}
list.jsp
(用户列表页面)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
用户管理系统
共找到 ${totalCount} 个用户
| ID | 姓名 | 操作 |
|---|---|---|
| ${user.id} | ${user.name} | 查看详情 |
返回首页
detail.jsp
(用户详情页面)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
用户详情
ID: ${user.id}
姓名: ${user.name}
邮箱: ${user.email}
403.jsp
(权限错误页面)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
访问被拒绝
${errorMessage}
index.jsp
(首页)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
欢迎使用用户管理系统
login.jsp
(登录页面)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
用户登录
${error}
3. 部署与测试
web.xml
(完整配置)
调试与故障排除
1. 常见问题及解决方案
问题1:转发后数据丢失
// 错误:在转发前调用了response.getWriter()
PrintWriter out = response.getWriter();
out.println(“Some output”);
// 下面这行会抛出 IllegalStateException
request.getRequestDispatcher(“/target”).forward(request, response);
// 正确:不要在转发前向响应写入任何内容
request.getRequestDispatcher(“/target”).forward(request, response);
问题2:URL路径问题
// 错误:使用绝对路径可能导致问题
request.getRequestDispatcher(“/WEB-INF/views/user/list.jsp”).forward(request, response);
// 更好的做法:使用上下文路径
String contextPath = request.getContextPath();
// 但注意:RequestDispatcher的路径总是相对于上下文根的
问题3:中文乱码
// 在转发前设置编码
request.setCharacterEncoding(“UTF-8”);
response.setContentType(“text/html;charset=UTF-8”);
// 或者使用过滤器统一处理
@WebFilter(“/*”)
public class EncodingFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
request.setCharacterEncoding(“UTF-8”);
response.setContentType(“text/html;charset=UTF-8”);
chain.doFilter(request, response);
}
}
2. 调试技巧
添加调试信息
:
public class DebugForwardServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 打印当前请求信息
System.out.println(“=== 转发调试信息 ===”);
System.out.println(“请求URI: ” + request.getRequestURI());
System.out.println(“请求参数: ” + request.getQueryString());
System.out.println(“Request作用域属性: “);
Enumeration
while (names.hasMoreElements()) {
String name = names.nextElement();
System.out.println(” ” + name + ” = ” + request.getAttribute(name));
}
// 执行转发
RequestDispatcher dispatcher = request.getRequestDispatcher(“/target”);
System.out.println(“准备转发到: /target”);
dispatcher.forward(request, response);
System.out.println(“转发完成”);
}
}
总结
Servlet转发是Java Web开发的核心技术,掌握它需要理解:
基础概念
:转发 vs 重定向,RequestDispatcher的使用
数据共享
:Request、Session、Application作用域的合理使用
高级技巧
:包含式转发、过滤器配合、异常处理
性能优化
:避免循环转发,使用转发器模式
最佳实践
:WEB-INF保护、编码处理、路径管理
通过本文的完整示例和详细说明,你应该能够构建结构清晰、易于维护的Web应用。记住,良好的转发机制是MVC架构成功的关键!