Skip to content

7. Web 应用程序 MVC [personne] – 版本 3


[ref1]中的推荐阅读:第9章


7.1. 简介

我们计划在发送至浏览器的 HTML 页面中添加 JavaScript 代码。浏览器会执行其所显示页面中嵌入的 JavaScript 代码。 该技术与 Web 服务器用于生成 HTML 文档的技术(Java/servlets/JSP、ASP.NET、 ASP、PHP、Perl、Python 等)所使用的技术。

[formulaire.jsp]

该页面生成的视图将如下所示:

Image

标签带有边框的按钮调用的是嵌入在页面 HTML 中的 JavaScript 代码:

标签
类型 HTML
功能
Submit
<submit>
相当于旧版本中的 [Envoyer] 按钮:将输入的值发送至控制器
[Envoyer]
<button>
新按钮——在将数据提交给控制器之前,先在本地验证所输入数据的有效性
Rétablir
<reset>
将表单恢复至浏览器最初接收时的状态
[Effacer]
<button>
清除两个输入字段的内容

以下是 [Envoyer] 和 [Effacer] 按钮的使用示例:

我们还将使用 JavaScript 代码来管理视图 [erreurs] 和 [réponse] 中的链接 [Retour au formulaire]。以视图 [réponse] 为例:

1

区别在于 [1] 中显示的 URL。在之前的版本中,该 URL 为:

[http://localhost:8080/personne2/main?action=retourFormulaire]

在此,操作已不再包含在 URL 中,因为它将通过 POST 发送,而非 GET。 此修改将导致无论请求何种操作,浏览器显示的唯一 URL 均为 [http://localhost:8080/personne3/main]。

7.2. Eclipse 项目

要为 Web 应用程序 [/personne3] 创建 Eclipse 项目 [mvc-personne-03],需按照第 78 页第 6.2 节所述步骤复制项目 [mvc-personne-02]。

7.3. Web 应用程序 [personne3] 的配置

应用程序 /personne3 的文件 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-03</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>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-03]

参数 [urlControleur] 已消失。在上一版本中,它用于设定视图 [formulaire] 中 POST 的目标。在此新版本中,目标将设为空字符串 c.a.d。 浏览器显示的 URL。我们已说明该 URL 将始终为 [http://localhost:8080/personne3/main],这是我们为视图 [formulaire] 中的 POST 所需要的。

首页 [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("/personne3/main");
%>
  • 第 5 行:页面 [index.jsp] 将客户端重定向至应用程序 [/personne3] 中控制器 [ServletPersonne] 的 URL。

7.4. 视图代码

7.4.1. 视图 [formulaire]

该视图已变为如下形式:

Image

它由以下页面生成: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");
%>


<html>
  <head>
    <title>Personne - formulaire</title>
    <script language="javascript">
        // -------------------------------
        function effacer(){
          // 清除输入字段
          with(document.frmPersonne){
            txtNom.value="";
            txtAge.value="";
          }//配合
        }//清除
        // -------------------------------      
        function envoyer(){
          // 发送前验证参数
          with(document.frmPersonne){
            // 名称不能为空
            champs=/^\s*$/.exec(txtNom.value);
            if(champs!=null){
              // 名称为空
              alert("Vous devez indiquer un nom");
              txtNom.value="";
              txtNom.focus();
              // 返回可视化界面
              return;
            }//if
            // 年龄必须为正整数
            champs=/^\s*\d+\s*$/.exec(txtAge.value);
            if(champs==null){
              // 年龄不正确
              alert("Age incorrect");
              txtAge.focus();
              // 返回可视化界面
              return;
            }//if
            // 参数正确 - 将其发送至服务器
            submit();
          }//with
        }//发送
      </script>
  </head>
  <body>
    <center>
      <h2>Personne - formulaire</h2>
      <hr>
      <form name="frmPersonne" 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="Submit"></td>
            <td><input type="button" value="[Envoyer]" onclick="envoyer()"></td>
            <td><input type="reset" value="Rétablir"></td>
            <td><input type="button" value="[Effacer]" onclick="effacer()"></td>
          </tr>
        </table>
        <input type="hidden" name="action" value="validationFormulaire">
      </form>
    </center>
  </body>
</html>


更新内容:

  • 第 56 行:表单名称为 [frmPersonne]。该名称将在 JavaScript 代码中使用。 请注意缺少 [action] 属性,这将导致表单 [frmPersonne] 被提交至浏览器显示的 URL。
  • 第 70 行:按钮 [Submit] 起到了之前版本中按钮 [Envoyer] 的作用
  • 第 71 行:点击类型为 [Button] 的按钮 [Envoyer],将执行第 24 行定义的 JavaScript 函数 [envoyer]
  • 第 72 行:按钮 [Rétablir] 的功能未发生变化
  • 第 73 行:点击类型为 [Button] 的按钮 [Effacer],将执行第 16 行定义的 JavaScript 函数 [effacer]

在讲解 JavaScript 代码之前,先回顾一些符号说明:

JavaScript 将显示的页面及其内容视为一个对象树,其根节点是对象 [document]。 在该文档中,可能包含一个或多个表单。[document.frmPersonne] 指代第 56 行定义的名称为 [frmPersonne] 的表单。该表单本身也包含对象。 输入字段是其中的一部分。因此,[document.frmPersonne.txtNom] 指代在第 60 行定义的输入字段 HTML 的图像对象。 对象 [txtNom] 具有多种属性,其中包括属性 [value],该属性指代输入字段的内容。因此,[document.frmPersonne.txtNom.value] 指代输入字段 [txtNom] 的内容。

  • 第 16-22 行:JavaScript 函数 [effacer] 将空字符串填入输入字段 [txtNom, txtAge] 中。
  • 第 24-49 行:JavaScript 函数 [envoyer] 在提交 [txtNom, txtAge] 输入字段的值之前,会使用正则表达式验证其有效性。
  • 第 28 行:检查输入字段 [txtNom] 的值是否符合正则表达式 /s*,该表达式表示 0 个或多个空格。 如果答案为是,则表示用户未输入姓名。如果与模式匹配,变量 champs 的值将不同于指针 null,否则其值为 null
  • 第 29 行:如果与模板匹配
  • 第 30 行:向用户显示一条消息
  • 第 32 行:将空字符串填入字段 [txtNom](该字段可能包含一串空格)
  • 第 33 行:将闪烁光标定位到字段 [txtNom]
  • 第34行:终止函数。此时,服务器上不会有POST字段的输入值。
  • 第38-45行:对字段[txtAge]执行类似操作
  • 第47行:若执行至此,说明输入值正确。此时将表单[frmPersonne]提交至Web服务器。此时的处理过程与点击标有[Submit]的按钮完全相同。

7.4.2. 视图 [reponse]

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

与上一版本相比,新功能仅在使用视图 [réponse] 中的链接 [Retour au formulaire] 时才会显示:

区别在于 [1] 中显示的 URL。在上一版本中,该 URL 为:

[http://localhost:8080/personne2/main?action=retourFormulaire]

在此,操作已不再包含在URL中,因为它将通过POST发送,而非GET。 新页面 JSP [reponse.jsp] 是 lqqqaaaaaaaaaqqAAAaaa

如下:


<%@ 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>
    <form name="frmPersonne" method="post">
      <input type="hidden" name="action" value="retourFormulaire">
    </form>
    <a href="javascript:document.frmPersonne.submit();">
      <%= lienRetourFormulaire %>
    </a>
  </body>
</html>

更新内容:

  • 第 35-37 行:链接 [Retour au formulaire] 嵌入了 JavaScript。点击该链接将触发 [href] 属性中的 JavaScript 代码执行。 正如我们在研究 [formulaire.jsp] 时所见,我们知道该代码会提交表单 [frmPersonne] 中 <input>、<select> 等类型字段的值。
  • 第 32-34 行:定义表单 [frmPersonne]。该表单仅包含一个 <input type="hidden" ...> 类型的字段,即隐藏字段。 该字段 [action] 用于向控制器传递待执行的操作名称,此处为 [retourFormulaire]。

7.4.3. 视图 [erreurs]

该视图用于提示表单中的输入错误。所做的修改与视图 [réponse] 相同:

区别在于 [1] 中显示的 URL。在之前的版本中,该 URL 为:

[http://localhost:8080/personne2/main?action=retourFormulaire]

在此,该操作不再包含在URL中,因为它将通过POST发送,而非GET。新的页面如下:


<%@ 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>
    <form name="frmPersonne" method="post">
      <input type="hidden" name="action" value="retourFormulaire">
    </form>
    <a href="javascript:document.frmPersonne.submit();">
      <%= lienRetourFormulaire %>
    </a>
  </body>
</html>

更新内容:

  • 第30-32行:链接点击处理机制的更新。[réponse.jsp]研究中对此的说明同样适用于此处。

7.5. 视图测试

为了对上述视图进行测试,我们将它们的页面 JSP 复制到 Eclipse 项目的 /WebContent/JSP 文件夹中:

Image

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

[formulaire.jsp]:


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

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

已添加第 4-5 行以创建第 9-10 行所需的模板。

[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:

 

我们确实获得了预期的视图。

7.6. 控制器 [ServletPersonne]

Web 应用程序 [/personne3] 中的控制器 [ServletPersonne] 将处理以下操作:

编号
请求
来源
处理
1
[GET /personne3/main]
用户输入的URL
- 发送空的视图 [formulaire]
2
[POST /personne3/main]
带参数 [txtNom,
txtAge, action=validationFormulaire]
已发布
点击按钮
[Envoyer] 视图中的
[formulaire]
- 检查参数值 [txtNom, txtAge]
- 若参数值不正确,则发送视图 [erreurs(erreurs)]
- 若参数正确,则发送视图 [reponse(nom,age)]
3
[POST /personne3/main]
带参数
[action=retourFormulaire]
已发送
点击链接 [返回
表单]
[réponse] 和 [erreurs]。
- 发送已预填入最新输入值的视图 [formulaire]

因此,我们需要处理一个新操作:[POST /personne3/main],其提交参数为 [action=retourFormulaire],以取代旧操作 [GET /personne3/main?action=retourFormulaire]。

7.6.1. 控制器框架

控制器 [ServletPersonne] 的骨架与上一版本完全相同。

package istia.st.servlets.personne;

...
import javax.servlet.http.HttpSession;

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

    // 初始化
    @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 doRetourFormulaire(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        ...
    }

    // 表单验证
    void doValidationFormulaire(HttpServletRequest request,
    ...
    }

    // 提交
    public void doPost(HttpServletRequest request, HttpServletResponse response)
    ...
    }
}

新增内容:

  • 第 11 行,数组 [paramètres] 不再包含参数 [urlControleur],该参数已从文件 [web.xml] 中删除。

[init, doValidationFormulaire]方法保持不变。[doGet, doInit, doRetourFormulaire]方法略有更改。

7.6.2. 方法 [doGet]

方法 [doGet] 必须处理操作 [POST /personne3/main],并使用已提交的参数 [action=retourFormulaire]:

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

...
        if(méthode.equals("post") && action.equals("retourFormulaire")){
            // 返回输入表单
            doRetourFormulaire(request,response);
            return;
        }
        // 其他情况
        doInit(request,response);
    }
  • 第6-9行:处理操作[POST /personne3/main],并使用已提交的参数[action=retourFormulaire]

7.6.3. 方法 [doInit]

该方法处理第1号请求 [GET /personne3/main]。其代码如下:

            // 显示空表单
    void doInit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        // 获取用户会话
        HttpSession session = request.getSession(true);        
        // 提交空表单
        session.setAttribute("nom", "");
        session.setAttribute("age", "");
        getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward(
                request, response);
        return;
    }

新变化在于,第 8 行显示的视图 [formulaire] 的模型中不再包含元素 [action]。

7.6.4. 方法 [doRetourFormulaire]

该方法处理第 1 号请求 [POST /personne3/main],其提交的元素中包含 [action=retourFormulaire]。其代码如下:

    // 显示预填表单
    void doRetourFormulaire(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        // 获取用户会话
        HttpSession session = request.getSession(true);        
        // 会话中是否包含姓名?
        String nom = (String) session.getAttribute("nom");
        if (nom == null)
            session.setAttribute("nom", "");
        // 会话中是否包含年龄?
        String age = (String) session.getAttribute("age");
        if (age == null)
            session.setAttribute("age", "");
        // 显示表单
        getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward(
                request, response);
        return;
    }

新变化在于,第 14 行显示的视图 [formulaire] 的模板中不再包含元素 [action]。

7.7. 测试

在将 Eclipse 项目 [personne-mvc-03] 集成到 Tomcat 后,启动或重启 Tomcat。访问 URL [http://localhost:8080/personne3],然后按照第 7.1 节中的示例继续进行测试。