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 时,将显示以下内容:

请注意以下几点:
- 您必须导入 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 头部可通过以下方式在此处设置:
- 为了将响应发送给客户端,Servlet 会使用响应对象提供的输出流:
- 获取该输出流后,将 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 客户端发送的参数。一个输入表单:

Servlet 发送的响应:

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 方法发送参数
- 接收到的参数将由 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 头:

该 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 头部:
- `request.getHeader("header")` 方法允许您检索特定的 HTTP 头部。上面的示例提供了一些示例。请记住,这里显示的头部是由浏览器发送的。服务器也有自己的 HTTP 头部,有时这些头部与浏览器的头部一致。浏览器发送的 HTTP 头部旨在向服务器告知浏览器的功能。
标头 | 含义 |
浏览器标识 | |
浏览器支持的MIME类型。例如,image/gif表示该浏览器可以处理GIF格式的图像 | |
格式为 host:port。指明浏览器希望连接的机器和端口。 | |
浏览器接受的、用于处理服务器发送文档的编码格式。因此,如果服务器上既有普通未压缩格式的文档,也有 gzip 格式的文档,且浏览器已表明其支持 gzip 格式,则服务器可以发送 gzip 格式的文档以节省带宽。 | |
浏览器支持的语言。如果服务器拥有同一文档的多种语言版本,它将发送浏览器支持语言的版本。 | |
浏览器请求的 URL | |
浏览器请求的连接模式。Keep-alive 表示服务器在向浏览器提供所请求的页面后不应关闭连接。例如,如果浏览器发现收到的页面包含指向图片的链接,它可以向服务器发出新的请求来获取这些图片,而无需建立新的连接。一旦浏览器收到页面中的所有元素,它就会主动关闭连接。 |
3.1.4. 获取环境信息
以下 Servlet 演示了如何访问 Servlet 的运行时环境信息。其中部分信息由浏览器作为 HTTP 头发送,因此可以使用前面的方法进行获取。

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());
以下是一些可用方法及其含义的列表:
方法 | 含义 |
Web 服务器的名称 | |
Web 服务器的运行端口 | |
浏览器用于发送请求的 GET 或 POST 方法 | |
发起请求的浏览器所属客户端计算机的名称 | |
该机器的 IP 地址 | |
浏览器发送的内容类型(HTTP Content-Type 标头) | |
浏览器发送的字符数(HTTP Content-Length 标头) | |
浏览器请求的 HTTP 协议版本 | |
浏览器请求的 URI。对应于 http://hote:port/URI 中 host:port 标识符之后的 URL 部分 |
3.1.5. 使用 JBuilder 创建一个 Servlet,并将其部署到 Tomcat
接下来我们将介绍如何创建和运行一个 Java Servlet。我们将使用两个工具:JBuilder 来编译 Servlet,Tomcat 来运行它。仅使用 Tomcat 或许就足够了。然而,它提供的调试功能有限。我们将重新审视之前开发的那个显示服务器接收到的参数的示例。该 Servlet 首先发送以下输入表单:

Servlet 发送的响应:

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”选项

在上方的“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 的安装目录:

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

从现在起,您可以编译符合 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:

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

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

请注意,返回响应的 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 名称:
最后,请注意以下几点:
- 由于 <form> 标签没有 <action> 属性,浏览器会将表单值发送给生成该表单的 Servlet。在这种情况下,浏览器会将表单中输入的数据发送至提供该表单的 URL。
- <form> 标签指定表单数据必须使用 POST 方法发送。这就是为什么这些值会被 Servlet 的 doPost 方法检索到的原因。
3.1.6.2. 动态表单生成 - 2
我们将重新审视前面的示例,并按以下方式进行修改。表单保持不变:

但响应内容有所不同:

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

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

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

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

3.1.6.4. 从表单中获取值
我们将重新审视一个我们已经见过的示例,即以下网页表单:

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> 标签已按以下方式定义:
浏览器将表单值“POST”到 URL http://localhost:8080/examples/servlet/parameters,该 URL 指向一个由 Tomcat 管理的 Servlet,该 Servlet 会显示前一个表单的值。如果我们直接调用该参数 Servlet,将得到以下结果:

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

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

该响应显然包含了表单中输入的值。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);
}
}
这段代码运用了之前在另一个示例中介绍的技术。请注意以下两点:
- lst2 控件是一个多选列表,因此可以同时选择多个项目。在我们的示例中就是这种情况,其中已选中了 list1 和 list3 这两个项目。 lst2 的值由浏览器以 lst2=liste1&lst2=liste3 的形式发送至服务器。Java Servlet 可通过 getParameterValues 方法将这些值作为数组检索:在此处,request.getParameterValues("lst2") 返回一个包含两个字符串 ["liste1", "liste3"] 的数组。
- areaSaisie 控件是一个多行输入框。request.getParameter("areaSaisie") 会将该字段的内容作为单个字符串返回。若要提取其中的单行内容,可以使用 String 类的 split 方法。以下代码
可从输入字段中提取各行内容。这些行以 \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 头。我们将对表单进行一项修改:
表单值将通过 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,并在未修改表单内容的情况下点击“提交”。我们收到以下响应:

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

请注意 HTTP 头部 content-type 和 content-length,这是 POST 请求的特征。此外,请注意表单值不再显示在浏览器的地址栏中。
3.2. JSP 页面
JSP(Java Server Pages)是编写 Web 服务器应用程序的另一种方式。实际上,这些 JSP 页面在执行前会被编译为 Servlet,因此我们本质上是在使用 Servlet 技术。JSP 页面能够更清晰地突出显示生成的 HTML 页面的结构。以下是一些示例,其中部分示例可通过访问 Tomcat 主页上的 JSP 链接进行查看:
![]() | ![]() |
3.2.1. 获取环境信息
在此,我们将重新审视一个之前用 Servlet 演示过的示例:显示 Servlet 的环境变量。这是来自 JSP 示例中的“snoop”示例:

该 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 中研究过的示例。向浏览器展示一个表单:

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

该 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 代码,该代码将成为 JSP 页面 main 方法的一部分 | |
用于设置 JSP 页面的属性。例如: import="java.util.*,java.sql.*" 用于指定 JSP 页面所需的库 extends="aParentClass" 用于让 JSP 页面继承自另一个类 |
3.2.4. JSP 隐式对象
在之前的示例中,我们遇到了两个未声明的对象:request 和 out。它们是 JSP 页面转换为 Servlet 时自动定义的对象。这些被称为隐式对象或预定义对象。还有其他对象,但这些是与 response 对象一起最常用的:
对象 | 含义 |
可通过该对象访问 Web 客户端的请求(getParameter、getParameterNames、getParameterValues) | |
用于构建 Web 服务器对客户端响应的对象。允许您设置要发送给 Web 客户端的 HTTP 头部。 | |
用于将 HTML 代码发送给客户端的输出流(print、println) |
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 中:

该名称反映了 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 代码发送给客户端。
- 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 页面生成以下网页:

让我们来看看上面这四行是如何生成的:
<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 方法的一部分。它们是如何访问 lastName、firstName 变量以及 hisDog 和 displayFriend 方法的?
是 JSP 页面 main 方法的局部变量,因此该方法内部可以访问它 | |
是 JSP 页面的全局变量,因此在 main 方法中可见 | |
是 JSP 页面的公共方法,因此可在 main 方法中访问 | |
是 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 文件中哪几行存在错误。错误可能由多种原因引起:
- 页面上的 JSP 代码有误(例如,使用的 JSP 标签有错误)
- JSP 页面中包含的 Java 代码有误
第一种原因可通过检查页面的 JSP 代码来排除。第二种原因可通过检查 Java 代码来排除。这可以通过使用 JBuilder 等工具直接编译为 JSP 页面生成的 .java 文件来实现,该工具提供的调试功能比 Tomcat 更强大。
3.2.8. 示例
我们将重新回顾之前介绍过的那个涉及Servlet的示例:用户从列表中选择一个数字,服务器会告知用户所选数字,同时返回该列表,并将用户选择的元素高亮显示:

为了构建此页面,我们调用了 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 的实例变量(即必须在多个方法间共享的变量)已原样保留。这些变量主要包括 title、values 和 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 文本文件进行配置:

该文本文件实际上是一个 XML(扩展标记语言)文件。XML 文档是一种包含标签的文本文档,与 HTML 文档类似。然而,HTML 标签有明确的定义,而 XML 标签则没有。因此,以下文档即为一个 XML 文档:
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 浏览它,则会看到如下显示:

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

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 查看该文件,会看到一个复杂的文档。我们仅关注以下几行:

这里我们关注的是 <Context ...> 标签。它用于定义 Web 应用程序。其中有两个属性值得特别注意:
- path:这是 Web 应用程序的名称
- docBase:这是应用程序所在的文件夹。这里是一个相对路径:examples。相对于哪个文件夹?答案同样可以在 server.xml 文件的以下行中找到:

上述行定义了 Web 服务器:
- name:Web 服务器的名称
- appBase:其所服务的文档树的根目录。这里同样是一个相对路径:webapps。它是相对于 Tomcat 服务器安装目录 <tomcat> 的。因此,这指的是 <tomcat>\webapps 文件夹。
示例 Web 应用程序将其文件存储在 examples 文件夹中(参见上文的 docBase)。该名称是相对于服务器 Web 目录树的根目录而言的,即 <tomcat>\webapps。因此,具体路径为 <tomcat>\webapps\examples 文件夹。让我们来仔细看看这个文件夹:

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

该文件用于配置示例 Web 应用程序。我们暂不深入探讨该文件的细节,因为目前它过于复杂。我们仅关注以下几行代码:
<servlet>
<servlet-name>
servletToJsp
</servlet-name>
<servlet-class>
servletToJsp
</servlet-class>
</servlet>
<servlet> 标签用于在 Web 应用程序中定义一个 Servlet。请注意,此处的 Web 应用程序是 examples。servlet 标签包含另外两个标签:
- <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)查找该类并执行它。

3.3.2. 示例:部署“list”Web应用程序
我们将重新审视一个已经学过的 Servlet,该 Servlet 向用户展示了一组数字列表,用户从中选择一个数字。随后,Servlet 会确认用户所选的数字:

如上图浏览器地址栏所示,该 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 应用程序下:

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

我们通过在 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 文件夹中:

上图所示的截图中,src 文件夹内放置了源文件 gener3.java。该文件夹可能并不存在,在本演示中它没有任何实际用途。现在我们可以开始运行测试了:
- 停止并重启 Tomcat,以便它重新读取 server.xml 配置文件。这里我们使用的是 Windows 系统。在 Unix 系统上,您可以强制 Tomcat 重新读取配置文件而无需停止服务。
- 使用浏览器访问 URL http://localhost:8080/liste/servlet/lstValeurs

我们可以看到,之前的 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:

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 为例:

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

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

我们确实获得了预期的 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 文档的标题 | |
Servlet从中检索数据的ODBC数据库的DSN名称 | |
具有上述数据库读取权限的用户名 | |
该用户的密码 |
如果 DSNValeurs 数据库的管理员更改了用户 admDbValeurs 的密码,则必须修改 Servlet 源代码并重新编译。这并不太实用。Servlet 的 web.xml 配置文件提供了一种替代方案,允许使用 <init-param> 标签定义 Servlet 初始化参数:
允许您定义参数名称 | |
定义与前一个参数关联的值 |
Servlet 可以通过以下方法访问其初始化参数:
Servlet 类的成员方法,用于 Web 编程的 HttpServlet 类即由此类派生而来。该方法返回一个 ServletConfig 对象,用于访问 Servlet 的配置参数。 | |
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 目录下:

lstValeurs2 Servlet 有四个初始化参数:title、DSNValeurs、admDbValeurs 和 mdpDbValeurs。此外,已通过 <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:

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

3.3.5. Web 应用程序的初始化参数
在上一个示例中,只有 lstValeurs2 Servlet 可以访问 title、DSNValeurs、admDbValeurs 和 mdpDbValeurs 参数。同一 liste 应用程序中的另一个 Servlet 可能需要使用 lstValeurs2 Servlet 所使用的同一数据库中的数据。 在这种情况下,需要在新的 servlet 的 web.xml 文件的配置部分中重新定义 DSNValeurs、admDbValeurs 和 mdpDbValeurs 参数。另一种解决方案是在应用程序级别(而非 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 类的成员方法,用于 Web 编程的 HttpServlet 类即由此派生而来。该方法返回一个 ServletContext 对象,用于访问应用程序的配置参数 | |
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 目录下:

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

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 文件中发现了四个常量:title、DSNValues、admDbValues 和 mdpDbValues。常量 DSNValues、admDbValues 和 mdpDbValues 现在已在应用程序级别定义,因此我们可以假设属于该应用程序的 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):

在应用程序的 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 应用程序的根目录下:

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

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 类,返回一个 RequestDispatcher 对象。url 参数是我们要将客户端请求转发到的 URL 的名称。这种请求转发只能发生在同一个应用程序内。因此,url 参数是相对于该应用程序 Web 目录结构的路径。 | |
RequestDispatcher 接口的一个方法,用于将客户端的请求和响应对象(必须用于构建响应)转发到上一个 URL。 | |
当一个 Servlet 或 JSP 页面将请求传递给另一个 Servlet 或 JSP 页面时,通常不仅需要传递客户端的请求,还需要传递其他信息——即其自身处理该请求时生成的信息。 ServletRequest 类的 setAttribute 方法允许您以类似于 (属性, 值) 对字典的格式向客户端的请求对象添加属性,其中属性是属性的名称,值是表示其值的任何对象。 | |
允许您检索请求属性的值。该方法将由接收该请求的 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 文档的标题 | |
错误 JSP 页面的 URL | |
显示数字列表的 JSP 页面的 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 页面。该类本身不生成响应,由 JSPerreur 和 JSPliste 这两个 JSP 页面负责处理。此前,Servlet 会通过 setAttribute 方法向客户端请求添加属性:
- 发生错误时,为 JSPerreur 页面设置错误消息 msgError
- 用于 JSPliste 页面的显示值(values)、用户选中的值(choice)、表单标题(title)以及表单 action 属性的 URL(URLservlet)
该类经过编译后被放置在应用程序的类文件中:

显示错误消息的 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,位于应用程序根目录下:

该页面具有别名 /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 页面专用的标签,其语法如下:
其中 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,位于应用程序根目录下:

该 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 调用,并接收 title、values 和 choice 属性。如果这些参数中缺少任何一个,控制权将传递给 URLservlet Servlet(/liste/valeurs4)。如果所有参数均存在,则会显示数字列表以及用户选择的数字(如有)。
若请求主 Servlet 的 URL,将得到以下结果:

其源代码如下(查看/源代码):
<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 生成。我们可以看到,title、values 和 URLservlet 属性已成功获取。
关于 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 的某个特定方法:
对于 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 的任何请求都会递增该计数器,并显示其值:
第一次:

第二次:

如上所示,在两次请求之间,Servlet 并未被卸载;否则,第二次请求时的计数器值应为 101。当 Servlet 被卸载时,该方法
(如果存在的话)会被执行。对于 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;
}
计数器的递增操作是故意写得比较笨拙的。假设两个线程的执行过程如下:
![]() |
- 在时间点 T1,线程 TH1 被执行。它从 myCounter 读取计数器值(=145),随后被中断并失去处理器控制权。因此,它来不及对 myCounter 进行递增操作,也来不及将新值复制到 counter 中。
- 在时间点 T2,线程 TH2 被执行。它从 myCounter 读取计数器值(=145),随后被中断并失去处理器控制权。请注意,这两个线程拥有不同的 myCounter 变量。它们仅共享实例变量,即方法的全局变量。
- 在时间点 T3,线程 TH1 重新获得控制权并执行完毕。因此,它向其客户端返回 146。
- 在时间点 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 秒外,不会察觉到任何差异。

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


我们会得到相同的计数器值。与浏览器这种手动客户端相比,使用编程客户端更能凸显这个问题。以下是一个 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();
其余代码保持不变。





