5. MVC Web 应用程序 [person] – 版本 1
5.1. 应用程序视图
该应用程序使用了前几个示例中的表单。应用程序的首页如下所示:

我们将此视图称为 [form] 视图。如果输入内容正确,它们将显示在一个名为 [response] 的视图中:

如果输入有误,错误信息将显示在一个名为 [errors] 的视图中:

5.2. 应用程序架构
[person1] Web 应用程序将采用以下架构:

这是一种单层架构:没有 [业务] 或 [DAO] 层,只有 [Web] 层。[ServletPersonne] 是应用程序控制器,负责处理所有客户端请求。为了响应这些请求,它会使用三个视图 [form、response、errors] 中的一个。
我们需要确定 [ServletPersonne] 控制器在收到用户请求时,如何决定应采取的操作。客户端请求是一个 HTTP 流,其内容会因使用 GET 还是 POST 命令而有所不同。
GET 请求
在此情况下,HTTP 数据流如下所示:
第 1 行指定请求的 URL,例如:
该 URL 可用于指定要执行的操作。可以使用多种方法:
- URL 参数用于指定操作,例如 [/app?action=add&id=4]。在此,[action] 参数告知控制器当前请求的是哪个操作。
- URL 的最后一个元素指定操作,例如 [/app/add?id=4]。在此,URL 的最后一个元素 [/add] 由控制器用于确定其必须执行的操作。
还有其他解决方案。上述两种是常见的。
POST 请求
在这种情况下,HTTP 流程如下所示:
第 1 行指定了请求的 URL,例如:
该 URL 可用于指定要执行的操作,这与 GET 请求类似。在 GET 请求中,[action] 参数包含在 URL 中。此处同样适用,例如:
不过,[action] 参数也可以包含在提交的参数中(如上文第 15 行所示),例如:
接下来,我们将使用这些不同的技术来告诉控制器该做什么:
- 在请求的 URL 中包含 action 参数:
- 提交 action 参数:
- 将 URL 的最后一个元素用作操作名称:
5.3. Eclipse 项目
要为 Web 应用程序 [personne1] 创建 Eclipse 项目 [mvc-personne-01],请按照第 3.1 节中的步骤操作。

我们将不保留默认上下文 [mvc-personne-01]。我们将如下所示选择 [personne1]:

结果如下:

若需更改 Web 应用程序上下文,请使用以下选项:[右键单击项目 -> 属性 -> J2EE]:

在 [1] 中输入新的上下文。
我们将在 [WEB-INF] 文件夹中创建一个名为 [vues] 的子文件夹:[右键单击 WEB-INF -> 新建 -> 文件夹]:
![]() | ![]() |
新项目现在如下所示:

项目完成后将呈现如下效果:

- [ServletPersonne] 控制器位于 [src] 文件夹中
- 用于视图 [form、response、errors] 的 JSP 页面位于 [WEB-INF/vues] 文件夹中,这阻止了用户直接访问它们,如下例所示:

接下来我们将介绍 [/personne1] Web 应用程序的各个组件。建议读者在阅读过程中逐步创建这些组件。
5.4. [person1] Web 应用程序的配置
/person1 应用程序的 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-01</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>
</servlet>
<!-- Mapping ServletPersonne-->
<servlet-mapping>
<servlet-name>personne</servlet-name>
<url-pattern>/main</url-pattern>
</servlet-mapping>
<!-- welcome files -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
这个配置文件说明了什么?
- 第 34–37 行:URL /main 由名为 person 的 Servlet 处理
- 第 10–13 行:名为“person”的 Servlet 是 [ServletPersonne] 类的实例
- 第 14–19 行:定义了一个名为 [urlResponse] 的配置参数。这是 [response] 视图的 URL。
- 第 20–25 行:定义了一个名为 [urlErrors] 的配置参数。这是 [errors] 视图的 URL。
- 第 26–31 行:定义名为 [urlForm] 的配置参数。这是 [form] 视图的 URL。
- 第 40 行:[index.jsp] 将作为应用程序的首页。
用于 [form、response、errors] 视图的 JSP 页面的 URL 均由配置参数定义。这样,在移动这些页面时无需重新编译应用程序。
当用户请求 URL [/person1] 时,[index.jsp] 文件将发送响应(主页文件,第 40 行)。该文件位于 [WebContent] 文件夹的根目录下:

其内容如下:
<%@ 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("/personne1/main");
%>
[index.jsp] 页面仅将客户端重定向至 URL [/person1/main]。因此,当浏览器请求 URL [/person1] 时,[index.jsp] 会向其发送以下 HTTP 响应:
- 第 1 行:HTTP/1.1 响应,用于指示服务器重定向到另一个 URL
- 第 4 行:浏览器应重定向到的 URL
收到此响应后,浏览器将按照指示(第 4 行)请求 URL [/person1/main]。[/person1] 应用程序的 [web.xml] 文件指定此请求将由 [ServletPersonne] 控制器处理(第 35–36 行)。
5.5. 视图代码
我们从创建视图开始编写 Web 应用程序。这些视图有助于定义用户在图形界面方面的需求,并且可以在不依赖控制器的情况下进行测试。
5.5.1. [表单]视图
此视图用于显示用于输入姓名和年龄的表单:

HTML 类型 | 名称 | 角色 | |
<input type="text"> | txtName | 输入姓名 | |
<input type="text"> | txtAge | 输入年龄 | |
<input type="submit"> | 将输入的值发送至 URL /person1/main 的服务器 | ||
<input type="reset"> | 将页面恢复到浏览器最初接收时的状态 | ||
<input type="button"> | 用于清除输入字段 [1] 和 [2] 的内容 |
该按钮由 JSP 页面 [formulaire.jsp] 生成。其模板包含以下元素:
- [name]:在与会话属性键“name”关联的会话属性中找到的名称(字符串)
- [age]:在与会话属性键“age”关联的数据中找到的年龄(字符串)
当用户请求 URL [/person1/main](即 [ServletPersonne] 控制器的 URL)时,将获取 [form] 视图。生成 [form] 视图的 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">
<%
// on récupère les données du modèle
String nom=(String)request.getAttribute("nom");
String age=(String)request.getAttribute("age");
%>
<html>
<head>
<title>Personne - formulaire</title>
</head>
<body>
<center>
<h2>Personne - formulaire</h2>
<hr>
<form 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>
- 第6-7行:JSP页面首先从请求中获取其模型的[name, age]元素。在正常应用程序运行中,控制器[ServletPersonne]将构建此模型。
- 第18-38行:JSP页面生成一个HTML表单(<form>标签)
- 第 18 行:<form> 标签未指定 action 属性,用于指定处理 [Submit] 按钮(第 32 行)提交的值的 URL。 表单值将被提交至获取表单的 URL,即 [ServletPersonne] 控制器的 URL。因此,该控制器既用于生成最初由 GET 请求请求的空表单,也用于处理通过 [Submit] 按钮提交的输入数据。
- 提交的值来自 HTML 字段 [txtName](第 22 行)、[txtAge](第 26 行)和 [action](第 37 行)。最后一个参数将使控制器知道它需要做什么。
- 表单首次显示时,输入字段 [txtName] 和 [txtAge] 分别通过变量 [name](第 22 行)和 [age](第 26 行)进行初始化。 这些变量从请求属性(第 6–7 行)中获取其值,而这些属性已知是由 Servlet 初始化的。因此,是 Servlet 设置了表单输入字段的初始内容。
- 第 33 行:类型为 [reset] 的 [Reset] 按钮将表单恢复到浏览器接收它时的状态。
- 第 34 行:类型为 [reset] 的 [Clear] 按钮目前没有功能。
今后,当需要同时指定视图名称及其模型时,我们将把此视图称为 [form(name, age)] 视图。此外,请注意,当用户点击 [Submit] 按钮时,[txtName, txtAge] 参数会被提交到 URL [/person1/main]。
5.5.2. [response]视图
当表单中输入的值有效时,该视图会显示这些值:

该视图由 JSP 页面 [response.jsp] 生成。其模板包含以下元素:
- [name]:在会话属性中找到的名称(字符串),与键“name”相关联
- [age]:一个年龄(字符串),该值将出现在会话属性中,与键“age”相关联
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">
<%
// on récupère les données du modèle
String nom=(String)request.getAttribute("nom");
String age=(String)request.getAttribute("age");
%>
<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>
</body>
</html>
- 第 6-7 行:JSP 页面首先从请求中获取其模型的 [name, age] 元素。在应用程序的正常运行过程中,[ServletPersonne] 控制器将构建此模型。
- 随后,模型元素 [name, age] 分别在第 20 行和第 24 行显示出来
此后,我们将此视图称为 [response(name, age)] 视图。
5.5.3. [errors]视图
该视图以以下形式报告输入错误:

它由 JSP 页面 [errors.jsp] 生成。其模型由以下元素组成:
- [errors]:一个错误消息列表(ArrayList),这些消息将出现在请求属性中,并关联键“errors”
JSP 页面 [errors.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" %>
<%
// on récupère les données du modèle
ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
%>
<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");
}//for
%>
</ul>
</body>
</html>
- 第 8 行:JSP 页面首先从请求中的模型中检索 [errors] 元素。该元素表示一个包含 String 元素的 ArrayList 对象。这些元素即为错误消息。在应用程序正常运行期间,[ServletPersonne] 控制器将构建此模型。
- 第 18–22 行:显示错误消息列表。为此,我们需要在页面的 HTML 主体中编写 Java 代码。我们应始终尽量减少此类代码,以避免 HTML 代码杂乱无章。稍后我们将看到,有方法可以减少 JSP 页面中的 Java 代码量。
- 第 4 行:请注意 JSP 页面所需包的 import 标签
我们将此视图称为 [errors(errors)] 视图。
5.6. 视图测试
即使尚未编写控制器,也可以测试 JSP 页面的有效性。为此,必须满足两个条件:
- 必须能够直接从应用程序请求这些页面,而无需通过控制器
- JSP 页面必须自行初始化模型,该模型通常由控制器构建
要执行这些测试,我们将视图的 JSP 页面复制到 Eclipse 项目的 [/WebContent/JSP] 文件夹中:

然后,在 JSP 文件夹中,对页面进行如下修改:
[form.jsp]:
...
<%
// -- test : on crée le modèle de la page
request.setAttribute("nom","tintin");
request.setAttribute("age","30");
%>
<%
// on récupère les données du modèle
String nom=(String)request.getAttribute("nom");
String age=(String)request.getAttribute("age");
%>
<html>
<head>
...
第3至7行是新增的,用于生成第11至12行所需的模板。
[reponse.jsp]:
...
<%
// -- test : on crée le modèle de la page
request.setAttribute("nom","milou");
request.setAttribute("age","10");
%>
<%
// on récupère les données du modèle
String nom=(String)request.getAttribute("nom");
String age=(String)request.getAttribute("age");
%>
<html>
<head>
...
第3至7行是添加的,用于创建第11至12行中页面所需的模板。
[errors.jsp]:
...
<%
// -- test : on crée le modèle de la page
ArrayList<String> erreurs1=new ArrayList<String>();
erreurs1.add("erreur1");
erreurs1.add("erreur2");
request.setAttribute("erreurs",erreurs1);
%>
<%
// on récupère les données du modèle
ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
%>
<html>
<head>
...
第3至9行是新增的,用于构建第13行所需的模型。
如果尚未启动 Tomcat,请先启动,然后访问以下 URL:
![]() | ![]() |
![]() |
我们得到了预期的视图。既然我们对应用程序的 JSP 页面已有相当程度的信心,就可以继续编写其控制器 [ServletPersonne] 了。
5.7. [ServletPersonne] 控制器
剩下的就是编写 Web 应用程序的核心:控制器。其作用是:
- 获取客户端的请求,
- 处理客户端请求的操作,
- 返回相应的视图。
[ServletPersonne] 控制器将处理以下操作:
请求 | 请求 | origin | 处理 |
1 | [GET /person1/hand] | 用户输入的 URL | - 发送空的 [form] 视图 |
2 | [POST /person1/hand] 并携带参数 [txtName, txtAge, action] | 点击 [提交] 按钮 [表单] | - 检查参数 [txtName, txtAge] 的值 - 如果答案错误,则返回视图 [errors(errors)] - 如果正确,则返回 [response(name,age)] 视图 |
当用户请求 URL [/person1/main] 时,应用程序便会启动。根据应用程序的 [web.xml] 文件(参见第 5.4 节),该请求由 ServletPersonne 类的实例处理,下面我们将对此进行说明。
5.7.1. 控制器框架
[ServletPersonne] 控制器的代码如下:
- 第 20–22 行:Servlet 首次加载时执行的 [init] 方法
- 第 25–28 行:当向应用程序发出 GET 请求时,Web 服务器调用的 [doGet] 方法
- 第 42–46 行:当向应用程序发出 POST 请求时,Web 服务器调用的 [doPost] 方法。如图所示,这也将由 [doGet] 方法(第 45 行)处理。
- 第 31–33 行:[doInit] 方法处理操作 #1 [GET /person1/main]
- 第 36–39 行:[doValidationFormulaire] 方法处理操作 #2 [POST /person1/main],并处理提交的参数 [txtName, txtAge, action]。
接下来我们将详细介绍控制器中的各种方法
5.7.2. 控制器初始化
当 Servlet 容器加载控制器类时,会执行其 [init] 方法。此过程仅发生一次。一旦加载到内存中,控制器便驻留于此并处理来自不同客户端的请求。每个客户端由独立的执行线程处理,因此控制器的方法会由不同线程同时执行。请注意,正因如此,控制器绝不能包含其方法可能修改的字段。其字段必须为只读。 这些字段由 [init] 方法初始化,这也是该方法的主要作用。该方法具有一个独特特征:仅由单个线程执行一次。因此,在此方法内部,对控制器字段的并发访问不会产生问题。[init] 方法的目的是初始化 Web 应用程序所需的对象,这些对象将以只读模式被所有客户端线程共享。这些共享对象可以放置在两个位置:
- 控制器的私有字段
- 应用程序的执行上下文(ServletContext)
[ServletPersonne] 控制器 [init] 方法的代码如下:
- 第 16 行:获取 Web 应用程序配置,即 [web.xml] 文件的内容
- 第 19–29 行:获取 Servlet 初始化参数,其名称在第 9 行的 [parameters] 数组中定义
- 第 21 行:获取参数值
- 第 25 行:如果参数不存在,则将该错误添加到最初为空的 [initializationErrors] 错误列表中(第 8 行)。
- 第 28 行:如果参数存在,则将其与值一起存储在初始为空的 [params] 字典中(第 10 行)。
- 第 31–35 行:[urlErrors] 参数必须存在,因为它指定了能够显示任何初始化错误的 [errors] 视图的 URL。如果该参数不存在,则通过抛出 [ServletException] 来终止应用程序(第 33 行)。
5.7.3. [doGet] 方法
[doGet] 方法处理发送到 Servlet 的 GET 和 POST 请求,因为 [doPost] 方法会重定向到 [doGet] 方法。其代码如下:
- 第 18–25 行:我们检查初始化错误列表是否为空。如果不是,则显示 [errors(initializationErrors)] 视图,该视图将报告错误。
要理解这段代码,你需要回顾 [errors] 视图模板:
<%
// on récupère les données du modèle
ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
%>
[errors] 视图期望请求中存在名为“errors”的键。控制器在第 20 行创建了该键。
- 第 28 行:我们获取客户端用于发起请求的 [get] 或 [post] 方法
- 第 30 行:从请求中获取 [action] 参数的值。请注意,在我们的应用程序中,只有请求 #2 [POST /person1/main] 包含 [action] 参数。在此请求中,该参数的值为 [validationFormulaire]。
- 第 31–34 行:如果 [action] 参数不存在,则将其值设为 "init"。初始请求 #1 [GET /person1/main] 即属于这种情况。
- 第 36–40 行:处理请求 #1 [GET /person1/main]。
- 第 41–45 行:处理请求 #2 [POST /person1/main]。
- 第 47 行:如果前两种情况均不适用,则按情况 #1 进行处理
5.7.4. [doInit] 方法
该方法处理请求 #1 [GET /person1/main]。对于此请求,它必须返回空的 [form(name, age)] 视图。其代码如下:
- 第 18-19 行:显示 [form] 视图。回顾一下该视图所期望的模型:
<%
// on récupère les données du modèle
String nom=(String)request.getAttribute("nom");
String age=(String)request.getAttribute("age");
%>
- 第 16-17 行:[form] 视图的 [name, age] 模型被初始化为空字符串。
5.7.5. [doValidationForm] 方法
该方法处理请求 #2 [POST /person1/main],其中提交的参数为 [action, txtName, txtAge]。其代码如下:
- 第 16-17 行:从客户端请求中获取 "txtNom" 和 "txtAge" 参数的值。
- 第 19-26 行:检查这两个参数的有效性
- 第 28-33 行:如果任一参数不正确,则显示 [errors(callErrors)] 视图。回顾该视图的模板:
<%
// on récupère les données du modèle
ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
%>
- 第 35-38 行:如果检索到的两个参数“txtNom”和“txtAge”具有有效值,则显示 [response(name, age)] 视图。回顾 [response] 视图的模板:
<%
// on récupère les données du modèle
String nom=(String)request.getAttribute("nom");
String age=(String)request.getAttribute("age");
%>
5.8. 测试
让我们按照第 3.3 节所述的步骤,将 [mvc-personne-01] 项目添加到 Tomcat 应用程序中:

启动 Tomcat。完成后,我们可以继续进行第 5.1 节中作为示例展示的测试。我们还可以添加更多测试。例如,我们可以从 web.xml 中移除一个 urlXXX 配置参数,并观察结果。如下所示,[web.xml] 中已将其中一个参数注释掉:
<!--
<init-param>
<param-name>urlFormulaire</param-name>
<param-value>
/WEB-INF/vues/formulaire.jsp
</param-value>
</init-param>
-->
我们启动或重启 Tomcat,并请求 URL [http://localhost:8080/personne1/main]。我们得到以下响应:





