10. MVC Web 应用程序 [person] – 版本 5
10.1. 简介
在此版本中,我们进行了两项更改:
第一项改动涉及客户端如何向服务器指示其希望执行的操作。此前,这通常通过客户端 GET 或 POST 请求中的 [action] 参数来指定。在此版本中,操作将由客户端请求的 URL 的最后一个元素指定,如下所示:

在 1 中,表单提交的目标 URL 是 [/person5/do/validateForm]。正是 URL 的最后一个元素 [validateForm] 使控制器能够识别应执行的操作。 在 2 中,由 [返回表单] 链接触发的 POST 请求被发送到了 URL [/person5/do/returnForm]。同样,URL 的最后一个元素 [returnForm] 告知控制器应执行哪个操作。
我们引入这一变更,是因为这是 Struts 或 Spring MVC 等最广泛使用的 Web 开发框架所采用的方法。
应用程序中的所有 URL 都将采用 [/person5/do/action] 的格式。[/person5] 应用程序的 [web.xml] 文件将指定其接受 [/do/*] 格式的 URL:
<servlet-mapping>
<servlet-name>personne</servlet-name>
<url-pattern>/do/*</url-pattern>
</servlet-mapping>
控制器将按以下方式获取要执行的操作的名称:
[request] 对象的 [getPathInfo] 方法返回请求 URL 的最后一个元素。
第二个改动涉及用户输入在请求/响应周期之间的存储方式。目前,这些信息存储在会话中。如果用户众多且每个用户需要存储的大量数据,这种方法可能会存在弊端。 事实上,每个用户都有自己独立的会话。此外,除非提供了注销选项,否则会话在用户注销后仍会保持活跃一段时间。因此,1,000 个各占 100 字节的会话将占用 1 MB 的内存。这仍属于中等需求,且很少有应用程序会同时保持 1,000 个活跃会话。
尽管如此,仍有比会话更节省内存的替代方案,了解这些方案很有必要。在此,我们将采用 Cookie 方法。让我们通过一个示例来说明这一点。
步骤 1:用户提交表单:
![]() |
此请求/响应循环导致客户端与服务器之间发生以下 HTTP 交互:
这是一个标准的 POST 请求。这里没有什么特别值得注意的地方,只是尽管我们不打算使用会话,Web 服务器还是会创建一个。这从第 11 行浏览器发回给服务器的会话令牌中可以明显看出,该令牌是它之前从服务器接收到的。
我们可以看到,在第 3 行和第 4 行,向客户端浏览器发送了 [Set-Cookie] HTTP 头,一个用于名称(第 3 行),一个用于年龄(第 4 行)。这些 Cookie 的值即为上文 POST 1 请求第 14 行中提交的值。
步骤 2:返回表单

这一请求/响应循环导致客户端与服务器之间发生以下 HTTP 交互:
这里我们看到点击 [返回表单] 链接触发的 POST 请求。在第 11 行,我们可以看到浏览器使用 HTTP [Cookie] 头将收到的 Cookie [name, age, JSESSIONID] 发回给服务器。这就是 Cookie 的工作原理。客户端将服务器发送给它的 Cookie 发回给服务器。 在此示例中,控制器将接收 [pauline, 18] 这些值,并必须将其填入 2 中显示的 [form] 视图的 [txtName, txtAge] 字段中。
这里没有什么特别需要注意的地方,除了服务器在此响应中未发送任何 Cookie 这一事实。这并不会阻止浏览器在接下来的交互中将从服务器接收到的所有 Cookie 发回,即使这毫无意义。因此,我们以增加客户端/服务器交互中的字符流量为代价,减轻了服务器可用内存的负担。
10.2. Eclipse 项目
要为 Web 应用程序 [/personne5] 创建 Eclipse 项目 [mvc-personne-05],请按照第 6.2 节所述的步骤复制 [mvc-personne-04] 项目。
![]() | ![]() |
10.3. 配置 [personne5] Web 应用程序
/personne5 应用程序的 web.xml 文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>mvc-personne-05</display-name>
<!-- ServletPersonne -->
<servlet>
<servlet-name>personne</servlet-name>
<servlet-class>
istia.st.servlets.personne.ServletPersonne
</servlet-class>
...
</servlet>
<!-- Mapping ServletPersonne-->
<servlet-mapping>
<servlet-name>personne</servlet-name>
<url-pattern>/do/*</url-pattern>
</servlet-mapping>
<!-- welcome files -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
除了一些细节外,此文件与上一版本中的文件完全相同:
- 第 6 行:Web 应用程序的显示名称已更改为 [mvc-personne-05]
- 第 18 行:应用程序处理的 URL 格式为 [/do/*]。此前仅处理 [/main] 这一 URL。现在,可处理的 URL 数量与待处理的操作数量相同。
主页 [index.jsp] 发生如下变化:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<c:redirect url="/do/formulaire"/>
- 第 5 行:[index.jsp] 页面将客户端重定向至 URL [/person5/do/form],这相当于要求控制器执行 [form] 操作。
10.4. 视图代码
视图 [表单、响应、错误] 的变化非常小。唯一的改变是,待执行的操作不再像以前那样通过提交表单中名为 [action] 的隐藏字段来指定。现在,它是在提交表单的目标 URL 中定义的,即在 <form> 标签的 [action] 属性中:
[form.jsp]:
...
<html>
<head>
<title>Personne - formulaire</title>
<script language="javascript">
...
</script>
</head>
<body>
<center>
<h2>Personne - formulaire</h2>
<hr>
<form name="frmPersonne" action="validationFormulaire" method="post">
...
</form>
</center>
</body>
</html>
- 第 [13] 行:表单的 [action] 参数在缺席了前几个版本一段时间后再次出现。要理解此处该属性的值,请记住应用程序处理的所有 URL 都采用 [/do/action] 的形式。在第 [13] 行中,[action] 属性的值是一个相对 URL(不以 / 开头)。 因此,浏览器会将其补全为当前显示页面的 URL,该 URL 必然采用 [/do/action] 这种形式。最后一个元素将被 <form> 标签中 [action] 属性的相对 URL 替换,从而生成 [/do/validationFormulaire] 作为 POST 目标。
- 隐藏的 [action] 字段已消失
[response.jsp]:
...
<html>
...
<body>
...
<form name="frmPersonne" action="retourFormulaire" method="post">
</form>
<a href="javascript:document.frmPersonne.submit();">
${lienRetourFormulaire}
</a>
</body>
</html>
- 第 [7] 行:POST 请求的目标将是 [/do/returnForm]
- 第 7–8 行中,表单中的隐藏字段 [action] 已被移除。
[errors.jsp]:
...
<html>
...
<body>
...
<form name="frmPersonne" action="retourFormulaire" method="post">
</form>
<a href="javascript:document.frmPersonne.submit();">
${lienRetourFormulaire}
</a>
</body>
</html>
- 第 [6] 行:POST 目标将为 [/do/returnForm]
- 第 6–7 行中,表单中的隐藏字段 [action] 已被移除。
欢迎读者参照前几个版本中的方法,测试这些新视图。
10.5. [ServletPersonne] 控制器
[/personne5] Web 应用程序的 [ServletPersonne] 控制器将处理以下操作:
否。 | 请求 | 来源 | 处理 |
1 | [GET /person5/do/form] | 用户输入的 URL | - 发送空的 [form] 视图 |
2 | [POST /person5/do/formValidation] 并携带参数 [txtName, txtAge] 已提交 | 点击 [提交] 按钮 [表单] | - 检查参数 [txtName, txtAge] 的值 - 若值不正确,则发送 [errors(errors)] 视图 - 若值正确,则返回 [response(name,age)] 视图 |
3 | [POST /person5/do/returnForm] 不带提交参数 | 点击 [返回 表单] [响应] 中,并查看 [错误]。 | - 发送已预先填入最新输入值的 [表单] 视图 |
[ServletPersonne] 控制器的骨架与上一版本完全相同。我们将回顾对 [doValidationFormulaire、doRetourFormulaire、doGet] 方法所做的修改;[init、doInit、doPost] 方法保持不变。
10.5.1. [doGet] 方法
[doGet] 方法检索待执行操作的方式与以前的版本不同:
- 第 12 行:获取待执行的操作。其形式为 [/action]。
- 第 18–22 行:处理由 GET 请求发起的 [/form] 操作
- 第 23–27 行:处理由 POST 请求发起的 [/validateForm] 操作
- 第 28–32 行:处理由 POST 请求发起的 [/returnForm] 操作
10.5.2. [doValidationFormulaire] 方法
该方法处理请求 #2 [POST /person5/do/validationFormulaire],其中提交的元素包含 [txtName, txtAge]。其代码如下:
新增内容:
- [doValidationFormulaire] 方法返回视图 [response, errors] 中的一个。无论返回何种响应,控制器都会在其内部设置两个 Cookie(第 8–9 行)。Cookie 由 [Cookie] 对象表示,其构造函数接受两个参数:Cookie 键及其关联的值。
- 第 8 行:名称字段输入的值被存入键名为 "name" 的 Cookie 中
- 第 9 行:年龄字段的输入值被放入键名为 "age" 的 Cookie 中
- 通过 [response.addCookie] 方法,将 Cookie 添加到发送给客户端的 HTTP 响应中。此处仅对响应进行准备,实际发送将在发送给客户端的视图 JSP 页面执行时才发生。
10.5.3. [doRetourFormulaire] 方法
该方法处理请求 #2 [POST /person5/do/retourFormulaire],且不包含任何提交的数据。其代码如下:
新增内容:
[doRetourFormulaire] 方法应显示一个预先填入了最新输入内容的表单。在之前的版本中,这些数据存储在会话中。在本版本中,我们不再使用会话,而是使用 Cookie 来存储客户端与服务器之间交互的数据。 当客户端请求表单验证时,会根据情况收到 [response] 或 [errors] 视图,同时还会收到两个标记为“name”和“age”的 Cookie。当点击这两个视图上的 [返回表单] 链接(这会触发对 URL [/do/retourFormulaire] 的 POST 请求)时,浏览器会将收到的这两个 Cookie 发回给服务器。
- 第 4–18 行:我们提取标记为“name”和“age”的 Cookie 的值。奇怪的是,没有方法可以根据 Cookie 的键来获取其值。因此,我们必须遍历收到的每个 Cookie。
- 完成此操作后,将获取的两个值放入 [form] 视图模板中(第 20–21 行),以便其进行显示。
10.6. 测试
将 Eclipse 项目 [person-mvc-05] 集成到 Tomcat 后,启动或重启 Tomcat,然后访问 URL [http://localhost:8080/personne5]。


