多个请求使用同一个Servlet
Java Web 中多个请求共享同一个 Servlet 的实现方案
在 Java Web 开发中,多个请求共用同一个 Servlet 是常见需求(如一个模块的增删改查操作)。这种方式可避免创建过多 Servlet 类,简化代码结构。本文将详细介绍两种主流实现方案:基于请求参数的方法分发和基于 URL 路径的反射调用。
方案一:基于请求参数的方法分发
通过在 URL 中添加 method 参数(如 ?method=add 或 ?method=delete),在 Servlet 中根据参数值分发到不同处理方法。
实现步骤:
- 前端请求携带 method 参数
<!-- 列表页面 -->
<a href="/user?method=list">用户列表</a>
<!-- 添加页面 -->
<a href="/user?method=toAdd">添加用户</a>
<!-- 表单提交 -->
<form action="/user?method=save" method="post">
<!-- 表单内容 -->
</form>
- Servlet 中根据 method 分发处理
public class UserServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 获取 method 参数
String method = request.getParameter("method");
if (method == null || method.isEmpty()) {
response.getWriter().write("请指定 method 参数");
return;
}
// 根据 method 分发到对应方法
switch (method) {
case "list":
list(request, response); // 处理列表查询
break;
case "toAdd":
toAdd(request, response); // 跳转到添加页面
break;
case "save":
save(request, response); // 保存用户
break;
case "delete":
delete(request, response); // 删除用户
break;
default:
response.getWriter().write("不支持的 method:" + method);
}
}
// 处理用户列表查询
private void list(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 业务逻辑:查询用户列表
request.setAttribute("users", userService.findAll());
request.getRequestDispatcher("/user/list.jsp").forward(request, response);
}
// 跳转到添加页面
private void toAdd(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.getRequestDispatcher("/user/add.jsp").forward(request, response);
}
// 保存用户
private void save(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 业务逻辑:保存用户
String username = request.getParameter("username");
userService.save(new User(username));
response.sendRedirect(request.getContextPath() + "/user?method=list");
}
// 删除用户
private void delete(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 业务逻辑:删除用户
int id = Integer.parseInt(request.getParameter("id"));
userService.delete(id);
response.sendRedirect(request.getContextPath() + "/user?method=list");
}
}
优点:
- 实现简单,逻辑清晰,适合小型项目;
- 前端请求易于编写,参数直观。
缺点:
- URL 中暴露方法名,安全性较低;
- 方法过多时,
switch语句会变得冗长,不易维护。
方案二:基于 URL 路径的反射调用
通过 web.xml 或注解配置 *.do 通配符匹配请求,根据 request.getServletPath() 获取 URL 路径(如 /user/add.do),解析路径并通过反射调用对应方法。
实现步骤:
- 配置 Servlet 匹配所有 *.do 请求
// 匹配所有以 .do 结尾的请求
public class UserServlet extends HttpServlet {
// ...
}
或在 web.xml 中配置:
<servlet>
<servlet-name>UserServlet</servlet-name>
<servlet-class>com.example.UserServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UserServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
- 前端请求使用路径区分方法
<!-- 列表页面 -->
<a href="/user/list.do">用户列表</a>
<!-- 添加页面 -->
<a href="/user/toAdd.do">添加用户</a>
<!-- 表单提交 -->
<form action="/user/save.do" method="post">
<!-- 表单内容 -->
</form>
- 解析 URL 路径并通过反射调用方法
public class UserServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 1. 获取请求路径(如 /user/list.do)
String servletPath = request.getServletPath();
// 2. 截取方法名(如从 /user/list.do 中提取 list)
int start = servletPath.lastIndexOf("/") + 1;
int end = servletPath.lastIndexOf(".do");
String methodName = servletPath.substring(start, end);
try {
// 3. 通过反射获取对应的方法
Method method = getClass().getDeclaredMethod(
methodName,
HttpServletRequest.class,
HttpServletResponse.class
);
// 4. 调用方法
method.invoke(this, request, response);
} catch (NoSuchMethodException e) {
response.getWriter().write("不支持的操作:" + methodName);
} catch (Exception e) {
e.printStackTrace();
response.getWriter().write("操作失败:" + e.getMessage());
}
}
// 处理用户列表查询(方法名需与 URL 中的路径匹配)
private void list(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setAttribute("users", userService.findAll());
request.getRequestDispatcher("/user/list.jsp").forward(request, response);
}
// 跳转到添加页面
private void toAdd(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.getRequestDispatcher("/user/add.jsp").forward(request, response);
}
// 保存用户
private void save(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter("username");
userService.save(new User(username));
response.sendRedirect(request.getContextPath() + "/user/list.do");
}
// 删除用户
private void delete(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
int id = Integer.parseInt(request.getParameter("id"));
userService.delete(id);
response.sendRedirect(request.getContextPath() + "/user/list.do");
}
}
优点:
- URL 路径更规范(如
/user/list.do),隐藏方法名,安全性更高; - 无需手动维护
switch语句,新增方法只需添加对应函数,扩展性好。
缺点:
- 反射调用存在一定性能损耗(小型项目可忽略);
- 方法名与 URL 路径强绑定,需严格遵循命名规范。
两种方案的对比与选择
| 特性 | 方案一(基于 method 参数) | 方案二(基于 URL 路径 + 反射) |
|---|---|---|
| 实现复杂度 | 低(switch 分发) | 中(反射调用) |
| 代码可维护性 | 低(方法多时分支冗长) | 高(新增方法无需修改分发逻辑) |
| 安全性 | 低(暴露方法名) | 高(路径隐藏实现细节) |
| 性能 | 高(直接调用) | 略低(反射开销) |
| 适用场景 | 小型项目、快速开发 | 中大型项目、需规范 URL 路径 |
最佳实践
- 参数校验:无论哪种方案,都需对请求参数进行校验(如
method是否为空、路径是否合法),避免空指针或反射异常。 - 异常处理:在分发逻辑中添加全局异常捕获,统一返回错误信息,避免直接暴露堆栈。
- 命名规范:方案二中,建议方法名与业务操作语义一致(如
list、save、delete),便于团队协作。 - 框架借鉴:复杂项目可直接使用 Spring MVC 等框架,其
@RequestMapping注解本质上是对方案二的高级封装,自动完成 URL 与方法的映射