6. Web应用程序 MVC[personne] – 版本 2
接下来我们将提供前一个应用程序 [/personne1] 的变体,命名为 [/personne2, /personne3, ...]。这些变体不会改变应用程序的初始架构,其架构仍如下所示:

针对这些变体,我们将简化说明内容,仅介绍相较于上一版本所做的修改。
6.1. 引言
现在,我们计划为应用程序添加会话管理功能。请回顾以下要点:
- HTTP 客户端-服务器对话是由一系列彼此独立的请求-响应序列组成的
- 会话在同一用户的不同请求-响应序列之间起到记忆作用。如果有 N 个用户,则会有 N 个会话。
以下屏幕截图展示了当前应用程序运行时所需的效果:
交易编号 1
![]() | ![]() |
新增内容是返回表单的链接,该链接已添加到视图 [erreurs] 中。
交流第 2 期
![]() | ![]() |
在交互1中,用户为(姓名,年龄)这对数据提供了值(xx,yy)。如果服务器在交互过程中获取了这些值,那么在交互结束时,它会“忘记”这些值。 然而,我们可以发现,在第2次交互中,服务器却能在响应中重新显示这些值。正是“会话”这一概念,使得Web服务器能够在后续的客户端-服务器交互过程中记住数据。当然,还有其他可能的解决方案来解决这个问题。
在第1次交互过程中,服务器会将客户端发送的(姓名,年龄)数据对存储在会话中,以便在第2次交互时显示出来。
以下是另一个在两次交互之间使用会话的示例:
交互 1
![]() | ![]() |
新功能在于回复页面中新增了返回表单的链接。
交流第2期
![]() | ![]() |
6.2. Eclipse 项目
为了创建 Web 应用程序 [/personne2] 的 Eclipse 项目 [mvc-personne-02],我们将复制 Eclipse 项目 [mvc-personne-01] 以保留现有内容。为此,请按以下步骤操作:
[clic droit sur projet mvc-personne-01 -> Copy]:

然后是 [clic droit dans Package Explorer -> Paste]:
![]() | ![]() - 我们将新项目的名称设为 [1],并将一个已存在但为空的文件夹名称设为 [2] |
此时将创建项目 [mvc-personne-02]:

目前它与项目 [mvc-personne-01] 完全相同。在能够使用它之前,我们需要手动进行一些修改。进入视图 [Servers],尝试将这个新应用程序添加到 Tomcat 管理的应用程序中:
![]() | ![]() |
我们可以看到,在 [1] 中,Tomcat 无法识别新项目 [mvc-personne-02]。为了让 Tomcat 识别该项目,需要修改 [mvc-personne-02] 项目的配置文件。 我们使用 [File / Open File] 选项打开 [<mvc-personne-02>/.settings/.component] 文件:
<?xml version="1.0" encoding="UTF-8"?>
<project-modules id="moduleCoreId">
<wb-module deploy-name="mvc-personne-01">
<wb-resource deploy-path="/" source-path="/WebContent"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src"/>
<property name="java-output-path" value="/build/classes/"/>
<property name="context-root" value="personne1"/>
</wb-module>
</project-modules>
第 3 行指定了要在 Tomcat 中部署的 Web 模块名称。此处的名称与项目 [mvc-personne-01] 的名称相同。我们将它改为 [mvc-personne-02]:
<wb-module deploy-name="mvc-personne-02">
此外,我们还可以借此机会修改第7行中[mvc-personne-02]应用程序上下文的名称,因为它与项目[mvc-personne-01]的名称存在冲突:
<property name="context-root" value="personne2"/>
第二项修改可以在Eclipse中直接进行。但我不清楚如何在不通过配置文件的情况下完成第一项修改。
完成上述操作后,我们保存新的 [.content] 文件,然后退出并重新启动 Eclipse,以便使其生效。
Eclipse重启后,让我们尝试执行之前失败的操作:
![]() | ![]() |
这次,[mvc-personne-02] 项目已被成功识别。我们将它添加到配置为由 Tomcat 执行的项目中:

6.3. Web 应用程序 [personne2] 的配置
应用程序 /personne2 的文件 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-02</display-name>
<!-- ServletPersonne -->
<servlet>
<servlet-name>personne</servlet-name>
<servlet-class>
istia.st.servlets.personne.ServletPersonne
</servlet-class>
<init-param>
<param-name>urlReponse</param-name>
<param-value>
/WEB-INF/vues/reponse.jsp
</param-value>
</init-param>
<init-param>
<param-name>urlErreurs</param-name>
<param-value>
/WEB-INF/vues/erreurs.jsp
</param-value>
</init-param>
<init-param>
<param-name>urlFormulaire</param-name>
<param-value>
/WEB-INF/vues/formulaire.jsp
</param-value>
</init-param>
<init-param>
<param-name>urlControleur</param-name>
<param-value>
main
</param-value>
</init-param>
<init-param>
<param-name>lienRetourFormulaire</param-name>
<param-value>
Retour au formulaire
</param-value>
</init-param>
</servlet>
<!-- 映射 ServletPersonne-->
<servlet-mapping>
<servlet-name>personne</servlet-name>
<url-pattern>/main</url-pattern>
</servlet-mapping>
<!-- 欢迎文件 -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
该文件与上一版本完全相同,只是声明了两个新的初始化参数:
- 第 6 行:Web 应用程序的显示名称已更改为 [mvc-personne-02]
- 第 31-36 行:定义了名为 [urlControleur] 的配置参数,该参数指向 URL [main],该 URL 指向 Servlet [ServletPersonne]
- 第 37-42 行:定义了一个名为 [lienRetourFormulaire] 的配置参数,该参数是返回表单的链接文本,来自页面 JSP、[erreurs.jsp] 和 [reponse.jsp]。
主页 [index.jsp] 发生变化:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%
response.sendRedirect("/personne2/main");
%>
- 第 5 行:页面 [index.jsp] 将客户端重定向至应用程序 [/personne2] 的控制器 [ServletPersonne] 的 URL。
6.4. 视图代码
6.4.1. 视图 [formulaire]
该视图与上一版本完全相同:

它由以下页面生成:JSP [formulaire.jsp]:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%
// 从模板中提取数据
String nom=(String)session.getAttribute("nom");
String age=(String)session.getAttribute("age");
String urlAction=(String)request.getAttribute("urlAction");
%>
<html>
<head>
<title>Personne - formulaire</title>
</head>
<body>
<center>
<h2>Personne - formulaire</h2>
<hr>
<form action="<%=urlAction%>" method="post">
<table>
<tr>
<td>Nom</td>
<td><input name="txtNom" value="<%= nom %>" type="text" size="20"></td>
</tr>
<tr>
<td>Age</td>
<td><input name="txtAge" value="<%= age %>" type="text" size="3"></td>
</tr>
<tr>
</table>
<table>
<tr>
<td><input type="submit" value="Envoyer"></td>
<td><input type="reset" value="Rétablir"></td>
<td><input type="button" value="Effacer"></td>
</tr>
</table>
<input type="hidden" name="action" value="validationFormulaire">
</form>
</center>
</body>
</html>
更新内容:
- 第19行,表单现在有一个名为 [action] 的属性,其值为当用户点击类型为 submit 的 [Envoyer] 按钮时,浏览器应将表单值提交到的 URL。 变量 [urlAction] 的值将变为 action="main"。在用户执行以下操作后,将显示视图 [formulaire]:
- 初始请求:GET /personne2/main
- 点击链接 [Retour au formulaire]:GET /personne2/main?action=retourFormulaire
由于 [action] 属性未指定绝对 URL(以 / 开头),而是指定了相对 URL(不以 / 开头),因此浏览器将使用当前显示页面 [/personne2] 的 URL 前缀,并在此基础上附加该相对 URL。 因此,POST 的 URL 将变为 [/personne2/main],即控制器对应的 URL。该请求 POST 将携带第 23、27 和 38 行中的参数 [txtNom, txtAge, action]。
- 第8行:获取模型中[urlAction]元素的值。该值在当前请求的属性中查找,将在第19行使用。
- 第6-7行:获取模型中[nom, age]元素的值。这些值在会话属性中查找,而非像上一版本那样在查询属性中查找。 此举旨在满足视图 [réponse] 和 [erreurs] 链接中 [GET /personne2/main?action=retourFormulaire] 请求的需求。 在显示这两个视图之前,控制器会将表单中输入的数据存入会话,从而在用户使用视图 [réponse] 和 [erreurs] 的链接 [Retour au formulaire] 时,能够检索到这些数据。
6.4.2. 视图 [reponse]
当表单中输入的值有效时,该视图将显示这些值:
![]() | ![]() |
与上一版本相比,新增内容来自链接 [Retour au formulaire]。该视图由以下页面生成:JSP [reponse.jsp]:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%
// 获取模型数据
String nom=(String)request.getAttribute("nom");
String age=(String)request.getAttribute("age");
String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire");
%>
<html>
<head>
<title>Personne</title>
</head>
<body>
<h2>Personne - réponse</h2>
<hr>
<table>
<tr>
<td>Nom</td>
<td><%= nom %>
</tr>
<tr>
<td>Age</td>
<td><%= age %>
</tr>
</table>
<br>
<a href="?action=retourFormulaire"><%= lienRetourFormulaire %></a>
</body>
</html>
- 第31行:返回表单的链接。该链接包含两个部分:
- 目标 [href="?action=retourFormulaire"]。视图 [réponse] 显示在表单 [formulaire.jsp] 的 POST 之后,其 URL 为 [/personne2/main]。 因此,当显示视图 [réponse] 时,浏览器中显示的正是该最后一个 URL。 点击链接 [Retour au formulaire] 时,浏览器将向该链接的 [href] 属性指定的 URL 发起 GET 请求,此处为 "?action=retourFormulaire"。 如果 [href] 中没有 URL,浏览器将使用当前显示视图的 URL,即 c.a.d。[/personne2/main]。 最终,点击链接 [Retour au formulaire] 将触发浏览器向 URL [/personne2/main?action=retourFormulaire] 发送 GET 请求, c.a.d 作为应用程序控制器的 URL,并附带参数 [action] 来指示其应执行的操作。
- 链接的文本。该文本将作为控制器传递给页面的模板的一部分,并在第 10 行被提取。
6.4.3. 视图 [erreurs]
该视图用于报告表单中的输入错误:
![]() | ![]() |
与上一版本相比,新功能来自链接 [Retour au formulaire]。该视图由以下页面生成:JSP [erreurs.jsp]:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@ page import="java.util.ArrayList" %>
<%
// 从模型中提取数据
ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire");
%>
<html>
<head>
<title>Personne</title>
</head>
<body>
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
<%
for(int i=0;i<erreurs.size();i++){
out.println("<li>" + (String) erreurs.get(i) + "</li>\n");
}//用于
%>
</ul>
<br>
<a href="?action=retourFormulaire"><%= lienRetourFormulaire %></a>
</body>
</html>
- 第26行:返回表单的链接。该链接与视图[réponse]中的链接完全相同。建议读者重新阅读该视图的相关说明。
6.5. 视图测试
为了测试上述视图,我们将它们的页面 JSP 复制到 Eclipse 项目的 /WebContent/JSP 文件夹中:

然后在 JSP 文件夹中,对页面进行如下修改:
[formulaire.jsp]:
...
<%
// -- 测试:创建页面模板
session.setAttribute("nom","tintin");
session.setAttribute("age","30");
request.setAttribute("urlAction","main");
%>
<%
// 从模板中获取数据
String nom=(String)session.getAttribute("nom");
String age=(String)session.getAttribute("age");
String urlAction=(String)request.getAttribute("urlAction");
%>
已添加第 4-5 行以创建第 11-13 行所需的模板。
[reponse.jsp]:
<%
// -- 测试:创建页面模板
request.setAttribute("nom","milou");
request.setAttribute("age","10");
request.setAttribute("lienRetourFormulaire","Retour au formulaire");
%>
<%
// 从模板中获取数据
String nom=(String)request.getAttribute("nom");
String age=(String)request.getAttribute("age");
String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire");
%>
第 4-6 行已添加,用于创建第 11-13 行所需的模板。
[erreurs.jsp]:
<%
// -- 测试:创建页面模板
ArrayList<String> erreurs1=new ArrayList<String>();
erreurs1.add("erreur1");
erreurs1.add("erreur2");
request.setAttribute("erreurs",erreurs1);
request.setAttribute("lienRetourFormulaire","Retour au formulaire");
%>
<%
// 从模型中获取数据
ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire");
%>
第 4-8 行已添加,用于创建第 13-14 行页面所需的模板。
如果尚未启动,请启动 Tomcat,然后访问以下 URL:
![]() | ![]() |
![]() |
我们确实得到了预期的视图。
6.6. 控制器 [ServletPersonne]
Web 应用程序 [/personne2] 中的控制器 [ServletPersonne] 将处理以下操作:
编号 | 请求 | 来源 | 处理 |
1 | [GET /personne2/main] | 用户输入的URL | - 发送空的视图 [formulaire] |
2 | [POST /personne2/main] 带参数 [txtNom, txtAge, 操作] 已提交 | 点击按钮 [Envoyer] 视图中的 [formulaire] | - 检查参数值 [txtNom, txtAge] - 若不正确,则发送视图 [erreurs(erreurs)] - 若参数正确,则发送视图 [reponse(nom,age)] |
3 | [GET /personne2/main? action=返回表单] | 点击链接 [返回 表单] 回复]和[erreurs]视图中 | - 发送已预填入最新输入值的视图 [formulaire] |
因此,我们需要处理一个新的操作:[GET /personne2/main?action=retourFormulaire]。
6.6.1. 控制器框架
控制器[ServletPersonne]的骨架与上一版本几乎完全相同:
新增内容:
- 第 4 行:使用会话需要导入 [HttpSession] 包
- 第 28-30 行:新方法 [doRetourFormulaire] 处理新操作:[GET /personne2/main?action=retourFormulaire]。
6.6.2. [init] 控制器的初始化
方法 [init] 与上一版本完全相同。它会检查文件 [web.xml] 中是否包含数组 [paramètres] 中声明的元素:
- 第 5 行:已添加参数 [urlControleur](控制器 URL)和 [lienRetourFormulaire](视图 [réponse] 和 [erreurs] 的链接文本)。
6.6.3. 方法 [doGet]
方法 [doGet] 需处理此前不存在的操作 [GET /personne2/main?action=retourFormulaire]:
- 第 6-14 行:检查初始化错误列表是否为空。若非如此,则显示视图 [erreurs(erreursInitialisation)] 以报告相关错误。
要理解此代码,需回顾视图 [erreurs] 的模板:
<%
// 从模板中获取数据
ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire");
%>
视图 [erreurs] 期望在请求中包含一个名为“errors”的键值对。控制器在第 8 行创建了该键值对。它还期望包含一个名为“lienRetourFormulaire”的键值对。控制器在第 9 行创建了该键值对。此处的链接文本将为空。 因此,在发送的 [erreurs] 视图中将不会包含链接。实际上,如果应用程序初始化时出现错误,则必须重新配置该应用程序。此时无需通过链接提示用户继续使用应用程序。
- 第 34-37 行:处理新操作 [GET /personne2/main?action=retourFormulaire]
6.6.4. 方法 [doInit]
该方法处理第 1 号请求 [GET /personne2/main]。针对此请求,它必须发送空视图 [formulaire(nom,age)]。其代码如下:
- 第 4 行:若当前会话存在则获取,否则创建(来自 getSession 的参数 true)。
- 第 9-10 行:显示视图 [formulaire]。回顾该视图所需的模板:
<%
// 从模型中获取数据
String nom=(String)session.getAttribute("nom");
String age=(String)session.getAttribute("age");
String urlAction=(String)request.getAttribute("urlAction");
%>
- 第 6-7 行:将视图 [formulaire] 模型中的 [nom,age] 元素初始化为空字符串,并将其放入会话中,因为视图正是在此处等待这些元素。
- 第 8 行:模型中的 [urlAction] 元素使用文件 [web.xml] 中 [urlControleur] 参数的值进行初始化,并放入请求中。
6.6.5. 方法 [doValidationFormulaire]
该方法处理第 2 号请求 [POST /personne2/main],其中提交的参数为 [action, txtNom, txtAge]。其代码如下:
- 第 5-6 行:从客户端请求中获取参数“txtNom”和“txtAge”的值。
- 第8-10行:将这些值存储在会话中,以便当用户点击视图[réponse]和[erreurs]中的链接[Retour au formulaire]时能够检索到它们。
- 第12-19行:验证两个参数值的有效性
- 第21-28行:如果其中一个参数有误,则显示视图[erreurs(erreurs,lienRetourFormulaire)]。回顾该视图的模板:
<%
// 从模型中获取数据
ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire");
%>
- 第30-34行:如果获取的两个参数“txtNom”和“txtAge”具有有效值,则显示视图[reponse(nom,age,lienRetourFormulaire)]。 需注意视图 [reponse] 的模板:
<%
// 从模型中检索数据
String nom=(String)request.getAttribute("nom");
String age=(String)request.getAttribute("age");
String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire");
%>
6.6.6. 方法 [doRetourFormulaire]
该方法处理第 3 号请求 [GET /personne2/main?action=retourFormulaire]。其代码如下:
该方法执行完毕后,应显示视图 [formulaire],其中已预先填入用户最近的输入内容。回顾视图 [formulaire] 的模板:
<%
// 从模型中获取数据
String nom=(String)session.getAttribute("nom");
String age=(String)session.getAttribute("age");
String urlAction=(String)request.getAttribute("urlAction");
%>
因此,方法 [doRetourFormulaire] 必须构建上述视图模型。
- 第 4 行:获取控制器中存储了用户输入值(姓名、年龄)的会话。
- 第 7 行:从会话中获取姓名
- 第8-9行:若未存在,则将其作为空值存入。在应用程序的正常运行中,这种情况不应发生,因为操作[retourFormulaire]总是发生在操作[validationFormulaire]之后,即在输入数据存入会话之后。 但会话可能因有效期有限(通常为几十分钟)而过期。此时,第4行会创建一个新会话,该会话中不会包含用户名。因此,需在新会话中设置一个空用户名。
- 第11-13行:年龄字段也采用相同处理方式
- 若忽略会话过期的问题,则第3-13行是多余的。表单中的[nom,age]字段已存在于会话中,因此无需再次将其放入会话。
- 第15行:设定模板中[urlAction]元素的值
6.7. 测试
启动或重启 Tomcat。访问 URL [http://localhost:8080/personne2],然后按照第 6.1 节中的示例继续进行测试。
















