Skip to content

3. Java Servlet 和 JSP 页面的简介

本章包含各种Servlet和JSP页面的示例。这些示例已在运行于8080端口的Tomcat服务器上经过测试。通过点击主页上的链接,您可以访问这些Servlet和JSP页面的示例。 下文中的大部分示例均摘自 Tomcat 的示例集。要测试这些示例,只需启动 Tomcat,在浏览器中输入 URL http://localhost:8080,然后点击 Servlet 链接即可。

3.1. Java Servlets

3.1.1. 向 Web 客户端发送 HTML 内容

让我们来分析上面的“Hello World”示例。Servlet代码如下:


import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
 
public class HelloWorld extends HttpServlet {
 
    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Hello World!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>Hello World!</h1>");
        out.println("</body>");
        out.println("</html>");
    }
}

执行此 Servlet 时,将显示以下内容:

Image

请注意以下几点:

  • 您必须导入 Servlet 专用的类:

import javax.servlet.*;
import javax.servlet.http.*;

javax.servlet 库并非总是默认包含在 JDK 中。如果遇到这种情况,您可以直接从 Sun 网站下载该库。

  • Servlet 继承自 HttpServlet

public class HelloWorld extends HttpServlet {
  • 对 Servlet 发起的 GET 请求由 doGet 方法处理

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException

  • 同样,发往 Servlet 的 POST 请求由 doPost 方法处理

    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
  • HttpServletRequest 请求对象是让我们能够访问 Web 客户端所发请求的对象。Servlet 的响应将通过 HttpServletResponse 响应对象发送
  • response 对象允许我们设置将发送给客户端的 HTTP 头部。例如,Content-Type: text/html 头部可通过以下方式在此处设置:
        response.setContentType("text/html");
  • 为了将响应发送给客户端,Servlet 会使用响应对象提供的输出流:
        PrintWriter out = response.getWriter();
  • 获取该输出流后,将 HTML 代码写入其中,从而发送给客户端:
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>Hello World!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>Hello World!</h1>");
        out.println("</body>");
        out.println("</html>");

3.1.2. 获取 Web 客户端发送的参数

以下示例演示了 Servlet 如何获取 Web 客户端发送的参数。一个输入表单:

Image

Servlet 发送的响应:

Image

Servlet 的源代码如下:


import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
 
public class myRequestParamExample extends HttpServlet {
 
    String title="Récupération des paramètres d'un formulaire";
 
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>" + title + "</title>");
        out.println("</head>");
        out.println("<body bgcolor=\"white\">");
        out.println("<h3>" + title + "</h3>");
        String firstName = request.getParameter("firstname");
        String lastName = request.getParameter("lastname");
        if (firstName != null || lastName != null) {
            out.println("firstname= " + firstName + "<br>");
            out.println("lastname= " + lastName);
        } else {
            out.println("pas de paramètres");
        }
        out.println("<P>");
        out.print("<form action=\"RequestParamExample\" method=\"POST\">");
        out.println("firstname= <input type=text size=20 name=firstname>");
        out.println("<br>");
        out.println("lastname= <input type=text size=20 name=lastname>");
        out.println("<br>");
        out.println("<input type=submit>");
        out.println("</form>");
        out.println("</body>");
        out.println("</html>");
    }
 
    public void doPost(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        doGet(request, response);
    }
 
}

请注意与前一个示例相比,以下内容发生了变化:

  • 浏览器发送的参数可通过以下方式获取:

        String firstName = request.getParameter("firstname");
        String lastName = request.getParameter("lastname");

如果参数“parameterName”不在 Web 客户端发送的参数列表中,方法 request.getParameter("parameterName") 将返回指针。

  • 表单规定浏览器必须使用 POST 方法发送参数
        out.print("<form action=\"RequestParamExample\" method=\"POST\">");
  • 接收到的参数将由 Servlet 的 doPost 方法进行处理。在此,该方法仅调用 doGet 方法。因此,无论表单值是通过 GET 还是 POST 发送的,该 Servlet 都会对其进行处理。

    public void doPost(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        doGet(request, response);
    }

3.1.3. 获取 Web 客户端发送的 HTTP 头部

以下 Servlet 演示了如何检索 Web 客户端发送的 HTTP 头:

Image

该 Servlet 的源代码如下:


import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
 
public class RequestHeaderExample extends HttpServlet {
 
    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        Enumeration e = request.getHeaderNames();
        while (e.hasMoreElements()) {
            String name = (String)e.nextElement();
            String value = request.getHeader(name);
            out.println(name + " = " + value);
        }
    }
}

注意事项:

  • 正是 `request` 对象及其 `getHeaderNames` 方法,使我们能够以枚举的形式访问浏览器发送的 HTTP 头部:
        Enumeration e = request.getHeaderNames();
  • `request.getHeader("header")` 方法允许您检索特定的 HTTP 头部。上面的示例提供了一些示例。请记住,这里显示的头部是由浏览器发送的。服务器也有自己的 HTTP 头部,有时这些头部与浏览器的头部一致。浏览器发送的 HTTP 头部旨在向服务器告知浏览器的功能。
标头
含义
User-Agent
浏览器标识
Accept
浏览器支持的MIME类型。例如image/gif表示该浏览器可以处理GIF格式的图像
主机
格式为 host:port。指明浏览器希望连接的机器和端口。
Accept-Encoding
浏览器接受的、用于处理服务器发送文档的编码格式。因此,如果服务器上既有普通未压缩格式的文档,也有 gzip 格式的文档,且浏览器已表明其支持 gzip 格式,则服务器可以发送 gzip 格式的文档以节省带宽。
Accept-language
浏览器支持的语言。如果服务器拥有同一文档的多种语言版本,它将发送浏览器支持语言的版本。
Referer
浏览器请求的 URL
Connection
浏览器请求的连接模式。Keep-alive 表示服务器在向浏览器提供所请求的页面后不应关闭连接。例如,如果浏览器发现收到的页面包含指向图片的链接,它可以向服务器发出新的请求来获取这些图片,而无需建立新的连接。一旦浏览器收到页面中的所有元素,它就会主动关闭连接。

3.1.4. 获取环境信息

以下 Servlet 演示了如何访问 Servlet 的运行时环境信息。其中部分信息由浏览器作为 HTTP 头发送,因此可以使用前面的方法进行获取。

Image

Servlet 代码如下:


import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
 
public class RequestInfo extends HttpServlet {
 
    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>Request Information Example</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h3>Request Information Example</h3>");
        out.println("Method: " + request.getMethod());
        out.println("Request URI: " + request.getRequestURI());
        out.println("Protocol: " + request.getProtocol());
        out.println("PathInfo: " + request.getPathInfo());
        out.println("Remote Address: " + request.getRemoteAddr());
        out.println("</body>");
        out.println("</html>");
    }
 
 
    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        doGet(request, response);
    }
}

此处通过多种方法获取信息:

        out.println("Method: " + request.getMethod());
        out.println("Request URI: " + request.getRequestURI());
        out.println("Protocol: " + request.getProtocol());
        out.println("PathInfo: " + request.getPathInfo());
        out.println("Remote Address: " + request.getRemoteAddr());

以下是一些可用方法及其含义的列表:

方法
含义
getServerName()
Web 服务器的名称
getServerPort()
Web 服务器的运行端口
getMethod()
浏览器用于发送请求的 GET 或 POST 方法
getRemoteHost()
发起请求的浏览器所属客户端计算机的名称
getRemoteAddr()
该机器的 IP 地址
getContentType()
浏览器发送的内容类型(HTTP Content-Type 标头)
getContentLength()
浏览器发送的字符数(HTTP Content-Length 标头)
getProtocol()
浏览器请求的 HTTP 协议版本
getRequestURI()
浏览器请求的 URI。对应于 http://hote:port/URI host:port 标识符之后的 URL 部分

3.1.5. 使用 JBuilder 创建一个 Servlet,并将其部署到 Tomcat

接下来我们将介绍如何创建和运行一个 Java Servlet。我们将使用两个工具:JBuilder 来编译 Servlet,Tomcat 来运行它。仅使用 Tomcat 或许就足够了。然而,它提供的调试功能有限。我们将重新审视之前开发的那个显示服务器接收到的参数的示例。该 Servlet 首先发送以下输入表单:

Image

Servlet 发送的响应:

Image

Servlet 的源代码如下:


import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
 
public class myRequestParamExample extends HttpServlet {
 
    String title="Récupération des paramètres d'un formulaire";
 
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>" + title + "</title>");
        out.println("</head>");
        out.println("<body bgcolor=\"white\">");
        out.println("<h3>" + title + "</h3>");
        String firstName = request.getParameter("firstname");
        String lastName = request.getParameter("lastname");
        if (firstName != null || lastName != null) {
            out.println("firstname= " + firstName + "<br>");
            out.println("lastname= " + lastName);
        } else {
            out.println("pas de paramètres");
        }
        out.println("<P>");
        out.print("<form action=\"RequestParamExample\" method=\"POST\">");
        out.println("firstname= <input type=text size=20 name=firstname>");
        out.println("<br>");
        out.println("lastname= <input type=text size=20 name=lastname>");
        out.println("<br>");
        out.println("<input type=submit>");
        out.println("</form>");
        out.println("</body>");
        out.println("</html>");
    }
 
    public void doPost(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        doGet(request, response);
    }
 
}
  • 使用 JBuilder 创建一个名为 myRequestParamExample 的项目,并将前面的 myRequestParamExample.java 程序放入其中。
  • 在编译过程中,您可能会遇到以下问题:您的 JBuilder 可能缺少编译 Servlet 所需的 javax.servlet 库。在这种情况下,您需要配置 JBuilder 以使用额外的类库。针对 JBuilder 7 的具体操作步骤详见本文档附录。我们在此部分摘录了相关内容:
  • 启用“工具/配置 JDK”选项

Image

在上方的“JDK 设置”部分,“名称”字段通常显示为 JDK 1.3.1。如果您拥有更新版本的 JDK,请使用“更改”按钮指定其安装目录。在上图中,我们指定了安装有 JDK 1.4 的目录 E:\Program Files\jdk14。从现在起,JBuilder 将使用此 JDK 进行编译和执行。 在(类、源代码、文档)部分,您将看到 JBuilder 将扫描的所有类库列表——在此示例中,即来自 JDK 1.4 的类。该 JDK 包含的类对于 Java Web 开发尚不充分。要添加其他类库,请使用“添加”按钮并选择您想要使用的额外 .jar 文件。.jar 文件即为类库。 Tomcat 4.x 包含 Web 开发所需的所有类库。它们位于 <tomcat>\common\lib 目录下,其中 <tomcat> 是 Tomcat 的安装目录:

Image

我们将使用“添加”按钮,将这些库逐一添加到 JBuilder 扫描的库列表中:

Image

从现在起,您可以编译符合 J2EE 标准的 Java 程序,包括 Java Servlet。JBuilder 仅用于编译;后续的执行由 Tomcat 负责。

  • 现在您可以编译 myRequestParamExample.java 程序并生成 myRequestParamExample.class Servlet。该 Servlet 应放置在何处?如果初始的 Tomcat 配置未作更改,则 Servlet 的 .class 文件必须放置在 <tomcat>\webapps\examples\WEB-INF\classes 目录下(Tomcat 4.x)。
  • 请确认 Tomcat 正在运行,并使用浏览器访问 URL http://localhost:8080/examples/servlet/myRequestParamExample

Image

3.1.6. 示例

在以下示例中,我们采用了上述方法:

  • 使用 JBuilder 编译 Servlet 的 XX.java 源文件
  • 将 Servlet 的 XX.class 文件部署到 <tomcat>\webapps\examples\WEB-INF\classes
  • 在 Tomcat 运行时,在浏览器中输入 URL http://localhost:8080/examples/servlet/XX

3.1.6.1. 动态表单生成 - 1

我们将以生成一个仅包含一个控件(列表)的表单为例。该列表的内容通过从数组中提取的值动态构建。实际上,这些值通常是从数据库中检索的。表单如下所示:

Image

若点击上例中的“提交”按钮,将获得以下响应:

Image

请注意,返回响应的 URL 与显示表单的 URL 相同。这里有一个 Servlet,用于处理其发送的表单的响应。这是常见的情况。表单的 HTML 代码如下:

<html>
    <head><title>Génération de formulaire</title></head>
    <body>
    <h3>Choississez un nombre</h3><hr>
    <form method="POST">
      <select name="cmbValeurs" size="1">
        <option>zéro</option>
        <option>un</option>
        <option>deux</option>
        <option>trois</option>
        <option>quatre</option>
        <option>cinq</option>
        <option>six</option>
        <option>sept</option>
        <option>huit</option>
        <option>neuf</option>
      </select>
      <input type="submit" value="Envoyer">
    </form>
    </body>
</html>

请注意,表单发送的值是使用 POST 方法发送的。响应的 HTML 代码:

<html>
    <head><title>Voici ma réponse</title></head>
  <body>
      Vous avez choisi le nombre<h2>neuf</h2>
  </body>
</html>

生成此表单和此响应的Servlet代码如下:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class gener1 extends HttpServlet{
    // variables d'instance
    private String title="Génération d'un formulaire";
    private final String[] valeurs={"zéro","un","deux","trois","quatre","cinq","six",
      "sept","huit","neuf"};
    private final String HTML1=
                "<html>" +
                  "<head>" +
                    "<title>Génération de formulaire</title>"+
                    "</head>" +
                  "<body>" +
                     "<h3>Choississez un nombre</h3>"+
                     "<hr>" +
                     "<form method=\"POST\">";
        private final String HTML2="<input type=\"submit\" value=\"Envoyer\">";
        private final String HTML3="</form>\n</body>\n</html>";

    // GET
    public void doGet(HttpServletRequest request,HttpServletResponse response)
          throws IOException, ServletException{

      // on indique au client le type de document envoyé
      response.setContentType("text/html");
      // on envoie le formulaire
      PrintWriter out=response.getWriter();
            // début
      out.println(HTML1);
            // combo
            out.println("<select name=\"cmbValeurs\" size=\"1\">");
            for (int i=0;i<valeurs.length;i++){
                out.println("<option>"+valeurs[i]+"</option>");
            }//for
            out.println("</select>");
            // fin formulaire
            out.println(HTML2+HTML3);
    }//GET

        // POST
        public void doPost(HttpServletRequest request,HttpServletResponse response)
          throws IOException, ServletException{

            // on récupère le choix de l'utilisateur
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) doGet(request,response);

            // on prépare la réponse
            String réponse="<html><head><title>Voici ma réponse</title></head>";
            réponse+="<body>Vous avez choisi le nombre <h2>"+choix+"</h2></body></html>";
            // on indique au client le type de document envoyé
            response.setContentType("text/html");
            // on envoie le formulaire
            PrintWriter out=response.getWriter();
            out.println(réponse);
    }//POST
    }//classe

doGet 方法用于生成表单。其中有一个动态部分,即列表的内容,此处从数组中获取。doPost 方法用于生成响应。在此,唯一的动态部分是用户在表单列表中的选择值。该值通过 request.getParameter("cmbValeurs") 获取,其中 cmbValeurs 是列表的 HTML 名称:

      <select name="cmbValeurs" size="1">

最后,请注意以下几点:

  • 由于 <form> 标签没有 <action> 属性,浏览器会将表单值发送给生成该表单的 Servlet。在这种情况下,浏览器会将表单中输入的数据发送至提供该表单的 URL。
  • <form> 标签指定表单数据必须使用 POST 方法发送。这就是为什么这些值会被 Servlet 的 doPost 方法检索到的原因。

3.1.6.2. 动态表单生成 - 2

我们将重新审视前面的示例,并按以下方式进行修改。表单保持不变:

Image

但响应内容有所不同:

Image

在响应中,表单会被返回,用户选择的数字显示在表单下方。此外,当列表显示时,该数字即为被选中的数字。用户随后可以选择另一个数字:

Image

然后点击“提交”。用户将收到以下响应:

Image

名为 gener2.java 的 Servlet 代码如下:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class gener2 extends HttpServlet{
        // variables d'instance
        private String title="Génération d'un formulaire";
        private final String[] valeurs={"zéro","un","deux","trois","quatre","cinq","six",
            "sept","huit","neuf"};
        private final String HTML1=
                "<html>" +
                    "<head>" +
                        "<title>Génération de formulaire</title>"+
                    "</head>" +
                    "<body>" +
                         "<h3>Choisissez un nombre</h3>"+
                         "<hr>" +
                         "<form method=\"POST\">";
        private final String HTML2="<input type=\"submit\" value=\"Envoyer\"></form>\n";
        private final String HTML3="</body>\n</html>";

        // GET
        public void doGet(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // on récupère l'éventuel choix de l'utilisateur
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";

            // on indique au client le type de document envoyé
            response.setContentType("text/html");
            // on envoie le formulaire
            PrintWriter out=response.getWriter();
            // début
            out.println(HTML1);
            // combo
            out.println("<select name=\"cmbValeurs\" size=\"1\">");
            String selected="";
            for (int i=0;i<valeurs.length;i++){
                if(valeurs[i].equals(choix)) selected="selected"; else selected="";
                out.println("<option "+selected+">"+valeurs[i]+"</option>");
            }//for
            out.println("</select>");
            // suite formulaire
            out.println(HTML2);
            if(! choix.equals("")){
                // on affiche le choix de l'utilisateur
                out.println("<hr>Vous avez choisi le nombre <h2>"+choix+"</h2>");
            }//if
            // fin du formulaire
            out.println(HTML3);
        }//GET

        // POST
        public void doPost(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // on renvoie sur GET
            doGet(request,response);
        }//POST
    }//classe

doGet 方法包揽了所有工作:它构建要发送给客户端的表单,并处理客户端返回的值。以下几点值得注意:

  • 我们检查 cmbValeurs 参数是否具有值。
  • 如果有,在构建列表内容时,会将每个项目与用户的选中项进行比较,并在用户选中的项目上设置 selected 属性:<option selected>item</option>。此外,选中的值会显示在表单下方。

3.1.6.3. 动态表单生成 - 3

我们正在解决与之前相同的问题,但这次值是从数据库中检索的。在本例中,这是一个 MySQL 数据库:

  • 数据库名为 dbValues
  • 其所有者为 admDbValeurs,密码为 mdpDbValeurs
  • 该数据库包含一个名为 tvaleurs 的表
  • 该表仅包含一个名为 value 的整数字段
E:\Program Files\EasyPHP\mysql\bin>mysql --database=dbValeurs --user=admDbValeurs --password=mdpDbVa
leurs

mysql> show tables;
+---------------------+
| Tables_in_dbValeurs |
+---------------------+
| tvaleurs            |
+---------------------+
1 row in set (0.00 sec)

mysql> describe tvaleurs;
+--------+---------+------+-----+---------+-------+
| Field  | Type    | Null | Key | Default | Extra |
+--------+---------+------+-----+---------+-------+
| valeur | int(11) |      |     | 0       |       |
+--------+---------+------+-----+---------+-------+

mysql> select * from tvaleurs;
+--------+
| valeur |
+--------+
|      0 |
|      1 |
|      2 |
|      3 |
|      4 |
|      6 |
|      5 |
|      7 |
|      8 |
|      9 |
+--------+
10 rows in set (0.00 sec)

MySQL 数据库 dbValeurs 已通过 MySQL 的 ODBC 驱动程序开放访问。其 DSN(数据源名称)为 odbc-valeurs。Servlet 代码如下:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
import java.util.*;

public class gener3 extends HttpServlet{
        // le titre de la page
        private final String title="Génération d'un formulaire";
        // la base de données des valeurs de liste
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
        // valeurs de liste
        private String[] valeurs=null;
        // msg d'erreur
        private String msgErreur=null;
        // code HTML
        private final String HTML1=
                "<html>" +
                    "<head>" +
                        "<title>Génération de formulaire</title>"+
                    "</head>" +
                    "<body>" +
                         "<h3>Choisissez un nombre</h3>"+
                         "<hr>" +
                         "<form method=\"POST\">";
        private final String HTML2="<input type=\"submit\" value=\"Envoyer\"></form>\n";
        private final String HTML3="</body>\n</html>";

        // GET
        public void doGet(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // on indique au client le type de document envoyé
            response.setContentType("text/html");
            // flux de sortie
            PrintWriter out=response.getWriter();

            // l'initialisation de la servlet s'est-elle bien passée ?
            if (msgErreur!=null){
                // il y a eu une erreur - on génère une page d'erreur
                out.println("<html><head><title>"+title+"</title></head>");
                out.println("<body><h3>Application indisponible ("+msgErreur+
                                        ")</h3></body></html>");
                return;
            }//if

            // on récupère l'éventuel choix de l'utilisateur
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";

            // on envoie le formulaire
            // début
            out.println(HTML1);
            // combo
            out.println("<select name=\"cmbValeurs\" size=\"1\">");
            String selected="";
            for (int i=0;i<valeurs.length;i++){
                if(valeurs[i].equals(choix)) selected="selected"; else selected="";
                out.println("<option "+selected+">"+valeurs[i]+"</option>");
            }//for
            out.println("</select>");
            // suite formulaire
            out.println(HTML2);
            if(! choix.equals("")){
                // on affiche le choix de l'utilisateur
                out.println("<hr>Vous avez choisi le nombre <h2>"+choix+"</h2>");
            }//if
            // fin du formulaire
            out.println(HTML3);
        }//GET

        // POST
        public void doPost(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // on renvoie sur GET
            doGet(request,response);
        }//POST

        // initialisation de la servlet
        public void init(){
            // remplit le tableau des valeurs à partir d'une base de données ODBC
            // de nom DSN : DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                // connexion à la base ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                // objet Statement
                st=connexion.createStatement();
                // exécution requête select pour récupérer les valeurs
                rs=st.executeQuery("select valeur from Tvaleurs");
                // les valeurs sont récupérées et mises dans un tableau dynamique
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // on enregistre la valeur dans la liste
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                // transformation liste --> tableau
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                // problème
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//init
    }//classe

需要注意的关键点如下:

  1. Servlet 可以通过一个方法进行初始化,该方法的签名必须为 public void init()。此方法仅在 Servlet 初次加载时执行
  2. 一旦加载,Servlet将始终驻留于内存中。这意味着在处理完客户端请求后,它不会被卸载。因此,它能更快速地响应客户端请求。
  3. 在我们的 Servlet 中,需要从数据库中检索一组值。由于该列表随时间推移不会发生变化,因此 init 方法是检索它的最佳时机。这样,Servlet 仅在初始加载时访问数据库一次,而非在每次客户端请求时都进行访问。
  4. 数据库访问可能失败。本 Servlet 的 init 方法在发生故障时会设置一条错误消息 msgErreurdoGet 方法会检查该消息,若发生错误,doGet 将生成一个显示错误的页面。
  5. init 方法的实现采用了基于 ODBC-JDBC 驱动程序的标准数据库访问方式。如有需要,建议读者查阅访问 JDBC 数据库的相关方法。

当 Servlet 被执行而 MySQL 服务器尚未启动时,将显示以下错误页面:

Image

若此时启动 MySQL 服务器,您将看到以下页面:

Image

如果您选择数字 6 并点击“提交”:

Image

3.1.6.4. 从表单中获取值

我们将重新审视一个我们已经见过的示例,即以下网页表单:

Image

balises2.htm 表单的 HTML 代码如下:

<html>

  <head>
      <title>balises</title>
    <script language="JavaScript">
        function effacer(){
          alert("Vous avez cliqué sur le bouton Effacer");
      }//effacer
        </script>
  </head>

  <body background="/images/standard.jpg">
...

    <form method="POST" action="http://localhost:8080/examples/servlet/parameters">

      <table border="0">
        <tr>
          <td>Etes-vous marié(e)</td>
          <td>
              <input type="radio" value="Oui" name="R1">Oui
              <input type="radio" name="R1" value="non" checked>Non
          </td>
        </tr>
        <tr>
          <td>Cases à cocher</td>
          <td>
              <input type="checkbox" name="C1" value="un">1
              <input type="checkbox" name="C2" value="deux" checked>2
              <input type="checkbox" name="C3" value="trois">3
          </td>
        </tr>
        <tr>
          <td>Champ de saisie</td>
          <td>
              <input type="text" name="txtSaisie" size="20" value="qqs mots">
          </td>
        </tr>
        <tr>
          <td>Mot de passe</td>
          <td>
              <input type="password" name="txtMdp" size="20" value="unMotDePasse">
          </td>
        </tr>
        <tr>
          <td>Boîte de saisie</td>
          <td>
               <textarea rows="2" name="areaSaisie" cols="20">
ligne1
ligne2
ligne3
</textarea>
          </td>
        </tr>
        <tr>
          <td>combo</td>
          <td>
              <select size="1" name="cmbValeurs">
                <option>choix1</option>
                <option selected>choix2</option>
                <option>choix3</option>
              </select>
          </td>
        </tr>
        <tr>
          <td>liste à choix simple</td>
          <td>
              <select size="3" name="lst1">
                <option selected>liste1</option>
                <option>liste2</option>
                <option>liste3</option>
                <option>liste4</option>
                <option>liste5</option>
              </select>
          </td>
        </tr>
        <tr>
          <td>liste à choix multiple</td>
          <td>
              <select size="3" name="lst2" multiple>
                <option selected>liste1</option>
                <option>liste2</option>
                <option selected>liste3</option>
                <option>liste4</option>
                <option>liste5</option>
              </select>
          </td>
        </tr>
        <tr>
          <td>bouton</td>
          <td>
              <input type="button" value="Effacer" name="cmdEffacer" onclick="effacer()">
          </td>
        </tr>
        <tr>
          <td>envoyer</td>
          <td>
              <input type="submit" value="Envoyer" name="cmdRenvoyer">
          </td>
        </tr>
        <tr>
          <td>rétablir</td>
          <td>
              <input type="reset" value="Rétablir" name="cmdRétablir">
          </td>
        </tr>
      </table>
      <input type="hidden" name="secret" value="uneValeur">
    </form>
  </body>
</html>

表单中的 <form> 标签已按以下方式定义:

    <form method="POST" action="http://localhost:8080/examples/servlet/parameters">

浏览器将表单值“POST”到 URL http://localhost:8080/examples/servlet/parameters,该 URL 指向一个由 Tomcat 管理的 Servlet,该 Servlet 会显示前一个表单的值。如果我们直接调用参数 Servlet,将得到以下结果:

Image

如果表单 balises2.htm 输入的内容如下:

Image

并且您点击“提交”按钮,此次 Servlet 将被带入参数调用。随后它返回以下响应:

Image

该响应显然包含了表单中输入的值。Servlet 代码如下:

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class parameters extends HttpServlet{
    // variables d'instance
    String title="Récupération des paramètres d'un formulaire";

    private String getParameter(HttpServletRequest request, String contrôle){
      // rend la valeur request.getParameter(contrôle) ou "" si elle n'existe pas
      String valeur=request.getParameter(contrôle);
      if(valeur==null) return ""; else return valeur;
    }//getParameter

    // GET
    public void doGet(HttpServletRequest request,HttpServletResponse response)
        throws IOException, ServletException
    {
      // on commence par récupérer les paramètres du formulaire
      String R1=getParameter(request,"R1");
      String C1=getParameter(request,"C1");
      String C2=getParameter(request,"C2");
      String C3=getParameter(request,"C3");
      String txtSaisie=getParameter(request,"txtSaisie");
      String txtMdp=getParameter(request,"txtMdp");
      String areaSaisie=getParameter(request,"areaSaisie");
      String[] lignes=areaSaisie.split("\\r\\n");
      String cmbValeurs=getParameter(request,"cmbValeurs");
      String lst1=getParameter(request,"lst1");
      String[] lst2=request.getParameterValues("lst2");
      String secret=getParameter(request,"secret");

      // on indique le contenu du document
        response.setContentType("text/html");
      // on envoie le document
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>" + title + "</title>");
        out.println("</head>");
        out.println("<body bgcolor=\"white\">");
        out.println("<h3>" + title + "</h3>");
        out.println("<hr>");
        out.println("<table border=\"1\">");
        out.println("<tr><td>R1</td><td>"+R1+"</td></tr>");
        out.println("<tr><td>C1</td><td>"+C1+"</td></tr>");
        out.println("<tr><td>C2</td><td>"+C2+"</td></tr>");
        out.println("<tr><td>C3</td><td>"+C3+"</td></tr>");
        out.println("<tr><td>txtSaisie</td><td>"+txtSaisie+"</td></tr>");
        out.println("<tr><td>txtMdp</td><td>"+txtMdp+"</td></tr>");
        for(int i=0;i<lignes.length;i++)
          out.println("<tr><td>areaSaisie["+i+"]</td><td>"+lignes[i]+"</td></tr>");
        out.println("<tr><td>cmbValeurs</td><td>"+cmbValeurs+"</td></tr>");
        out.println("<tr><td>lst1</td><td>"+lst1+"</td></tr>");
        if(lst2==null)
          out.println("<tr><td>lst2</td><td></td></tr>");
        else
          for(int i=0;i<lst2.length;i++)
            out.println("<tr><td>lst2</td><td>"+lst2[i]+"</td></tr>");
        out.println("<tr><td>secret</td><td>"+secret+"</td></tr>");
        out.println("</body>");
        out.println("</html>");
    }

    // POST
    public void doPost(HttpServletRequest request,HttpServletResponse response)
        throws IOException, ServletException
    {
      // renvoie sur GET
      doGet(request,response);
    }
}

这段代码运用了之前在另一个示例中介绍的技术。请注意以下两点:

  1. lst2 控件是一个多选列表,因此可以同时选择多个项目。在我们的示例中就是这种情况,其中已选中了 list1 list3 这两个项目。 lst2 的值由浏览器以 lst2=liste1&lst2=liste3 的形式发送至服务器。Java Servlet 可通过 getParameterValues 方法将这些值作为数组检索:在此处,request.getParameterValues("lst2") 返回一个包含两个字符串 ["liste1", "liste3"] 的数组。
  2. areaSaisie 控件是一个多行输入框。request.getParameter("areaSaisie") 会将该字段的内容作为单个字符串返回。若要提取其中的单行内容,可以使用 String 类的 split 方法。以下代码
      String areaSaisie=getParameter(request,"areaSaisie");
      String[] lignes=areaSaisie.split("\\r\\n");

可从输入字段中提取各行内容。这些行以 \r\n(0D0A)字符结尾。

要执行测试,我们需要:

  • 如前所述,使用 JBuilder 构建并编译了 Servlet 参数
  • 将生成的类放置在 <tomcat>\webapps\examples\WEB-INF\classes 目录下,其中 <tomcat> 是 Tomcat 的安装目录。
  • 访问了 URL http://localhost:81/html/balises2.htm,其代码已在上文展示
  • 填写表单并点击了“提交”按钮。

3.1.6.5. 从 Web 客户端获取 HTTP 头

我们将使用与之前相同的示例,但在响应发送表单值的 Web 客户端时,我们将同时返回它发送的 HTTP 头。我们将对表单进行一项修改:

    <form method="GET" action="http://localhost:8080/examples/servlet/headers">

表单值将通过 GET 方法发送至位于 <tomcat>\webapps\examples\WEB-INF\classes 目录下的名为 headers 的 Java Servlet。该 headers Servlet 是使用 JBuilder 构建并编译的:

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class headers extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
       // determine the nature of the document
        response.setContentType("text/html");
       // we obtain a writing flow
        PrintWriter out = response.getWriter();
      // display header list HTTP
        Enumeration e = request.getHeaderNames();
        while (e.hasMoreElements()) {
            String name = (String)e.nextElement();
            String value = request.getHeader(name);
            out.println("<b>"+name + "</b> = " + value + "<br>");
        }
    }//GET

    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
      //GET
      doGet(request,response);
    }//POST
}

我们访问网址 http://localhost:81/html/balises2.htm,并在未修改表单内容的情况下点击“提交”。我们收到以下响应:

Image

请注意浏览器地址栏中的带参数 URL,它显示了用于传输参数的方法(GET)。我们将使用相同的示例,但更改发送参数的方法(POST):

    <form method="POST" action="http://localhost:8080/examples/servlet/headers">

我们得到以下新的响应:

Image

请注意 HTTP 头部 content-typecontent-length,这是 POST 请求的特征。此外,请注意表单值不再显示在浏览器的地址栏中。

3.2. JSP 页面

JSP(Java Server Pages)是编写 Web 服务器应用程序的另一种方式。实际上,这些 JSP 页面在执行前会被编译为 Servlet,因此我们本质上是在使用 Servlet 技术。JSP 页面能够更清晰地突出显示生成的 HTML 页面的结构。以下是一些示例,其中部分示例可通过访问 Tomcat 主页上的 JSP 链接进行查看:

3.2.1. 获取环境信息

在此,我们将重新审视一个之前用 Servlet 演示过的示例:显示 Servlet 的环境变量。这是来自 JSP 示例中的“snoop”示例:

Image

该 JSP 页面的源代码位于 <tomcat>\jakarta-tomcat\examples\jsp\snp\snoop.jsp(Tomcat 3.x)或 <tomcat>\examples\jsp\snp\snoop.jsp(Tomcat 4.x)


<html>
<!--
  Copyright (c) 1999 The Apache Software Foundation.  All rights 
  reserved.
-->
 
  <body bgcolor="white">
    <h1> Request Information </h1>
    <font size="4">
      JSP Request Method: <%= request.getMethod() %>
      <br>
      Request URI: <%= request.getRequestURI() %>
      <br>
      Request Protocol: <%= request.getProtocol() %>
      <br>
      Servlet path: <%= request.getServletPath() %>
      <br>
      Path info: <%= request.getPathInfo() %>
      <br>
      Path translated: <%= request.getPathTranslated() %>
      <br>
      Query string: <%= request.getQueryString() %>
      <br>
      Content length: <%= request.getContentLength() %>
      <br>
      Content type: <%= request.getContentType() %>
      <br>
      Server name: <%= request.getServerName() %>
      <br>
      Server port: <%= request.getServerPort() %>
      <br>
      Remote user: <%= request.getRemoteUser() %>
      <br>
      Remote address: <%= request.getRemoteAddr() %>
      <br>
      Remote host: <%= request.getRemoteHost() %>
      <br>
      Authorization scheme: <%= request.getAuthType() %> 
      <hr>
      The browser you are using is <%= request.getHeader("User-Agent") %>
      <hr>
    </font>
  </body>
</html>

请注意以下几点:

  • 这段代码与 HTML 非常相似。但是,它包含 <%= 表达式 %> 标签,这是 JSP 语言特有的。JSP 编译器会将 HTML 文本中的整个标签替换为表达式的值。
  • 本例使用了 Java `request` 对象的方法,该对象即我们在研究 Servlet 时已接触过的 `request` 对象。因此,它是一个 `HttpServletRequest` 对象。这样,标签 `<%= request.getRemoteHost() %>` 在 HTML 代码中将被替换为发起请求的 Web 客户端机器的名称。
  • 虽然使用 Servlet 也能实现相同的效果,但在这种情况下,网页的结构会更加直观。

3.2.2. 获取 Web 客户端发送的参数

这里我们重新回顾之前在 Servlet 中研究过的示例。向浏览器展示一个表单:

Image

响应上述请求后,浏览器将收到以下页面:

Image

该 JSP 页面的代码如下:


<%
  // variables locales à la procédure principale
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>
 
<!-- code HTML -->
<html>
  <head>
    <title><%= title %></title>
  </head>
  <body bgcolor="white">
    <h3><%= title %></h3>
    <%
      if (firstName != null || lastName != null) {
        out.println("firstname= " + firstName + "<br>");
        out.println("lastname= " + lastName);
      } else {
        out.println("pas de paramètres");
      }
    %>
    <P>
    <form method="POST">
      firstname= <input type="text" size="20" name="firstname">
      <br>
      lastname= <input type="text" size="20" name="lastname">
      <br>
      <input type="submit">
    </form>
  </body>
</html>
  • 虽然我们再次看到了与前一个示例相同的 <%= 表达式 %> 标签,但出现了一个新标签:<% Java 指令; %><% 标签用于引入 Java 代码。当遇到闭合标签 %> 时,该代码即告结束。
  • 前面的全部代码(HTML + JSP)将被转换为一个 Java Servlet。它将被封装在一个方法中,即 JSP 页面的 main 方法。这就是为什么在 JSP 页面开头声明的 Java 变量可以在散布于 HTML 中的其他 JSP 代码部分中访问:这些变量和代码段将属于同一个 Java 方法。 然而,如果我们的 JSP 代码中包含方法,由于方法作用域的限制,变量 `title`、`firstname` `lastname` 在这些方法内部将不可访问。我们需要将它们设为全局变量,或者作为参数传递给方法。我们稍后将再次讨论这一点。
  • 要在 HTML 代码中包含动态内容,有两种方法:<%= 表达式 %> out.println(表达式)out 对象是一个输出流,类似于 Servlet 示例中遇到的同名对象,但类型不同:它是 JspWriter 对象,而非 PrintWriter。它允许使用 print println 方法向 HTML 流中写入内容。
  • 与等效的 Servlet 相比,JSP 页面更能体现生成的 HTML 页面的结构。

3.2.3. JSP 标签

以下是您在 JSP 页面中可能会遇到的标签及其含义。

标签
含义
<!-- 注释 -->
HTML 注释。已发送至客户端。
<%-- 注释 --%>
JSP 注释。不会发送给客户端。
<%! 声明、方法 %>
声明全局变量和方法。这些变量在所有方法中均可使用
<%= 表达式 %>
表达式的值将插入到 HTML 页面中,代替该标签
<% Java 代码 %>
包含 Java 代码,该代码将成为 JSP 页面 main 方法的一部分
<%@ page attribute1=value1
属性2=值2 … %>
用于设置 JSP 页面的属性。例如:
import="java.util.*,java.sql.*" 用于指定 JSP 页面所需的库
extends="aParentClass" 用于让 JSP 页面继承自另一个类

3.2.4. JSP 隐式对象

在之前的示例中,我们遇到了两个未声明的对象:requestout。它们是 JSP 页面转换为 Servlet 时自动定义的对象。这些被称为隐式对象或预定义对象。还有其他对象,但这些是与 response 对象一起最常用的:

对象
含义
HttpServletRequest request
可通过该对象访问 Web 客户端的请求(getParameter、getParameterNames、getParameterValues
HttpServletResponse response
用于构建 Web 服务器对客户端响应的对象。允许您设置要发送给 Web 客户端的 HTTP 头部。
JspWriter out
用于将 HTML 代码发送给客户端的输出流(printprintln

3.2.5. 将 JSP 页面转换为 Servlet

让我们重新审视 myRequestParamExample.jsp 中的 JSP 代码:


<%
  // variables locales à la procédure principale
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>
 
<!-- code HTML -->
<html>
  <head>
    <title><%= title %></title>
  </head>
  <body bgcolor="white">
    <h3><%= title %></h3>
    <%
      if (firstName != null || lastName != null) {
        out.println("firstname= " + firstName + "<br>");
        out.println("lastname= " + lastName);
      } else {
        out.println("pas de paramètres");
      }
    %>
    <P>
    <form method="POST">
      firstname= <input type="text" size="20" name="firstname">
      <br>
      lastname= <input type="text" size="20" name="lastname">
      <br>
      <input type="submit">
    </form>
  </body>
</html>

当浏览器向 Tomcat 服务器请求此 JSP 页面时,服务器会将其转换为 Servlet。如果请求的 URL 是

http://localhost:8080/examples/jsp/perso/intro/myRequestParamExample.jsp,Tomcat 4.x 会将生成的 Servlet 放置在目录 <tomcat>\work\localhost\examples\jsp\perso\intro 中:

Image

该名称反映了 JSP 页面的 URL,即 http://localhost:8080/examples/jsp/perso/intro/myRequestParamExample.jsp。如上所示,我们可以访问为该 JSP 页面生成的 Servlet 的 Java 代码。在本示例中,其内容如下:

package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;


public class myRequestParamExample$jsp extends HttpJspBase {


    static {
    }
    public myRequestParamExample$jsp( ) {
    }

    private static boolean _jspx_inited = false;

    public final void _jspx_init() throws org.apache.jasper.runtime.JspException {
    }

    public void _jspService(HttpServletRequest request, HttpServletResponse  response)
        throws java.io.IOException, ServletException {

        JspFactory _jspxFactory = null;
        PageContext pageContext = null;
        HttpSession session = null;
        ServletContext application = null;
        ServletConfig config = null;
        JspWriter out = null;
        Object page = this;
        String  _value = null;
        try {

            if (_jspx_inited == false) {
                synchronized (this) {
                    if (_jspx_inited == false) {
                        _jspx_init();
                        _jspx_inited = true;
                    }
                }
            }
            _jspxFactory = JspFactory.getDefaultFactory();
            response.setContentType("text/html;charset=ISO-8859-1");
            pageContext = _jspxFactory.getPageContext(this, request, response,
                        "", true, 8192, true);

            application = pageContext.getServletContext();
            config = pageContext.getServletConfig();
            session = pageContext.getSession();
            out = pageContext.getOut();


                   // variables local to the main procedure
                  String title="Récupération des paramètres d'un formulaire";
                  String firstName = request.getParameter("firstname");
                  String lastName = request.getParameter("lastname");

                out.write("\r\n\r\n<!-- code HTML -->\r\n<html>\r\n  <head>\r\n    <title>");
                out.print( title );
                out.write("</title>\r\n  </head>\r\n  <body bgcolor=\"white\">\r\n    <h3>");
                out.print( title );
                out.write("</h3>\r\n    ");

                      if (firstName != null || lastName != null) {
                        out.println("firstname= " + firstName + "<br>");
                        out.println("lastname= " + lastName);
                      } else {
                        out.println("pas de paramètres");
                      }

                out.write("\r\n    <P>\r\n    <form method=\"POST\">\r\n      firstname= <input type=\"text\" size=\"20\" name=\"firstname\">\r\n      <br>\r\n      lastname= <input type=\"text\" size=\"20\" name=\"lastname\">\r\n      <br>\r\n      <input type=\"submit\">\r\n    </form>\r\n  </body>\r\n</html>\r\n");

        } catch (Throwable t) {
            if (out != null && out.getBufferSize() != 0)
                out.clearBuffer();
            if (pageContext != null) pageContext.handlePageException(t);
        } finally {
            if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);
        }
    }
}

生成的代码相当复杂。我们将仅关注以下几点:

  • Servlet 的 main 方法如下:
    public void _jspService(HttpServletRequest request, HttpServletResponse  response)
        throws java.io.IOException, ServletException {

该方法在 Servlet 启动时被调用。我们可以看到它接受两个参数:客户端的请求对象以及用于生成对 Web 客户端响应的响应对象。

  • 在 main 方法中,声明了一个名为 out 的 JspWriter 对象并对其进行初始化。该对象用于通过 out.print("HTML 代码") 语句将 HTML 代码发送给客户端。
        JspWriter out = null;
...
            out = pageContext.getOut();
  • Java 代码


<%
  // variables locales à la procédure principale
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>

已完整包含在 Servlet 的 main _jspService 方法中。位于 <%… %> 标签内的任何代码也同样适用

  • JSP 页面的 HTML 代码通过 out.print("codeHTML") out.write(...) 语句输出。例如

                out.write("</title>\r\n  </head>\r\n  <body bgcolor=\"white\">\r\n    <h3>");
  • 在此示例中,除了主方法 _jspService 之外,没有其他方法。

3.2.6. JSP 页面的方法和全局变量

请看以下 JSP 页面:


<%!
  // la balise précédente démarre la partie variables et méthodes globales
  // cette partie sera reprise sans modification dans la servlet
 
  // une variabe globale
  String prenom="inconnu";
 
  // une méthode  
  private String sonChien(){
    return "milou";
  }//sonChien
 
  // une autre méthode
  private void afficheAmi(JspWriter out) throws Exception{
    out.println("<p>Son ami s'appelle Haddock</p>");
  }//afficheAmi
 
  // fin de la partie globale de la servlet
%>  
 
<%
  // la balise précédente indique que le code qui suit sera enregistré
  // dans la méthode principale de la servlet
 
  // variable locale à la méthode principale
  String nom="tintin";
%>
 
 
<%-- code HTML --%>
<html>
  <head>
    <title>Page JSP</title>
  </head>
  <body>
    <center>
      <h2>Page JSP</h2>
      <p>Son nom est <%= nom %></p>
      <p>Son prénom est <%= prenom %></p>
      <p>Son chien s'appelle <%= sonChien() %></p>
      <%
        // le nom de son ami
        afficheAmi(out);
      %>
    </center>
  </body>
</html>

此 JSP 页面生成以下网页:

Image

让我们来看看上面这四行是如何生成的:


      <p>Son nom est <%= nom %></p>
      <p>Son prénom est <%= prenom %></p>
      <p>Son chien s'appelle <%= sonChien() %></p>
      <%
        // le nom de son ami
        afficheAmi(out);
      %>

上面的代码位于 <%..%> 标签内,因此将成为生成的 Servlet 的 main _jspService 方法的一部分。它们是如何访问 lastNamefirstName 变量以及 hisDogdisplayFriend 方法的?

name (tintin)
是 JSP 页面 main 方法的局部变量,因此该方法内部可以访问它
firstName (未知)
是 JSP 页面的全局变量,因此在 main 方法中可见
sonChien (milou)
是 JSP 页面的公共方法,因此可在 main 方法中访问
displayFriend (Haddock)
是 JSP 页面的公共方法,因此可在 main 方法中调用。请注意,out 对象作为参数传递给了该方法。此处必须如此。实际上,out 对象是在 Servlet 的 main 方法中声明并初始化的,并非全局变量。

现在,让我们看看从该 JSP 页面生成的 Java Servlet 代码(已去除冗余代码):

package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;


public class tintin$jsp extends HttpJspBase {

          // the preceding tag starts the global variables and methods section
           // this part will be adopted unchanged in the servlet

           // global variability
          String prenom="inconnu";

           // a method  
          private String sonChien(){
            return "milou";
          }//sonChien

           // another method
          private void afficheAmi(JspWriter out) throws Exception{
            out.println("<p>Son ami s'appelle Haddock</p>");
          }//afficheAmi

           // end of the global part of the servlet

    static {
    }
    public tintin$jsp( ) {
    }

    private static boolean _jspx_inited = false;

    public final void _jspx_init() throws org.apache.jasper.runtime.JspException {
    }

    public void _jspService(HttpServletRequest request, HttpServletResponse  response)
        throws java.io.IOException, ServletException {

        JspFactory _jspxFactory = null;
        PageContext pageContext = null;
        HttpSession session = null;
        ServletContext application = null;
        ServletConfig config = null;
        JspWriter out = null;
        Object page = this;
        String  _value = null;
        try {

            if (_jspx_inited == false) {
                synchronized (this) {
                    if (_jspx_inited == false) {
                        _jspx_init();
                        _jspx_inited = true;
                    }
                }
            }
            _jspxFactory = JspFactory.getDefaultFactory();
            response.setContentType("text/html;charset=ISO-8859-1");
            pageContext = _jspxFactory.getPageContext(this, request, response,
                        "", true, 8192, true);

            application = pageContext.getServletContext();
            config = pageContext.getServletConfig();
            session = pageContext.getSession();
            out = pageContext.getOut();

                out.write("  \r\n\r\n");
                  // the preceding tag indicates that the following code will be saved
                   // in the servlet's main method

                   // variable local to the main method
                  String nom="tintin";

                out.write("\r\n\r\n\r\n");
                out.write("\r\n<html>\r\n  <head>\r\n    <title>Page JSP</title>\r\n  </head>\r\n  <body>\r\n    <center>\r\n      <h2>Page JSP</h2>\r\n      <p>Son nom est ");
                out.print( nom );
                out.write("</p>\r\n      <p>Son prénom est ");
                out.print( prenom );
                out.write("</p>\r\n      <p>Son chien s'appelle ");
                out.print( sonChien() );
                out.write("</p>\r\n      ");

                        // his friend's name
                        afficheAmi(out);

                out.write("\r\n    </center>\r\n  </body>\r\n</html>\r\n");
        } catch (Throwable t) {
            if (out != null && out.getBufferSize() != 0)
                out.clearBuffer();
            if (pageContext != null) pageContext.handlePageException(t);
        } finally {
            if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);
        }
    }
}

如上所示,位于 JSP 标签 <%! .. %> 之间的 Java 代码已被完整包含,且不属于 Servlet 的 main 方法 _jspService。在此部分声明的变量是实例变量,因此对所有方法而言都是全局的;此外,这里还可以定义除 _jspService 以外的其他方法。


  // this part will be adopted unchanged in the servlet
 
   // global variability
  String prenom="inconnu";
 
   // a method
  private String sonChien(){
    return "milou";
  }//sonChien
 
   // another method
  private void afficheAmi(JspWriter out) throws Exception{
    out.println("<p>Son ami s'appelle Haddock</p>");
  }//afficheAmi
 
   // end of the global part of the servlet

3.2.7. 在 Tomcat 服务器上部署和调试 JSP 页面

当您想要构建一个 JSP 页面并在 Tomcat 服务器上使用时,就会面临一个问题:该将该页面放置在服务器目录结构的何处。实现方法有多种,我们稍后会再讨论。 目前,最简单的方法是将 JSP 页面放置在目录树 <tomcat>\webapps\examples\jsp(Tomcat 4.x)中的某个文件夹内,其中 <tomcat> 是 Tomcat 的安装目录。 因此,前一个示例中的 URL 为 http://localhost:8080/examples/jsp/perso/tintin/tintin.jsp。这意味着 tintin.jsp 页面位于 <tomcat>\webapps\examples\jsp\perso\tintin 文件夹中。

JSP 页面会被转换为 Java 源文件,当浏览器请求该 JSP 页面的 URL 时,Tomcat 会对其进行编译。此过程中可能会发生编译错误。Tomcat 4.x 会在返回给浏览器的响应中报告这些错误,并明确指出 .java 文件中哪几行存在错误。错误可能由多种原因引起:

  1. 页面上的 JSP 代码有误(例如,使用的 JSP 标签有错误)
  2. JSP 页面中包含的 Java 代码有误

第一种原因可通过检查页面的 JSP 代码来排除。第二种原因可通过检查 Java 代码来排除。这可以通过使用 JBuilder 等工具直接编译为 JSP 页面生成的 .java 文件来实现,该工具提供的调试功能比 Tomcat 更强大。

3.2.8. 示例

我们将重新回顾之前介绍过的那个涉及Servlet的示例:用户从列表中选择一个数字,服务器会告知用户所选数字,同时返回该列表,并将用户选择的元素高亮显示:

Image

为了构建此页面,我们调用了 Servlet 代码并进行了如下修改:

  • 我们将不生成 HTML 代码的 Java 代码保留原样
  • 将生成 HTML 代码的 Java 代码转换为 HTML 和 JSP 代码的混合形式

最终生成了以下 JSP 页面:


 
<%@ page import="java.sql.*, java.util.*" %>
 
<%!
 
        // variables globales de l'application
        // le titre de la page
        private final String title="Génération d'un formulaire";
        // la base de données des valeurs de liste
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
        // valeurs de liste
        private String[] valeurs=null;
        // msg d'erreur
        private String msgErreur=null;
 
        // initialisation de la page JSP - n'est exécutée qu'une seule fois
        public void jspInit(){
            // remplit le tableau des valeurs à partir d'une base de données ODBC
            // de nom DSN : DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                // connexion à la base ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                // objet Statement
                st=connexion.createStatement();
                // exécution requête select pour récupérer les valeurs
                rs=st.executeQuery("select valeur from Tvaleurs");
                // les valeurs sont récupérées et mises dans un tableau dynamique
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // on enregistre la valeur dans la liste
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                // transformation liste --> tableau
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                // problème
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//init
 
%>
 
<%
    // code de _jspService exécuté à chaque requête cliente
  // y-at-il eu une erreur lors de l'initialisation de la page JSP ?
  if(msgErreur!=null){
%>
       <!-- code HTML -->
    <html>
        <head>
          <title>Erreur</title>
      </head>
      <body>
          <h3>Application indisponible (<%= msgErreur %></h3>
      </body>
    </html>
<%
      // fin de jspService
    return;
  }//if
 
    // on récupère l'éventuel choix de l'utilisateur
    String choix=request.getParameter("cmbValeurs");
    if(choix==null) choix="";
%>
 
  <%-- pas d'erreur - code HTML de la page normale --%>
      <html>
        <head>
          <title><%= title %></title>
      </head>
      <body>
          <h3>Choisissez une valeur</h3>
          <form method="POST">
            <select name="cmbValeurs">
              <%
                // affichage dynamique des valeurs
                          String selected="";
                          for (int i=0;i<valeurs.length;i++){
                              if(valeurs[i].equals(choix)) selected="selected"; else selected="";
                              out.println("<option "+selected+">"+valeurs[i]+"</option>");
                          }//for
            %>
          </select>
          <input type="submit" value="Envoyer">
        </form>
<%
        // y-avait-il une valeur choisie ?
                if(! choix.equals("")){
        // on affiche le choix de l'utilisateur
%>        
                <hr>Vous avez choisi le nombre<h2><%= choix %></h2>
<%        
                }//if
%>        
      </body>
    </html>

请注意以下几点:

  • Servlet 的 import 语句被包含在 <% page import="..." %> 指令中
  • <%! ... %> 标签用于封装应用程序的全局变量和 Java 方法
  • Servlet 的 init 方法仅在 Servlet 加载时执行一次,对于 JSP 页面,该方法被称为 jspInit。这两个方法的作用相同。因此,此处完整包含了 Servlet 的 init 方法的代码。
  • Servlet 的实例变量(即必须在多个方法间共享的变量)已原样保留。这些变量主要包括 titlevalues msgError,它们随后将在 JSP 代码中被使用。
  • <% ... %> 标签包围的 Java 代码将被包含在 _jspService 方法中,该方法会在客户端发出请求时执行。
  • 与 Servlet 类似,_jspService 方法首先会检查 msgError 变量的值,以确定是否需要生成错误页面。如果发生错误,则生成错误页面并退出(return)。
  • 如果没有错误,则生成包含值列表的表单
  • 完成此操作后,它会检查用户是否选择了某个数字;如果选择了,则在生成的页面上显示该数字

与 Servlet 相比有哪些优势?毫无疑问,生成的 HTML 代码更加清晰。但仍有大量 Java 代码“杂乱”地出现在其中。稍后我们将探讨另一种称为“委托”的方法,通过该方法,我们可以将大部分 Java 代码放在 Servlet 中,而 JSP 页面仅保留 HTML 和 JSP 代码。这将处理部分与呈现部分清晰地分离。

3.3. 在 Tomcat 服务器上部署 Web 应用程序

接下来我们将讲解如何使用 Tomcat 服务器部署 Java Web 应用程序。虽然以下说明针对该服务器,但在其他 J2EE 容器中部署 Java Web 应用程序的步骤与这里描述的类似。

3.3.1. server.xml 和 web.xml 配置文件

到目前为止,为了测试我们的Servlet和JSP页面,我们一直将

  • 将 Servlet 放置在 <tomcat>\webapps\examples\WEB-INF\classes 文件夹中。随后可通过 URL http://localhost:8080/examples/servlet/nomServlet
  • JSP 页面则位于 <tomcat>\webapps\examples\jsp 目录树中。通过 URL http://localhost:8080/examples/jsp/nomPageJSP 即可访问这些页面

我们从未解释过为何如此。Tomcat 服务器通过位于 <tomcat>\conf 目录下的 server.xml 文本文件进行配置:

Image

该文本文件实际上是一个 XML(扩展标记语言)文件。XML 文档是一种包含标签的文本文档,与 HTML 文档类似。然而,HTML 标签有明确的定义,而 XML 标签则没有。因此,以下文档即为一个 XML 文档:

<personne>
    <prenom>Pierre</prenom>
  <nom>Lucas</nom>
  <age>28</age>
</personne>

XML文档本质上是一种遵循特定标记规则的“标记化”文档:

  • 标记文本的形式为 <xx att1="val1" att2="val2" ....>text</xx>
  • 标签可以独立存在,形式为 <xx att1="val1" att2="val2" ..../>

“att”字段被称为“xx”标签的属性,而“val”字段则是与这些属性关联的值。有些 HTML 文档并非有效的 XML 文档。例如,HTML 标签 <br> 不是一个有效的 XML 标签。为了符合所有 XML 标签都必须闭合的规则,它应写为 <br/> 才算有效。 一种名为 XHTML 的 HTML 变体应运而生,旨在确保每个 XHTML 文档都是有效的 XML 文档。某些现代浏览器能够显示 XML 文件。因此,如果我们将上例中的 XML 文档命名为“person.xml”,并使用 IE6 浏览它,则会看到如下显示:

Image

IE6 能识别这些标签并对其进行高亮显示。它还能根据标签识别文档的结构。因此,如果我们将以下文档命名为“person2.xml”

<personne><prenom>Pierre</prenom><nom>Lucas</nom><age>28</age></personne>

并使用 IE6 查看时,显示效果如下:

Image

IE6正确识别了文档的结构和内容。XML文档的核心价值正体现在这一特性上:可以轻松提取XML文档的结构和内容。这通过一种称为XML解析器的程序来实现。XML文档正逐渐成为网络文档交换的标准。 假设机器 A 需要向机器 B 发送一个 DOC 文档。该 DOC 文档由数据库 DB-A 中包含的信息构建而成。机器 B 必须将该 DOC 文档存储在数据库 DB-B 中。交换过程可如下进行:

  • 机器 A 从数据库 DB-A 中检索数据,并将其封装在 XML 文本文档中
  • 将该 XML 文档通过网络发送至机器 B
  • 机器 B 使用 XML 解析器分析接收到的文档,并提取其结构和数据(正如我们在示例中 IE6 所做的那样)。随后,它可以将接收到的数据存储在数据库 DB-B 中

关于XML语言,我们不再赘述,因为它值得单独写一本书。

因此,Tomcat 通过 XML 文件 server.xml 进行配置。如果我们使用 IE6 查看该文件,会看到一个复杂的文档。我们仅关注以下几行:

Image

这里我们关注的是 <Context ...> 标签。它用于定义 Web 应用程序。其中有两个属性值得特别注意:

  • path:这是 Web 应用程序的名称
  • docBase:这是应用程序所在的文件夹。这里是一个相对路径:examples。相对于哪个文件夹?答案同样可以在 server.xml 文件的以下行中找到:

Image

上述行定义了 Web 服务器:

  • name:Web 服务器的名称
  • appBase:其所服务的文档树的根目录。这里同样是一个相对路径:webapps。它是相对于 Tomcat 服务器安装目录 <tomcat> 的。因此,这指的是 <tomcat>\webapps 文件夹。

示例 Web 应用程序将其文件存储在 examples 文件夹中(参见上文的 docBase)。该名称是相对于服务器 Web 目录树的根目录而言的,即 <tomcat>\webapps。因此,具体路径为 <tomcat>\webapps\examples 文件夹。让我们来仔细看看这个文件夹:

Image

在那里,我们可以找到 WEB-INF\classes 文件夹,我们曾将用于测试的 Servlet 存放在此处。WEB-INF 文件夹中包含一个名为 web.xml 的文件:

Image

该文件用于配置示例 Web 应用程序。我们暂不深入探讨该文件的细节,因为目前它过于复杂。我们仅关注以下几行代码:

    <servlet>
      <servlet-name>
          servletToJsp
      </servlet-name>
      <servlet-class>
          servletToJsp
      </servlet-class>
    </servlet>

<servlet> 标签用于在 Web 应用程序中定义一个 Servlet。请注意,此处的 Web 应用程序是 examplesservlet 标签包含另外两个标签:

  • <servlet-name>servletToJsp</servlet-name>:定义 Servlet 的名称
  • <servlet-name>servletToJsp</servlet-name>:定义在请求 Servlet 时要执行的类名称。在此示例中,Servlet 及其类名称相同。这并非强制要求。

浏览器是如何向 Tomcat 服务器请求 servletToJsp 这个 Servlet 的?

  • 浏览器请求 URL http://localhost:8080/examples/servlet/servletToJsp
  • Tomcat 解析 Servlet 路径 /examples/servlet/servletToJsp。它将路径的前半部分 /examples 解释为 Web 应用程序的名称,并查阅其 server.xml 配置文件以确定该应用程序的文档存储位置。如前所述,该位置位于 <tomcat>\webapps\examples 文件夹中。
  • Tomcat 使用 Servlet 路径的其余部分在 examples Web 应用程序中定位该 Servlet。路径 /servlet/servletToJsp 表示必须执行名为 servletToJsp 的 Servlet。随后,Tomcat 将读取 examples 应用程序的 web.xml 配置文件,该文件位于 <tomcat>\webapps\examples\WEB-INF 目录下。 它将在该文件中发现,servletToJsp Servlet 与 Java 类 servletToJsp 相关联(参见上文的 web.xml 文件)。随后,它将在 examples Web 应用程序的 WEB-INF\classes 文件夹中(即 <tomcat>\webapps\examples\WEB-INF\classes)查找该类并执行它。

Image

3.3.2. 示例:部署“list”Web应用程序

我们将重新审视一个已经学过的 Servlet,该 Servlet 向用户展示了一组数字列表,用户从中选择一个数字。随后,Servlet 会确认用户所选的数字:

Image

如上图浏览器地址栏所示,该 Servlet 的类文件名为 gener3。根据前文的说明:

  • URL /examples/servlet/gener3 表明这是一个来自示例 Web 应用程序的名为 gener3 的 Servlet
  • 在 examples 应用程序的 web.xml 文件中,并没有任何关于名为 gener3 的 Servlet 的记录。那么 Tomcat 是如何找到它的呢?我已经仔细检查了整个 web.xml 文件,却无法给出确切的答案……这个问题依然悬而未决……

我们选择将 gener3.class Servlet 以 lstValeurs 的名称部署在位于 E:\data\serge\Servlets\lstValeurs 文件夹中的 liste Web 应用程序下:

Image

我们将 gener3.class 文件放置在上述提到的 WEB-INF\classes 文件夹中:

Image

我们通过在 server.xml 文件中,在定义 manager Web 应用程序的相关行之上添加以下内容,来配置 liste Web 应用程序:

                 <!-- Perso: lstValeurs -->
                <Context path="/liste" docBase="e:/data/serge/servlets/lstValeurs" />

                <!-- Tomcat Manager Context -->
                <Context path="/manager" docBase="manager" debug="0" privileged="true" />
                <!-- Tomcat Examples Context -->
                <Context path="/examples" docBase="examples" debug="0" reloadable="true" crossContext="true">
........

定义 lstValeurs 应用程序的这一行表明,该应用程序位于 e:/data/serge/servlets/lstValeurs 文件夹中。现在我们需要为该应用程序定义 web.xml 文件。该文件将定义应用程序的唯一 Servlet:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
      <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
</web-app>

上述文件表明,名为 lstValeurs 的 Servlet 与类文件 gener3.class 相关联。此 web.xml 文件必须创建并保存在 liste 应用程序的 WEB-INF 文件夹中:

Image

上图所示的截图中,src 文件夹内放置了源文件 gener3.java。该文件夹可能并不存在,在本演示中它没有任何实际用途。现在我们可以开始运行测试了:

  • 停止并重启 Tomcat,以便它重新读取 server.xml 配置文件。这里我们使用的是 Windows 系统。在 Unix 系统上,您可以强制 Tomcat 重新读取配置文件而无需停止服务。
  • 使用浏览器访问 URL http://localhost:8080/liste/servlet/lstValeurs

Image

我们可以看到,之前的 URL 包含关键字 *servlet*,这与迄今为止使用的所有 Servlet URL 一样。我们可以通过在应用程序的 web.xml 文件中将 lstValeurs Servlet 与一个 URL 模式(*url-pattern*)关联起来,来消除这一点:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
      <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
  <servlet-mapping>
      <servlet-name>lstValeurs</servlet-name>
    <url-pattern>/valeurs</url-pattern>
  </servlet-mapping>
</web-app>

<servlet-mapping> 标签中,我们将路径 /values 与前几行定义的 lstValeurs Servlet 关联起来。保存新的 web.xml 文件,并访问 URL http://localhost:8080/liste/valeurs

Image

3.3.3. 部署 Web 应用程序的公共页面

我们刚刚了解了如何部署由单个 Servlet 组成的 Web 应用程序。一个 Web 应用程序可能包含许多组件:Servlet、JSP 页面、HTML 文件、Java 小程序等。我们该将这些应用程序元素放置在哪里呢? 如果 <application> 是 Tomcat 配置文件 server.xml 中通过应用程序的 docBase 属性定义的 Web 应用程序目录( ),那么我们已经看到 Servlet 被放置在 <application>\WEB-INF\classes 目录下。其他应用程序组件可以放置在 <application> 目录树中的任何位置,但不能放在 WEB-INF 目录内。以我们之前研究的 JSP 应用程序 listvaleurs.jsp 为例:

Image

该 JSP 页面存储在 <tomcat>\webapps\examples\jsp\perso\listvaleurs 文件夹中。该页面可以是之前部署的 list 应用程序的一个组件。让我们将 listvaleurs.jsp 文件直接放置在该应用程序的文件夹中:

Image

回顾 server.xml 文件中 liste 应用程序的配置:

                <Context path="/liste" docBase="e:/data/serge/servlets/lstValeurs" />

任何以路径 /liste 开头的 URL 都被视为 liste 应用程序的一部分,系统将在指定的文件夹中查找该 URL。现在,让我们使用浏览器访问 URL http://localhost:8080/liste/listvaleurs.jsp

Image

我们确实获得了预期的 JSP 页面。

3.3.4. Servlet 初始化参数

我们已经看到,Servlet 通过文件 <application>\WEB-INF\web.xml 进行配置,其中 <application> 是该 Servlet 所属的 Web 应用程序的文件夹。可以在该文件中包含 Servlet 初始化参数。让我们回到 liste Web 应用程序中的 lstValeurs Servlet,其配置文件如下:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
      <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
  <servlet-mapping>
      <servlet-name>lstValeurs</servlet-name>
    <url-pattern>/valeurs</url-pattern>
  </servlet-mapping>
</web-app>

与该 Servlet 关联的类是 gener3 类。该类的源代码中定义了几个常量:

public class gener3 extends HttpServlet{
        // page title
        private final String title="Génération d'un formulaire";
         // the list values database
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";

让我们回顾一下上面定义的四个常量的含义:

标题
Servlet 生成的 HTML 文档的标题
DSNValues
Servlet从中检索数据的ODBC数据库的DSN名称
admDbValues
具有上述数据库读取权限的用户名
dbPasswordValues
该用户的密码

如果 DSNValeurs 数据库的管理员更改了用户 admDbValeurs 的密码,则必须修改 Servlet 源代码并重新编译。这并不太实用。Servlet 的 web.xml 配置文件提供了一种替代方案,允许使用 <init-param> 标签定义 Servlet 初始化参数:

    <init-param>
        <param-name>...</param-name>
        <param-value>...</param-value>
    </init-param>
<param-name>
允许您定义参数名称
<param-value>
定义与前一个参数关联的值

Servlet 可以通过以下方法访问其初始化参数:

[Servlet].getServletConfig()
Servlet 类的成员方法,用于 Web 编程的 HttpServlet 类即由此类派生而来。该方法返回一个 ServletConfig 对象,用于访问 Servlet 的配置参数。
[ServletConfig].getInitParameter("parameter")
ServletConfig 类的成员方法,用于返回初始化参数 "parameter" 的值

我们使用以下新的 web.xml 文件来配置“liste”应用程序:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
      <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
    <servlet>
      <servlet-name>lstValeurs2</servlet-name>
    <servlet-class>gener5</servlet-class>
    <init-param>
        <param-name>title</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
    <init-param>
        <param-name>DSNValeurs</param-name>
      <param-value>odbc-valeurs</param-value>
    </init-param>
    <init-param>
        <param-name>admDbValeurs</param-name>
      <param-value>admDbValeurs</param-value>
    </init-param>
    <init-param>
        <param-name>mdpDbValeurs</param-name>
      <param-value>mdpDbValeurs</param-value>
    </init-param>   
  </servlet>
  <servlet-mapping>
      <servlet-name>lstValeurs</servlet-name>
    <url-pattern>/valeurs</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
      <servlet-name>lstValeurs2</servlet-name>
    <url-pattern>/valeurs2</url-pattern>
  </servlet-mapping>
</web-app>

列表应用程序中,我们定义了一个名为 lstValeurs2 的第二个 Servlet,并与 gener5 类文件相关联。该文件已放置在 <application>\WEB-INF\classes 目录下

Image

lstValeurs2 Servlet 有四个初始化参数:titleDSNValeursadmDbValeursmdpDbValeurs。此外,已通过 <servlet-mapping> 标签为该 Servlet 定义了别名 /values2。因此,可以通过 URL http://localhost:8080/liste/valeurs2 访问列表应用程序中的 lstValues2 Servlet。

为获取 Servlet 的初始化参数,已对 Servlet 的源代码进行了如下修改:

public class gener5 extends HttpServlet{
    // page title
    private String title=null;
    // the list values database
    private String DSNValeurs=null;
    private String admDbValeurs=null;
    private String mdpDbValeurs=null;
...............

         // servlet initialization
        public void init(){
             // retrieve servlet initialization parameters
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");
            DSNValeurs=config.getInitParameter("DSNValeurs");
            admDbValeurs=config.getInitParameter("admDbValeurs");
            mdpDbValeurs=config.getInitParameter("mdpDbValeurs");

             //have all the parameters been recovered?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // table of values is filled from a ODBC database
             // name DSN : DSNvaleurs
...............

要测试该 Servlet,必须重启 Tomcat,以便其加载“liste”应用程序的新 web.xml 配置文件。使用浏览器访问该 Servlet 的 URL:http://localhost:8080/liste/valeurs2

Image

如果 web.xml 文件中缺少 Servlet 所需的任何初始化参数,将显示以下页面:

Image

3.3.5. Web 应用程序的初始化参数

在上一个示例中,只有 lstValeurs2 Servlet 可以访问 titleDSNValeursadmDbValeursmdpDbValeurs 参数。同一 liste 应用程序中的另一个 Servlet 可能需要使用 lstValeurs2 Servlet 所使用的同一数据库中的数据。 在这种情况下,需要在新的 servlet 的 web.xml 文件的配置部分中重新定义 DSNValeursadmDbValeursmdpDbValeurs 参数。另一种解决方案是在应用程序级别(而非 servlet 级别)定义多个 servlet 共用的参数。该应用程序的新 web.xml 文件如下所示:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

  <context-param>
      <param-name>DSNValeurs</param-name>
    <param-value>odbc-valeurs</param-value>
  </context-param>
  <context-param>
      <param-name>admDbValeurs</param-name>
    <param-value>admDbValeurs</param-value>
  </context-param>
  <context-param>
      <param-name>mdpDbValeurs</param-name>
    <param-value>mdpDbValeurs</param-value>
  </context-param>   

    <servlet>
      <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
    <servlet>
      <servlet-name>lstValeurs3</servlet-name>
    <servlet-class>gener6</servlet-class>
    <init-param>
        <param-name>title</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
      <servlet-name>lstValeurs</servlet-name>
    <url-pattern>/valeurs</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
      <servlet-name>lstValeurs3</servlet-name>
    <url-pattern>/valeurs3</url-pattern>
  </servlet-mapping>
</web-app>

新Servlet名为lstValeurs3,与gener6类文件相关联,并已与别名/valeurs3关联(Servlet映射)。 title 参数是 Servlet 定义中唯一保留的参数。其余参数已被放置在应用程序配置中的 <context-param> 标签内。该标签用于定义应用程序特有的信息,而非特定 Servlet 或 JSP 页面的信息。Java Servlet 是如何访问这些通常被称为上下文参数的参数的?用于获取上下文信息的方法与用于获取 Servlet 特定初始化参数的方法非常相似:

[Servlet].getServletContext()
这是 Servlet 类的成员方法,用于 Web 编程的 HttpServlet 类即由此派生而来。该方法返回一个 ServletContext 对象,用于访问应用程序的配置参数
[ServletContext].getInitParameter("parameter")
ServletContext 类的此方法返回初始化参数 "parameter" 的值

gener6.java 类仅对之前使用的 gener5.java 中的 Java 代码进行了以下修改:

             // retrieve servlet initialization parameters
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");

            ServletContext context=getServletContext();
            DSNValeurs=context.getInitParameter("DSNValeurs");
            admDbValeurs=context.getInitParameter("admDbValeurs");
            mdpDbValeurs=context.getInitParameter("mdpDbValeurs");

             //have all the parameters been recovered?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // table of values is filled from a ODBC database
             // name DSN : DSNvaleurs
...............

Servlet 特有的 title 参数是通过 ServletConfig 对象获取的。在应用程序级别定义的其他三个参数是通过 ServletContext 对象获取的。我们编译此类,并将其与其他类一样放置在 <application>\WEB-INF\classes 目录下:

Image

我们重启 Tomcat,使其生效应用程序的新 web.xml 文件,并请求 URL http://localhost:8080/liste/valeurs3

Image

3.3.6. JSP 页面的初始化参数

我们已经了解了如何为 Servlet 或 Web 应用程序定义初始化参数。那么,对于 JSP 页面是否也能这样做呢?让我们回到之前学习过的 listvaleurs.jsp 页面的代码开头:

<%@ page import="java.sql.*, java.util.*" %>

<%!
        // variables globales de l'application
        // le titre de la page
        private final String title="Génération d'un formulaire";
        // la base de données des valeurs de liste
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
.........

我们在应用程序的 web.xml 文件中发现了四个常量:titleDSNValuesadmDbValuesmdpDbValues。常量 DSNValuesadmDbValuesmdpDbValues 现在已在应用程序级别定义,因此我们可以假设属于该应用程序的 JSP 页面能够访问它们。事实确实如此。 我们知道,JSP 页面将被转换为 Servlet。Servlet 可以通过 getServletContext() 方法访问上下文。而 title 常量的情况则更为微妙。实际上,我们将其定义在 Servlet 级别而非应用程序级别,如下所示:

    <servlet>
      <servlet-name>lstValeurs3</servlet-name>
    <servlet-class>gener6</servlet-class>
    <init-param>
        <param-name>title</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
  </servlet>

对于 JSP 页面,上述语法已不再适用,因为类文件的概念不再适用。不过,JSP 页面的配置语法与 Servlet 的非常相似,具体如下:

    <servlet>
      <servlet-name>JSPlstValeurs</servlet-name>
    <jsp-file>/listvaleurs2.jsp</jsp-file>
...
  </servlet>

实际上,JSP 页面被视为一个被赋予名称(servlet-name)的 Servlet。我们不将类文件与该 Servlet 关联,而是将待执行的 JSP 页面的源文件(jsp-file)与之关联。 因此,上述代码定义了一个名为 JSPlstValues 的 Servlet,并将其与 JSP 页面 /listvalues2.jsp 关联。路径 /listvalues2.jsp 是相对于应用程序根目录的。因此,对于我们的 list 应用程序而言,文件 listvalues2.jsp 将位于 list 应用程序的 docBase 文件夹中(参见 server.xml):

Image

在应用程序的 web.xml 文件中,JSP 页面的配置如下:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  <context-param>
      <param-name>DSNValeurs</param-name>
    <param-value>odbc-valeurs</param-value>
  </context-param>
  <context-param>
      <param-name>admDbValeurs</param-name>
    <param-value>admDbValeurs</param-value>
  </context-param>
  <context-param>
      <param-name>mdpDbValeurs</param-name>
    <param-value>mdpDbValeurs</param-value>
  </context-param>   
.......      
    <servlet>
      <servlet-name>JSPlstValeurs</servlet-name>
    <jsp-file>/listvaleurs2.jsp</jsp-file>
    <init-param>
        <param-name>JSPtitle</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
  </servlet>
..........
  <servlet-mapping>
      <servlet-name>JSPlstValeurs</servlet-name>
    <url-pattern>/jspvaleurs</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
........
</web-app>

JSP 页面 listvaleurs2.jsp 位于 liste 应用程序的根目录下,并与 JSP servlet 名称 JSPlstValeurs(servlet-name)相关联,该 servlet 又与别名 /jspvaleurs(servlet-mapping)相关联。因此,可以通过 URL http://localhost:8080/liste/jspvaleurs 访问我们的 JSP 页面。

原始的 JSP 页面 listvaleurs.jsp 已重命名为 listvaleurs2.jsp,并在 jspInit() 方法中获取其四个初始化参数:

<%!
         // application global variables
         // page title
        private String title=null;
        // the list values database
        private String DSNValeurs=null;
        private String admDbValeurs=null;
        private String mdpDbValeurs=null;
         // list values
        private String[] valeurs=null;
         // error msg
        private String msgErreur=null;

         // initialization of page JSP - executed only once
        public void jspInit(){

             // retrieve servlet initialization parameters
      ServletConfig config=getServletConfig();
            title=config.getInitParameter("JSPtitle");
      ServletContext context=getServletContext();
            DSNValeurs=context.getInitParameter("DSNValeurs");
            admDbValeurs=context.getInitParameter("admDbValeurs");
            mdpDbValeurs=context.getInitParameter("mdpDbValeurs");

             //have all the parameters been recovered?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // fills the table of values from a ODBC database
             // name DSN : DSNvaleurs
..............

JSP 页面与 Servlet 一样,通过相同的方式获取其初始化参数。上述文件保存在“liste”Web 应用程序的根目录下:

Image

重启 Tomcat 服务器以强制其重新加载应用程序的新 web.xml 配置文件。随后,您可以访问 URL http://localhost:8080/liste/jspvaleurs

Image

3.3.7. Web 应用程序内的 Servlet/JSP 协作

当客户端向 Web 服务器发出请求时,响应可能由多个 Servlet 和 JSP 页面共同生成。此前,响应通常由单个 Servlet 或 JSP 页面生成。我们已经看到,JSP 页面能提供更清晰的生成的 HTML 文档结构可读性。然而,它通常也包含大量 Java 代码。我们可以通过将

  • 将不负责生成响应 HTML 代码的 Java 代码移至一个或多个 Servlet 中
  • ,并将用于生成响应中发送给客户端的各种 HTML 文档的代码保留在 JSP 页面中

这应有助于加强Java代码与HTML代码的分离。我们将把这种新结构应用到我们的列表应用程序中:一个名为lstValeurs4的Java Servlet将负责在启动时从数据库读取数值,然后处理客户端的请求。根据分析结果,客户端的请求将被重定向到错误页面(erreur.jsp)或显示数字列表的页面(liste.jsp)。 因此,该列表应用程序将由一个Servlet和两个JSP页面组成。

Servlet 如何将从客户端接收到的请求传递给另一个 Servlet 或 JSP 页面?我们将使用以下方法:

[ServletContext].getRequestDispatcher(
String url)
方法,该方法属于 ServletContext 类,返回一个 RequestDispatcher 对象。url 参数是我们要将客户端请求转发到的 URL 的名称。这种请求转发只能发生在同一个应用程序内。因此,url 参数是相对于该应用程序 Web 目录结构的路径。
[RequestDispatcher].forward
(ServletRequest request,
 ServletResponse response)
RequestDispatcher 接口的一个方法,用于将客户端的请求响应对象(必须用于构建响应)转发到上一个 URL。
[ServletRequest].setAttribute(String name, Object obj)
当一个 Servlet 或 JSP 页面将请求传递给另一个 Servlet 或 JSP 页面时,通常不仅需要传递客户端的请求,还需要传递其他信息——即其自身处理该请求时生成的信息。 ServletRequest 类的 setAttribute 方法允许您以类似于 (属性, 值) 对字典的格式向客户端的请求对象添加属性,其中属性是属性的名称,值是表示其值的任何对象。
[ServletRequest].getAttribute(
String attribute)
允许您检索请求属性的值。该方法将由接收该请求的 Servlet 或 JSP 页面使用,以获取添加到请求中的信息。

负责处理表单的 Servlet 将在 web.xml 文件中配置如下:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  <context-param>
      <param-name>DSNValeurs</param-name>
    <param-value>odbc-valeurs</param-value>
  </context-param>
  <context-param>
      <param-name>admDbValeurs</param-name>
    <param-value>admDbValeurs</param-value>
  </context-param>
  <context-param>
      <param-name>mdpDbValeurs</param-name>
    <param-value>mdpDbValeurs</param-value>
  </context-param>   

............
    <servlet>
      <servlet-name>lstValeurs4</servlet-name>
    <servlet-class>gener7</servlet-class>
    <init-param>
        <param-name>title</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
    <init-param>    
        <param-name>JSPerreur</param-name>
      <param-value>/erreur.jsp</param-value>
    </init-param>      
    <init-param>      
        <param-name>JSPliste</param-name>
      <param-value>/liste.jsp</param-value>
    </init-param>      
    <init-param>      
        <param-name>URLservlet</param-name>
      <param-value>/liste/valeurs4</param-value>
    </init-param>      
  </servlet>
...........
  <servlet-mapping>
      <servlet-name>lstValeurs4</servlet-name>
    <url-pattern>/valeurs4</url-pattern>
  </servlet-mapping>
.......
</web-app>

lstValeurs4 Servlet 将拥有四个专属的初始化参数:

标题
要生成的 HTML 文档的标题
JSPerreur
错误 JSP 页面的 URL
JSPlist
显示数字列表的 JSP 页面的 URL
Servlet URL
与 JSPlist 页面显示的表单的 action 属性关联的 URL。该 URL 即为 lstValeurs4 Servlet 的 URL

该 Servlet 的别名为 /valeurs4(Servlet 映射),因此可通过 URL http://localhost:8080/liste/valeurs4 访问。它与类文件 gener7.java 相关联,其完整源代码如下:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
import java.util.*;

public class gener7 extends HttpServlet{
        // page title
        private String title=null;
        // the list values database
        private String DSNValeurs=null;
        private String admDbValeurs=null;
        private String mdpDbValeurs=null;
         // JSP display pages
        private String JSPerreur=null;
        private String JSPliste=null;
        // the URL of the servlet
        private String URLservlet=null;
        // list values
        private String[] valeurs=null;
         // error msg
        private String msgErreur=null;

        // -----------------------------------------------------------------
        // GET
        public void doGet(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // put msgErreur,title in the query attributes
            request.setAttribute("msgErreur",msgErreur);
            request.setAttribute("title",title);
            request.setAttribute("URLservlet",URLservlet);

             // was there an error loading the servlet?
            if(msgErreur!=null){
                 // we hand over to a JSP error page
                getServletContext().getRequestDispatcher(JSPerreur).forward(request,response);
                 // end
                return;
            }

             // there was no error
             // put the list of values in the query attributes
            request.setAttribute("valeurs",valeurs);

             // we retrieve the user's possible choice
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";
            request.setAttribute("choix",choix);

             // hand over to the JSP list presentation page
            getServletContext().getRequestDispatcher(JSPliste).forward(request,response);
            // end
            return;
        }//GET

        // -----------------------------------------------------------------
        // POST
        public void doPost(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

             // returns to GET
            doGet(request,response);
        }//POST

        // -----------------------------------------------------------------
         // servlet initialization
        public void init(){

             // retrieve servlet initialization parameters
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");
            JSPerreur=config.getInitParameter("JSPerreur");
            JSPliste=config.getInitParameter("JSPliste");
            URLservlet=config.getInitParameter("URLservlet");

            ServletContext context=getServletContext();
            DSNValeurs=context.getInitParameter("DSNValeurs");
            admDbValeurs=context.getInitParameter("admDbValeurs");
            mdpDbValeurs=context.getInitParameter("mdpDbValeurs");


             //have all the parameters been recovered?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null || JSPerreur==null || JSPliste==null || URLservlet==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // fills the table of values from a ODBC database
             // name DSN : DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                 // connection to the ODBC database
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                 // statement object
                st=connexion.createStatement();
                 // execute select query to retrieve values
                rs=st.executeQuery("select valeur from Tvaleurs");
                // values are retrieved and put into a dynamic table
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // the value is saved in the
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                 // transformation list --> table
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                 // problem
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//init
    }//class

该类的新功能在于:若发生错误,则将客户端请求转发至 JSPerreur 页面;否则转发至 JSPliste 页面。该类本身不生成响应,由 JSPerreurJSPliste 这两个 JSP 页面负责处理。此前,Servlet 会通过 setAttribute 方法向客户端请求添加属性:

  • 发生错误时,为 JSPerreur 页面设置错误消息 msgError
  • 用于 JSPliste 页面的显示值(values)、用户选中的值(choice)、表单标题(title)以及表单 action 属性的 URL(URLservlet

该类经过编译后被放置在应用程序的类文件中:

Image

显示错误消息的 JSP 页面配置如下:

    <servlet>
      <servlet-name>JSPerreur</servlet-name>
    <jsp-file>/erreur.jsp</jsp-file>
    <init-param>
        <param-name>mainServlet</param-name>
      <param-value>/valeurs4</param-value>
    </init-param>
  </servlet>
.........
<servlet-mapping>
      <servlet-name>JSPerreur</servlet-name>
    <url-pattern>/JSPerreur</url-pattern>
  </servlet-mapping>

与错误页面关联的 JSP 文件名为 error.jsp,位于应用程序根目录下:

Image

该页面具有别名 /JSPerreur,因此可通过 URL http://localhost:8080/liste/JSPerreur 访问。它有一个名为 mainServlet 的初始化参数,其值为上述主 Servlet 的别名。请注意,此别名是相对于 liste 应用程序的根目录否则,它将是 /liste/valeurs4。erreur.jsp 页面的代码如下:

<%
    // code de _jspService
  // on récupère le paramètre d'initialisation mainServlet
  String servletListValeurs=config.getInitParameter("mainServlet");
  // on récupère l'attribut msgErreur
  String msgErreur=(String)request.getAttribute("msgErreur");
  // attribut valide ?
  if(msgErreur!=null){
%>
       <!-- code HTML -->
    <html>
        <head>
          <title>Erreur</title>
      </head>
      <body>
          <h3>Application indisponible (<%= msgErreur %>)</h3>
      </body>
    </html>
<%
    } else { // attribut msgErreur invalide - retour à la servlet principale
%>
    <jsp:forward page="<%= servletListValeurs %>" />  
<%    
  }
%>  

该页面通常应由前一个 Servlet 调用,并由其传递 msgError 属性。不过,如果您知道其 URL,也可以直接调用它。此外,如果您发现 msgError 属性缺失,则将请求传递给主 Servlet。在此,我们使用一个 JSP 页面专用的标签,其语法如下:

<jsp:forward page="URL" />

其中 URL 是客户端请求将被转发到的 Servlet 的 URL。如果存在 msgError 属性,则显示错误页面。

显示数字列表的 JSP 页面配置如下:

    <servlet>
      <servlet-name>JSPliste</servlet-name>
    <jsp-file>/liste.jsp</jsp-file>
    <init-param>
        <param-name>mainServlet</param-name>
      <param-value>/valeurs4</param-value>
    </init-param>
.........
  <servlet-mapping>
      <servlet-name>JSPliste</servlet-name>
    <url-pattern>/JSPliste</url-pattern>
  </servlet-mapping>

与错误页面关联的 JSP 文件名为 liste.jsp,位于应用程序根目录下:

Image

该 Servlet 的别名为 /JSPliste,因此可通过 URL http://localhost:8080/liste/JSPliste 访问。它有一个名为 mainServlet 的初始化参数,其值为主 Servlet 的别名。liste.jsp 页面的代码如下:

  <%-- page d'affichage de la liste des valeurs --%>
  <%
      // code de jspService
      // on récupère le paramètre d'initialisation
      String servletListValeurs=config.getInitParameter("mainServlet");

    // on récupère les attributs de la requête venant de la servlet principale
    String title=(String) request.getAttribute("title");
    String[] valeurs=(String[]) request.getAttribute("valeurs");
    String choix=(String) request.getAttribute("choix");
    String URLservlet=(String) request.getAttribute("URLservlet");

    // attributs valides ?
    if(title==null || valeurs==null || choix==null){
        // il y a un attribut invalide - on passe la main à la servlet
   %>
   <jsp:forward page="<%= servletListValeurs %>" />
    <%
    }//if
  %>

  <%-- code HTML --%>  
      <html>
        <head>
          <title><%= title %></title>
      </head>
      <body>
          <h3>Choisissez une valeur</h3>
          <form method="POST" action="<%= URLservlet %>">
            <select name="cmbValeurs">
              <%
                // affichage dynamique des valeurs
                          String selected="";
                          for (int i=0;i<valeurs.length;i++){
                              if(valeurs[i].equals(choix)) selected="selected"; else selected="";
                              out.println("<option "+selected+">"+valeurs[i]+"</option>");
                          }//for
            %>
          </select>
          <input type="submit" value="Envoyer">
        </form>
                <%
            // y-avait-il une valeur choisie ?
                    if(! choix.equals("")){
                // on affiche le choix de l'utilisateur
                %>        
                <hr>Vous avez choisi le nombre<h2><%= choix %></h2>
                <%        
                    }//if
                %>        
      </body>
    </html>

此页面的行为与 error.jsp 页面相同。它通常应由 /list/values4 Servlet 调用,并接收 titlevalueschoice 属性。如果这些参数中缺少任何一个,控制权将传递给 URLservlet Servlet(/liste/valeurs4)。如果所有参数均存在,则会显示数字列表以及用户选择的数字(如有)。

若请求主 Servlet 的 URL,将得到以下结果:

Image

其源代码如下(查看/源代码):

<html>
    <head>
      <title>Génération d'un formulaire</title>
  </head>
  <body>
      <h3>Choisissez une valeur</h3>
      <form method="POST" action="/liste/valeurs4">
        <select name="cmbValeurs">
          <option >0</option>
        <option >1</option>
        <option >2</option>
        <option >3</option>
        <option >4</option>
        <option >6</option>
        <option >5</option>
        <option >7</option>
        <option >8</option>
        <option >9</option>
      </select>
      <input type="submit" value="Envoyer">
    </form>

  </body>
</html>

此 HTML 文档由 JSP 页面 liste.jsp 生成。我们可以看到,titlevaluesURLservlet 属性已成功获取。

关于 Servlet 与 JSP 页面的协作,我们可以总结如下:这里的 JSP 页面非常简短,且不包含那些不直接参与生成 HTML 响应的 Java 代码。因此,生成的文档结构更加清晰。

3.4. Servlet 和 JSP 页面的生命周期

3.4.1. 生命周期

本文重点探讨 Servlet 的生命周期,JSP 页面的生命周期由此衍生而来。假设一个 Servlet 被首次调用。 此时 Web 服务器会创建一个类实例并将其加载到内存中。该实例随后将处理该请求。处理完成后,Servlet 不会立即从内存中卸载。它会保留在内存中以处理其他请求,从而优化服务器的响应时间。只有当经过相当长的一段时间且未处理任何新请求时,它才会被卸载。该时间通常可在 Web 服务器中进行配置。

在内存中,Servlet 可以同时处理多个请求。Web 服务器为每个请求创建一个线程,所有线程都使用同一个 Servlet 实例:

上述所有线程共享该 Servlet 实例的变量。为了防止 Servlet 数据被破坏,可能需要对线程进行同步。我们稍后将再次讨论这一点。

当 Servlet 被加载时,会执行 Servlet 的某个特定方法:

public void init() throws ServletException{
}

对于 JSP 页面,则是该方法


  public void jspInit(){
  }

。以下是一个使用 jspInit 方法的 JSP 页面示例:

<html>
  <head>
    <title>Compteur synchronisé</title>
  </head>
  <body>
    Compteur= <%= getCompteur() %>
  </body>
</html>

<%!
  // variables et méthodes globales de la page JSP

  // variable d'instance
  int compteur;

  // méthode pour incrémenter le compteur  
  public int getCompteur(){
    // on incrémente le compteur
    int myCompteur=compteur;
    myCompteur++;
    compteur=myCompteur;
    // on le rend
    return compteur;
  }

  // la méthode exécutée au chargement initial de la page
  public void jspInit(){
    // init compteur
    compteur=100;
  }
%>

前面的 JSP 页面在 jspInit 中将计数器初始化为 100。此后对 Servlet 的任何请求都会递增该计数器,并显示其值:

第一次:

Image

第二次:

Image

如上所示,在两次请求之间,Servlet 并未被卸载;否则,第二次请求时的计数器值应为 101。当 Servlet 被卸载时,该方法

public void destroy(){
}

(如果存在的话)会被执行。对于 JSP 页面,则是该方法


  public void jspDestroy(){
  }

在这些方法中,您可以例如关闭在相应的 init 方法中打开的数据库连接。

3.4.2. 将请求同步到 Servlet

让我们回到前面的那个 JSP 页面,该页面会递增一个计数器并将结果返回给 Web 客户端。假设同时有两个请求。此时会创建两个线程来执行它们;这些线程将使用同一个 Servlet 实例,因此也会使用同一个计数器。回顾一下递增计数器的代码:


  public int getCompteur(){
    // on incrémente le compteur
    int myCompteur=compteur;
    myCompteur++;
    compteur=myCompteur;
    // on le rend
    return compteur;
  }

计数器的递增操作是故意写得比较笨拙的。假设两个线程的执行过程如下:

 
  1. 在时间点 T1,线程 TH1 被执行。它从 myCounter 读取计数器值(=145)随后被中断并失去处理器控制权。因此,它来不及对 myCounter 进行递增操作,也来不及将新值复制到 counter 中
  2. 在时间点 T2,线程 TH2 被执行。它从 myCounter 读取计数器值(=145),随后被中断并失去处理器控制权。请注意,这两个线程拥有不同的 myCounter 变量。它们仅共享实例变量,即方法的全局变量。
  3. 在时间点 T3,线程 TH1 重新获得控制权并执行完毕。因此,它向其客户端返回 146。
  4. 在时间点 T4,线程 TH2 重新获得控制权并执行完毕。它也向其客户端返回了 146,而本应返回 147。

这是一个线程同步问题。当 TH1 想要递增计数器时,必须阻止其他任何线程也进行此操作。为了突出这个问题,我们将 JSP 页面重写如下:

<html>
  <head>
    <title>Compteur synchronisé</title>
  </head>
  <body>
    Compteur= <%= getCompteur() %>
  </body>
</html>

<%!
  // variables et méthodes globales de la page JSP

  // variable d'instance
  int compteur;

  // méthode pour incrémenter le compteur  
  public int getCompteur(){
    // on lit le compteur
    int myCompteur=compteur;
    // on s'arrête 10 secondes
    try{
      Thread.sleep(10000);
    }catch (Exception ignored){}
    // on incrémente le compteur
    compteur=myCompteur+1;
    // on le rend
    return compteur;
  }

  // la méthode exécutée au chargement initial de la page
  public void jspInit(){
    // init compteur
    compteur=100;
  }
%>

在此,我们强制线程在读取计数器后暂停 10 秒。因此,该线程应释放 CPU,从而允许另一个线程读取尚未递增的计数器。当我们在浏览器中发起请求时,除了在获得结果前需要等待 10 秒外,不会察觉到任何差异。

Image

现在,如果我们打开两个浏览器窗口,并在时间上非常接近地发出两个请求:

Image

Image

我们会得到相同的计数器值。与浏览器这种手动客户端相比,使用编程客户端更能凸显这个问题。以下是一个 Perl 客户端,调用方式如下:

*program URL N*

其中

URL 是计数 Servlet 的 URL

N 是向该 Servlet 发送的请求次数

以下是针对 5 次请求获得的结果,这些结果清楚地表明了线程同步不佳的问题:它们都返回了相同的计数器值。


DOS>java clientCompteurJSP http://localhost:8080/examples/jsp/perso/compteur/compteur2.jsp 5
Compteur=121
Compteur=121
Compteur=121
Compteur=121
Compteur=121

Java客户端代码如下。

import java.net.*;
import java.util.regex.*;
import java.io.*;

public class clientCompteurJSP {

    public static void main(String[] params){

         // data
        String syntaxe="Syntaxe : pg URL nbAppels";

         // parameter verification
        if(params.length!=2){
            System.err.println(syntaxe);
            System.exit(1);
        }//if
         // URL
        URL urlCompteur=null;
        try{
            urlCompteur=new URL(params[0]);
            String query=urlCompteur.getQuery();
            if(query!=null) throw new Exception();
        }catch (Exception ex){
            System.err.println(syntaxe);
            System.err.println("URL ["+params[0]+" incorrecte");
            System.exit(2);
        }//try-catch
         // number of calls
        int nbAppels=0;
        try{
            nbAppels=Integer.parseInt(params[1]);
            if(nbAppels<=0) throw new Exception();
        }catch(Exception ex){
            System.err.println(syntaxe);
            System.err.println("Nombre d'appels ["+params[1]+" incorrect");
            System.exit(3);
        }//try-catch

         // parameters are correct - connections can be made to the URL
        try{
            getCompteurs(urlCompteur,nbAppels);
        }catch(Exception ex){
            System.err.println(syntaxe);
            System.err.println("L'erreur suivante s'est produite : "+ex.getMessage());
            System.exit(4);
        }//try-catch
    }//hand

    private static void getCompteurs (URL urlCompteur, int nbAppels)
            throws Exception {
         // does nbAppels at URL urlCompteur
         // displays the counter value returned by the web server each time


         // remove from urlCompteur the info needed to connect to the tax server
        String path=urlCompteur.getPath();
        if(path.equals("")) path="/";
        String host=urlCompteur.getHost();
        int port=urlCompteur.getPort();
        if(port==-1) port=urlCompteur.getDefaultPort();

         // calls are made to the URL
        Socket[] clients=new Socket[nbAppels];
        for(int i=0;i<nbAppels;i++){
             // connect to the server
            clients[i]=new Socket(host,port);
             // create a write stream to the server
            PrintWriter OUT=new PrintWriter(clients[i].getOutputStream(),true);
             // request URL - send HTTP headers
            OUT.println("GET " + path + " HTTP/1.1");
            OUT.println("Host: " + host + ":" + port);
            OUT.println("Connection: close");
            OUT.println("");
        }//for

         // local data
        String réponse=null;                        // server response
         // the model searched for in the HTML server response
        Pattern modèleCompteur=Pattern.compile("^\\s*Compteur= (\\d+)");
         // the model for a correct answer
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
         // the result of the model comparison
        Matcher résultat=null;

        for(int i=0;i<nbAppels;i++){
             // each client reads the response sent by the server

             // create customer input/output flows TCP
            BufferedReader IN=new BufferedReader(new InputStreamReader(clients[i].getInputStream()));

             // read the 1st line of the answer
            réponse=IN.readLine();
             // compare the HTTP line with the model of the correct answer
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                 // we have a URL problem
                throw new Exception("Client n° " + i + " - Le serveur a répondu : URL ["+ urlCompteur + "] inconnue");
            }//if

             // the response is read through to the end of the headers
            while((réponse=IN.readLine())!=null && ! réponse.equals("")){
            }//while

             // that's it for HTTP headers - move on to HTML code
             // to retrieve the counter value
            boolean compteurTrouvé=false;
            while((réponse=IN.readLine())!=null){
                 // compare the line with the counter model
                if(! compteurTrouvé){
                    résultat=modèleCompteur.matcher(réponse);
                    if(résultat.find()){
                         // meter found
                        System.out.println("Compteur="+résultat.group(1));
                        compteurTrouvé=true;
                    }//if
                }//if
            }//while

             // it's over
            clients[i].close();
        }//for
    }//getCompteurs

}//class

让我们来解释一下上面的代码:

  • 该程序接受两个参数:
    • 计数器对应的 JSP 页面的 URL
    • 为该 URL 创建的客户端数量
  • 因此,程序首先会验证参数的有效性:确认参数确实有两个,第一个在语法上符合 URL 格式,第二个是一个大于 0 的整数。 为了验证 URL 的语法正确性,我们使用 URL 类及其 URL(String) 构造函数,该构造函数会根据字符串(例如 http://istia.univ-angers.fr)创建一个 URL 对象。如果字符串不是语法上有效的 URL,则会抛出异常。这使我们能够验证第一个参数的有效性。
  • 参数验证完成后,控制权将传递给 getCompteurs 过程。该过程将创建 nbAppels 个客户端,所有客户端都将同时(或几乎同时)连接到 URL urlCompteur
  • 客户端需连接的端口和主机名来自 URL urlCompteur[URL].getHost() 返回主机名,[URL].getPort() 返回端口号。
  • 第一个循环允许每个客户端:
    • 连接到 Web 服务器
    • 请求 URL urlCompteur

在此循环中,客户端不会等待服务器的响应。这是因为我们希望服务器接收到近乎同时的请求。

  • 第二个循环允许每个客户端接收并处理服务器发送的响应。处理过程包括在响应中查找包含计数器值的行并将其显示出来。

为了解决之前提到的问题(即向所有五个客户端发送相同的计数器值),我们需要在进入用于读取和更新计数器的临界区之前,将计数服务的线程同步到一个单一对象上。新的 JSP 页面如下:


<html>
  <head>
    <title>Compteur synchronisé</title>
  </head>
  <body>
    Compteur= <%= getCompteur() %>
  </body>
</html>
 
<%!
  // variables et méthodes globales de la page JSP
 
  // variables d'instance
  int compteur;
  Object verrou=new Object();
 
  // méthode pour incrémenter le compteur  
  public int getCompteur(){
 
    // on syncronise la section critique
    synchronized(verrou){
      // on lit le compteur
      int myCompteur=compteur;
      // on s'arrête 10 secondes
      try{
        Thread.sleep(10000);
      }catch (Exception ignored){}
      // on incrémente le compteur
      compteur=myCompteur+1;
    }//synchronized
    // on le rend
    return compteur;
  }//getCompteur
 
  // la méthode exécutée au chargement initial de la page
  public void jspInit(){
    // init compteur
    compteur=100;
  }
%>

执行后,将得到以下结果:

dos>c:\perl\bin\perl.exe client2.pl http://localhost:8080/examples/jsp/perso/compteur/compteur3.jsp 5
    Compteur= 104
    Compteur= 106
    Compteur= 105
    Compteur= 107
    Compteur= 108

文档指出,Web 服务器有时可能会创建同一 Servlet 的多个实例。在这种情况下,之前的同步机制将不再有效,因为变量仅在单个实例中是局部变量,因此其他实例无法访问它。计数器变量也是如此。为了使它们对所有实例都具有全局性,我们编写如下代码:


// variable de classe
  static int compteur;
  static Object verrou=new Object();

其余代码保持不变。