Skip to content

5. Web 应用程序 MVC [personne] – 版本 1

5.1. 应用程序的视图

该应用程序沿用了前几个示例中使用的表单。应用程序的首页如下:

Image

我们将此视图命名为 [formulaire]。如果输入正确,这些数据将显示在名为 [réponse] 的视图中:

Image

如果输入有误,错误将在名为 [erreurs] 的视图中显示:

Image

5.2. 应用程序架构

Web 应用程序 [personne1] 将采用以下架构:

Image

该架构为单层架构:不存在 [métier] 或 [dao] 层,仅有一个 [web] 层。 [ServletPersonne] 是应用程序的控制器,负责处理所有客户端请求。为响应这些请求,它会调用三个视图之一:[formulaire, réponse, erreurs]。

我们需要确定控制器 [ServletPersonne] 如何在收到用户请求时确定应采取的操作。 客户端请求是一个 HTTP 流,其形式取决于请求是通过 GET 命令还是 POST 命令发出的。

请求 GET

在这种情况下,HTTP数据流如下所示:

GET /URL HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
[ligne vide]

第 1 行指定了请求的 URL,例如:

GET /personne1/ HTTP/1.1

可以使用该 URL 来指定要执行的操作。可以使用多种方法:

  1. URL中的参数指定操作,例如 [/appli?action=ajouter&id=4]。在此,参数 [action] 向控制器指示所请求的操作。
  2. URL 的最后一个元素指定操作,例如 [/appli/ajouter?id=4]。在此,控制器使用 URL 的最后一个元素 [/ajouter] 来确定应执行的操作。

还有其他解决方案。前两种方案较为常见。

请求 POST

在此情况下,HTTP 的处理流程如下:

POST /URL HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://localhost:8080/personne1/main
Cookie: JSESSIONID=9F5E5BFA29643FC6B1601EEED907E1F9
Content-Type: application/x-www-form-urlencoded
Content-Length: 43
[ligne vide]
txtNom=&txtAge=&action=validationFormulaire

第 1 行指定了请求的 URL,例如:

POST /personne1/main HTTP/1.1

可以使用该 URL 来指定要执行的操作,例如 GET。 对于 GET,参数 [action] 被整合到了 URL 中。此处的情况也可能与此类似,例如:

POST /appli?action=ajouter&id=4 HTTP/1.1

但参数 [action] 也可能包含在已发布的参数中(如上文第 15 行),例如:

POST /appli HTTP/1.1
...
[ligne vide]
action=ajouter&id=4

接下来,我们将使用这些不同的方法来指示控制器应执行的操作:

  • 将参数 action 嵌入请求的 URL 中:
POST /appli?action=ajouter&id=4 HTTP/1.1
  • 提交参数 action
POST /appli HTTP/1.1
...
[ligne vide]
action=ajouter&id=4
  • 使用 URL 的最后一个元素作为操作名称:
POST /appli/ajouter?id=4 HTTP/1.1

5.3. Eclipse 项目

要为 Web 应用程序 [personne1] 创建 Eclipse 项目 [mvc-personne-01],请按照第 3.1 节中描述的步骤进行操作。

Image

请勿保留默认提供的 [mvc-personne-01] 上下文。请如下所示选择 [personne1]:

Image

最终结果如下:

Image

如果需要更改Web应用程序的上下文,请使用选项[clic droit sur projet -> Properties -> J2EE]:

Image

在 [1] 中指定新的上下文。

我们将在文件夹 [WEB-INF] 中创建子文件夹 [vues]:[clic droit sur WEB-INF -> New -> Folder]:

现在的新项目如下:

Image

完成后,项目将变为:

Image

  • 控制器 [ServletPersonne] 位于文件夹 [src] 中
  • 视图 [formulaire, réponse, erreurs] 对应的页面 JSP 位于文件夹 [WEB-INF/vues] 中,这导致用户无法像下例所示那样直接请求这些页面:

Image

接下来我们将介绍 Web 应用程序 [/personne1] 的各个组件。建议读者在阅读过程中逐步创建这些组件。

5.4. Web 应用程序 [personne1] 的配置

应用程序 /personne1 中的文件 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>
    <!--  映射 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>

该配置文件说明了什么?

  • 第 34-37 行:URL /main 由名为 personne 的 Servlet 处理
  • 第 10-13 行:名为 personne 的 Servlet 是类 [ServletPersonne] 的一个实例
  • 第14-19行:定义了一个名为[urlReponse]的配置参数。这是视图[réponse]的URL。
  • 第 20-25 行:定义了一个名为 [urlErreurs] 的配置参数。这是视图 [erreurs] 的 URL。
  • 第 26-31 行:定义了一个名为 [urlFormulaire] 的配置参数。这是视图 [formulaire] 的 URL。
  • 第 40 行:[index.jsp] 将作为应用程序的首页。

视图 [formulaire, réponse, erreurs] 中的页面 JSP 的 URL 各自对应一个配置参数。这样就可以在不重新编译应用程序的情况下移动它们。

当用户请求 URL [/personne1] 时,将由文件 [index.jsp] 返回响应(主页文件,第 40 行)。该文件位于文件夹 [WebContent] 的根目录下:

Image

其内容如下:


<%@ 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 [/personne1/main]。 因此,当浏览器请求 URL [/personne1] 时,[index.jsp] 会向其发送以下来自 HTTP 的响应:

1
2
3
4
5
6
7
HTTP/1.x 302 Déplacé Temporairement
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=9F5E5BFA29643FC6B1601EEED907E1F9; Path=/personne1
Location: http://localhost:8080/personne1/main
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 0
Date: Thu, 18 May 2006 15:45:23 GMT
  • 第 1 行:HTTP/1.1 响应,指示服务器重定向至另一个 URL
  • 第4行:浏览器应重定向到的URL

收到此响应后,浏览器将按要求(第4行)请求URL [/personne1/main]。 应用程序 [/personne1] 中的文件 [web.xml] 表明,该请求将由控制器 [ServletPersonne] 处理(第 35-36 行)。

5.5. 视图代码

我们从编写 Web 应用程序的视图开始。这些视图有助于明确用户在图形界面方面的需求,并且可以在没有控制器的情况下进行测试。

5.5.1. 视图 [formulaire]

该视图用于显示姓名和年龄的输入表单:

Image

类型 HTML
名称
角色
1
<input type="text">
txtNom
输入姓名
2
<input type="text">
txtAge
输入年龄
3
<input type="submit">
 
将输入的值发送至服务器,URL为 /personne1/main
4
<input type="reset">
 
将页面恢复至浏览器最初接收时的状态
5
<input type="button">
 
用于清除输入字段 [1] 和 [2] 的内容

该页面由 JSP [formulaire.jsp] 生成。其模板由以下元素组成:

  • [nom]:在会话属性中找到的名称(字符串),与键“nom”相关联
  • [age]:在会话属性中找到的年龄(字符串),与键“age”相关联

当用户请求 URL [/personne1/main]、c.a.d 时,将生成视图 [formulaire]。控制器 URL 为 [ServletPersonne]。 生成视图 [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)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 首先从请求 [request] 中获取其模型中的 [nom, age] 元素。 在应用程序的正常运行中,将由控制器 [ServletPersonne] 构建该模型。
  • 第 18-38 行:页面 JSP 将生成一个表单 HTML(标签 <form>)
  • 第 18 行:<form> 标签缺少 action 属性,该属性用于指定处理由 [Envoyer] 按钮(类型为 submit,见第 32 行)提交的值的 URL。 因此,表单的值将被提交至获取该表单的 URL,即控制器 [ServletPersonne] 的 URL。 因此,该控制器既用于生成最初由 GET 请求的空表单,也用于处理通过 [Envoyer] 按钮提交的已输入数据。
  • 提交的值来自字段 HTML、[txtNom](第 22 行)、[txtAge](第 26 行)和 [action](第 37 行)。 最后一个参数将使控制器知道该执行什么操作。
  • 在表单初始显示时,输入字段 [txtNom, txtAge] 分别由变量 [nom](第 22 行)和 [age](第 26 行)初始化。 这些变量的属性值来源于请求(第6-7行),而这些属性已知是由Servlet初始化的。因此,正是Servlet确定了表单输入字段的初始内容。
  • 第 33 行:类型为 [reset] 的按钮 [Rétablir] 可将表单恢复至浏览器接收时的状态。
  • 第 34 行:类型为 [reset] 的按钮 [Effacer] 目前尚无功能。

此后,当我们需要同时指定视图名称及其模板时,将称此视图为 [formulaire(nom, age)]。 此外,需注意当用户点击按钮 [Envoyer] 时,参数 [txtNom, txtAge] 将被提交至 URL [/personne1/main]。

5.5.2. 视图 [reponse]

当表单中输入的值有效时,该视图将显示这些值:

Image

该视图由页面 JSP [reponse.jsp] 生成。其模板包含以下元素:

  • [nom]:一个名称(字符串),该值将从会话属性中获取,并关联到键“nom”
  • [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">
<%
    // 从模型中获取数据
  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 首先从请求 [request] 中获取其模型的 [nom, age] 元素。 在应用程序的正常运行中,将由控制器 [ServletPersonne] 构建该模板。
  • 随后,模型中的 [nom, age] 元素在第 20 行和第 24 行显示

随后,我们将此视图命名为 [réponse(nom, age)]。

5.5.3. 视图 [erreurs]

该视图会提示表单中的输入错误:

Image

该视图由页面 JSP [erreurs.jsp] 生成。其模板由以下元素组成:

  • [erreurs]:一个错误消息列表(ArrayList),该列表可在请求属性中找到,并关联于“erreurs”键

页面 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");  
%>

<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>
  </body>
</html>

  • 第8行:页面JSP首先从请求[request]中获取其模型中的元素[erreurs]。 该元素表示一个类型为 ArrayList 的对象,其中包含类型为 String 的元素。这些元素是错误消息。在应用程序的正常运行中,将由控制器 [ServletPersonne] 构建此模型。
  • 第 18-22 行:显示错误消息列表。为此,需要在页面的 HTML 主体中编写 Java 代码。 应始终尽量减少此类代码,以免污染 HTML 代码。我们稍后将看到,存在一些解决方案可以减少 JSP 页面中的 Java 代码量。
  • 第 4 行:请注意导入页面 JSP 所需包的标签

接下来,我们将此视图命名为 [erreurs(erreurs)]。

5.6. 视图测试

无需编写控制器即可测试 JSP 页面的有效性。为此需满足两个条件:

  • 必须能够直接向应用程序请求这些页面,而无需通过控制器
  • 页面JSP必须自行初始化模型,该模型通常由控制器构建

为了进行这些测试,我们将视图中的 JSP 页面复制到 Eclipse 项目的 [/WebContent/JSP] 文件夹中:

Image

然后在 JSP 文件夹中,对页面进行如下修改:

[formulaire.jsp]:


...

<%
  // -- 测试:创建页面模板
  request.setAttribute("nom","tintin");
  request.setAttribute("age","30");
%>

<%
    // 从模板中获取数据
  String nom=(String)request.getAttribute("nom");
  String age=(String)request.getAttribute("age");
%>
    
<html>
    <head>
...

已添加第 3-7 行以创建第 11-12 行所需的模板。

[reponse.jsp]:


...

<%
  // -- 测试:创建页面模板
  request.setAttribute("nom","milou");
  request.setAttribute("age","10");
%>

<%
    // 从模板中获取数据
  String nom=(String)request.getAttribute("nom");
  String age=(String)request.getAttribute("age");
%>

    
<html>
    <head>
...

第 3-7 行已添加,用于创建第 11-12 行所需的模板。

[erreurs.jsp]:


...

<%
  // -- 测试:创建页面模板
  ArrayList<String> erreurs1=new ArrayList<String>();
  erreurs1.add("erreur1");
  erreurs1.add("erreur2");
  request.setAttribute("erreurs",erreurs1);
%>

<%
// 从模型中获取数据
  ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");  
%>

    
<html>
    <head>
...

第3-9行已添加,用于创建第13行页面所需的模板。

如果尚未启动,请启动 Tomcat,然后请求以下 URL:

 

我们确实获得了预期的视图。现在,既然我们对应用程序中的 JSP 页面有了合理的信心,就可以开始编写其控制器 [ServletPersonne] 了。

5.7. 控制器 [ServletPersonne]

接下来需要编写我们Web应用程序的核心——控制器。其作用包括:

  • 接收客户端的请求,
  • 处理客户端请求的操作,
  • 返回相应的视图。

控制器 [ServletPersonne] 将处理以下操作:

编号
请求
来源
处理
1
[GET /personne1/main]
用户输入的URL
- 发送空的视图 [formulaire]
2
[POST /personne1/main]
带参数 [txtNom,
txtAge, 操作] 已提交
点击按钮
[Envoyer] 视图中的
[formulaire]
- 检查参数值 [txtNom, txtAge]
- 若不正确,发送视图 [erreurs(erreurs)]
- 若参数正确,则发送视图 [reponse(nom,age)]

当用户请求 URL [/personne1/main] 时,应用程序启动。根据应用程序的文件 [web.xml](参见第 5.4 节),该请求由一个类型为 ServletPersonne 的实例处理,我们现在对此进行描述。

5.7.1. 控制器框架

控制器 [ServletPersonne] 的代码如下:

package istia.st.servlets.personne;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@SuppressWarnings("serial")
public class ServletPersonne extends HttpServlet {
...

    // 初始化
    @SuppressWarnings("unchecked")
    public void init() throws ServletException {
...
    }

    @SuppressWarnings("unchecked")
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
...
    }

    // 显示初始视图
    void doInit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
...
    }

    // 表单验证
    void doValidationFormulaire(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException{
...
    }

    // 提交
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        // 将控制权移交至 GET
        doGet(request, response);
    }
}
  • 第20-22行:在Servlet初始加载时执行的[init]方法
  • 第25-28行:当应用程序收到GET类型的请求时,由Web服务器调用的[doGet]方法
  • 第 42-46 行:当向应用程序发出 POST 类型的请求时,由 Web 服务器调用的 [doPost] 方法。 如上所示,该请求也将由方法 [doGet] 处理(第 45 行)。
  • 第 31-33 行:方法 [doInit] 处理操作 1 [GET /personne1/main]
  • 第 36-39 行:方法 [doValidationFormulaire] 处理第 2 个操作 [POST /personne1/main],并使用提交的参数 [txtNom, txtAge, action]。

现在我们来描述控制器中的各种方法

5.7.2. 控制器初始化

当控制器类被 Servlet 容器加载时,其方法 [init] 会被执行。这仅会发生一次。一旦加载到内存中,控制器将驻留其中并处理来自不同客户端的请求。 每个客户端都会被分配一个执行线程,因此控制器的方法将由不同的线程同时执行。 需要提醒的是,正因如此,控制器不应包含其方法可能修改的字段。其字段必须为只读。这些字段由方法 [init] 进行初始化,这也是该方法的主要作用。该方法的特殊之处在于,它仅由单个线程执行一次。 因此,在此方法中不会出现对控制器字段的并发访问问题。方法 [init] 的目的是初始化 Web 应用程序所需的对象,这些对象将作为只读资源由所有客户端线程共享。这些共享对象可放置在两个位置:

  • 控制器的私有字段
  • 应用程序的执行上下文(ServletContext)

控制器 [ServletPersonne] 中的方法 [init] 的代码如下:

package istia.st.servlets.personne;

...
@SuppressWarnings("serial")
public class ServletPersonne extends HttpServlet {
    // 实例参数
    private String urlErreurs = null;
    private ArrayList erreursInitialisation = new ArrayList<String>();
    private String[] paramètres={"urlFormulaire","urlReponse"};
    private Map params=new HashMap<String,String>();

    // 初始化
    @SuppressWarnings("unchecked")
    public void init() throws ServletException {
        // 获取 Servlet 的初始化参数
        ServletConfig config = getServletConfig();
        // 处理其他初始化参数
        String valeur=null;
        for(int i=0;i<paramètres.length;i++){
            // 参数值
            valeur=config.getInitParameter(paramètres[i]);
            // 参数是否存在?
            if(valeur==null){
                // 记录错误
                erreursInitialisation.add("Le paramètre ["+paramètres[i]+"] n'a pas été initialisé");
            }else{
                // 保存参数值
                params.put(paramètres[i],valeur);
            }
            // 视图 [erreurs] 的 URL 具有特殊处理
            urlErreurs = config.getInitParameter("urlErreurs");
            if (urlErreurs == null)
                throw new ServletException(
                        "Le paramètre [urlErreurs] n'a pas été initialisé");
        }
    }
...
}
  • 第 16 行:获取 Web 应用程序的配置,即 c.a.d。文件 [web.xml] 的内容
  • 第19-29行:获取Servlet的初始化参数,其名称在第9行的[paramètres]表中定义
  • 第21行:获取参数的值
  • 第25行:如果参数不存在,则将错误添加到最初为空的错误列表 [erreursInitialisation] 中(第8行)。
  • 第 28 行:如果参数存在,则将其与值一起存储在最初为空的字典 [params] 中(第 10 行)。
  • 第 31-35 行:参数 [urlErreurs] 必须存在,因为它指定了视图 [erreurs] 的 URL,该视图能够显示可能出现的初始化错误。 如果该视图不存在,则通过调用 [ServletException] 来中断应用程序(第 33 行)。

5.7.3. 方法 [doGet]

方法 [doGet] 同时处理 GET 和 POST 发往 Servlet 的请求, 因为方法 [doPost] 调用了方法 [doGet]。其代码如下:

package istia.st.servlets.personne;

...
@SuppressWarnings("serial")
public class ServletPersonne extends HttpServlet {
    // 实例参数
    private String urlErreurs = null;
    private ArrayList erreursInitialisation = new ArrayList<String>();
    private String[] paramètres={"urlFormulaire","urlReponse"};
    private Map params=new HashMap<String,String>();

...
    @SuppressWarnings("unchecked")
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {

        // 检查 Servlet 的初始化情况
        if (erreursInitialisation.size() != 0) {
            // 将控制权移交给错误页面
            request.setAttribute("erreurs", erreursInitialisation);
            getServletContext().getRequestDispatcher(urlErreurs).forward(
                    request, response);
            // 结束
            return;
        }

        // 获取发送请求的方法
        String méthode=request.getMethod().toLowerCase();
        // 获取待执行的操作
        String action=request.getParameter("action");
        // 操作?
        if(action==null){
            action="init";
        }
        // 执行操作
        if(méthode.equals("get") && action.equals("init")){
            // 启动应用程序
            doInit(request,response);
            return;
        }
        if(méthode.equals("post") && action.equals("validationFormulaire")){
            // 验证输入表单
            doValidationFormulaire(request,response);
            return;
        }
        // 其他情况
        doInit(request,response);
    }

    // 提交
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        // 将控制权移交至 GET
        doGet(request, response);
    }
}
  • 第 18-25 行:检查初始化错误列表是否为空。若非如此,则显示视图 [erreurs(erreursInitialisation)] 以报告相关错误。

要理解此代码,需回顾视图 [erreurs] 的模板:

<%
// 从模型中获取数据
  ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");  
%>

视图 [erreurs] 期望在请求中包含一个名为“errors”的键值对。控制器在第 20 行创建了该键值对。

  • 第 28 行:获取客户端用于发送请求的 [get] 或 [post] 方法
  • 第 30 行:获取请求中 [action] 参数的值。 需要提醒的是,在我们的应用程序中,只有第 2 号请求 [POST /personne1/main] 包含参数 [action]。在此请求中,该参数的值为 [validationFormulaire]。
  • 第31-34行:如果参数[action]不存在,则将其赋值为“init”。在初始请求1号[GET /personne1/main]中即属于这种情况。
  • 第36-40行:处理第1个请求 [GET /personne1/main]。
  • 第 41-45 行:处理第 2 个请求 [POST /personne1/main]。
  • 第 47 行:如果不属于上述两种情况之一,则按情况 1 处理

5.7.4. 方法 [doInit]

该方法处理请求编号 1 [GET /personne1/main]。针对此请求,它必须发送空视图 [formulaire(nom,age)]。其代码如下:

package istia.st.servlets.personne;
...

@SuppressWarnings("serial")
public class ServletPersonne extends HttpServlet {
    // 实例参数
    private String urlErreurs = null;
    private ArrayList erreursInitialisation = new ArrayList<String>();
    private String[] paramètres={"urlFormulaire","urlReponse"};
    private Map params=new HashMap<String,String>();

...    
    // 初始视图显示
    void doInit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        // 提交空白表单
        request.setAttribute("nom", "");
        request.setAttribute("age", "");
        getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward(
                request, response);
        return;
    }
...
}
  • 第 18-19 行:显示视图 [formulaire]。回顾该视图所需的模板:

<%
    // 从模型中获取数据
  String nom=(String)request.getAttribute("nom");
  String age=(String)request.getAttribute("age");
%>
  • 第 16-17 行:视图 [formulaire] 的模板 [nom,age] 被初始化为空字符串。

5.7.5. 方法 [doValidationFormulaire]

该方法处理第 2 号请求 [POST /personne1/main],其中提交的参数为 [action, txtNom, txtAge]。其代码如下:

package istia.st.servlets.personne;

...
@SuppressWarnings("serial")
public class ServletPersonne extends HttpServlet {
    // 实例参数
    private String urlErreurs = null;
    private ArrayList erreursInitialisation = new ArrayList<String>();
    private String[] paramètres={"urlFormulaire","urlReponse"};
    private Map params=new HashMap<String,String>();
...
    // 表单验证
    void doValidationFormulaire(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException{
        // 获取参数
        String nom = request.getParameter("txtNom");
        String age = request.getParameter("txtAge");
        // 参数验证
        ArrayList<String> erreursAppel = new ArrayList<String>();
        // 姓名字段不能为空
        nom = nom.trim();
        if (nom.equals(""))
            erreursAppel.add("Le champ [nom] n'a pas été rempli");
        // 年龄必须为大于等于0的整数
        if (!age.matches("^\\s*\\d+\\s*$"))
            erreursAppel.add("Le champ [age] est erroné");
        // 参数中存在错误?
        if (erreursAppel.size() != 0) {
            // 发送错误页面
            request.setAttribute("erreurs", erreursAppel);
            getServletContext().getRequestDispatcher(urlErreurs).forward(request, response);
            return;
        }
        // 参数正确 - 发送响应页面
        request.setAttribute("nom", nom);
        request.setAttribute("age", age);
        getServletContext().getRequestDispatcher((String)params.get("urlReponse")).forward(request,
                response);
        return;
    }
...
}
  • 第16-17行:从客户端请求中获取参数“txtNom”和“txtAge”的值。
  • 第19-26行:验证这两个参数的有效性
  • 第28-33行:如果其中一个参数有误,则显示视图[erreurs(erreursAppel)]。回顾该视图的模板:

<%
// 正在从模型中获取数据
  ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");  
%>
  • 第 35-38 行:如果获取到的两个参数“txtNom”和“txtAge”具有有效值,则显示视图 [reponse(nom,age)]。 需注意视图 [reponse] 的模板:

<%
    // 正在从模型中检索数据
  String nom=(String)request.getAttribute("nom");
  String age=(String)request.getAttribute("age");
%>

5.8. 测试

按照第 3.3 节所述的步骤,将项目 [mvc-personne-01] 包含到 Tomcat 应用程序中:

Image

启动 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]。得到以下响应:

Image