0%

多个请求使用同一个Servlet

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

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

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

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

实现步骤:

  1. 前端请求携带 method 参数
1
2
3
4
5
6
7
8
<!-- 列表页面 -->
<a href="/user?method=list">用户列表</a>
<!-- 添加页面 -->
<a href="/user?method=toAdd">添加用户</a>
<!-- 表单提交 -->
<form action="/user?method=save" method="post">
<!-- 表单内容 -->
</form>
  1. Servlet 中根据 method 分发处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
@WebServlet("/user")
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}

@Override
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),解析路径并通过反射调用对应方法。

实现步骤:

  1. 配置 Servlet 匹配所有 *.do 请求
1
2
3
4
@WebServlet("*.do") // 匹配所有以 .do 结尾的请求
public class UserServlet extends HttpServlet {
// ...
}

或在 web.xml 中配置:

1
2
3
4
5
6
7
8
<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>
  1. 前端请求使用路径区分方法
1
2
3
4
5
6
7
8
<!-- 列表页面 -->
<a href="/user/list.do">用户列表</a>
<!-- 添加页面 -->
<a href="/user/toAdd.do">添加用户</a>
<!-- 表单提交 -->
<form action="/user/save.do" method="post">
<!-- 表单内容 -->
</form>
  1. 解析 URL 路径并通过反射调用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@WebServlet("*.do")
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}

@Override
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 路径

最佳实践

  1. 参数校验:无论哪种方案,都需对请求参数进行校验(如 method 是否为空、路径是否合法),避免空指针或反射异常。
  2. 异常处理:在分发逻辑中添加全局异常捕获,统一返回错误信息,避免直接暴露堆栈。
  3. 命名规范:方案二中,建议方法名与业务操作语义一致(如 listsavedelete),便于团队协作。
  4. 框架借鉴:复杂项目可直接使用 Spring MVC 等框架,其 @RequestMapping 注解本质上是对方案二的高级封装,自动完成 URL 与方法的映射

欢迎关注我的其它发布渠道