3. Java Servlet与JSP页面的简介
本章将提供各种Servlet和JSP页面的示例。这些示例已在Tomcat服务器上经过测试,该服务器运行在8080端口。通过首页的链接,可以访问Servlet和JSP页面的示例。 下文中的示例大多摘自 Tomcat 的示例库。要进行测试,只需启动 Tomcat,使用浏览器访问 URL http://localhost:8080,并点击 Servlet 链接即可。
![]() | ![]() |
3.1. Java Servlet
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 request 是让我们能够访问 Web 客户端所发请求的对象。Servlet 的响应将通过对象 HttpServletResponse response 进行
- response 对象允许我们设置将发送给客户端的 HTTP 头部。例如,Content-type: text/html 头部在此处通过以下方式设置:
- 为了将响应发送给客户端,Servlet 使用由 response 对象提供的输出流:
- 获取该输出流后,代码 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");
如果参数 nomParamètre 不属于 Web 客户端发送的参数,则方法 request.getParameter("nomParamètre") 会返回指针 null。
- 表单指定浏览器必须通过方法 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("entête") 可用于获取特定的 HTTP 头。上文示例列举了其中部分头。 需要注意的是,此处展示的头部信息均由浏览器发送。服务器也有其自身的 HTTP 头部,这些头部有时会包含浏览器的头部信息。浏览器发送的 HTTP 头部旨在向服务器提供有关浏览器功能的信息。
标头 | 含义 |
浏览器标识 | |
浏览器支持的格式。因此,MIME 表示该浏览器能够处理 GIF 格式的图像 | |
hote: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中位于标识符hote: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 版本进行说明。此处摘录部分内容:
- 启用“Tools/Configure JDKs”选项或(“Options/Configure JDK”)

在上述 JDK 设置部分中,Name 字段通常应填写 JDK 1.3.1。 如果您拥有更新版本的 JDK,请使用 Change 按钮指定该版本的安装目录。 上文中,我们指定了安装有 JDK 1.4 的目录 E:\Program Files\jdk14。现在,JBuilder 将使用该 JDK 进行编译和运行。 在(类、源代码、文档)部分,列出了所有将被 JBuilder 探索的类库,此处为 JDK 1.4 的类。仅凭这些类不足以进行 Java Web 开发。 若要添加其他类库,请点击 Add 按钮,并指定需要使用的额外 .jar 文件。 .jar 文件即为类库。Tomcat 4.x 自带了 Web 开发所需的所有类库。这些类库位于 <tomcat>\common\lib 目录下,其中 <tomcat> 是 Tomcat 的安装目录:

通过 Add 按钮,我们将把这些库逐一添加到 JBuilder 所扫描的库列表中:

从现在起,我们可以编译符合 J2EE 标准的 Java 程序,特别是 Java Servlet。JBuilder 仅用于编译,后续的运行将由 Tomcat 负责。
- 现在您可以编译程序 myRequestParamExample.java 并生成 Servlet myRequestParamExample.class。 该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 编译 XX.java 源代码的 Servlet
- 将 Servlet XX.class 部署到 <tomcat>\webapps\examples\WEB-INF\classes
- 启动 Tomcat,使用浏览器访问 URL http://localhost:8080/examples/servlet/XX
3.1.6.1. 表单的动态生成 - 1
我们以生成一个仅含一个控件(列表)的表单为例。该列表的内容通过从数组中获取的值动态构建。实际上,这些值通常来自数据库。表单如下:

如果在上述示例中执行 Envoyer,将得到以下响应:

值得注意的是,生成响应的 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{
// 实例变量
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{
// 向客户端指示所发送文档的类型
response.setContentType("text/html");
// 发送表单
PrintWriter out=response.getWriter();
// 开始
out.println(HTML1);
// 下拉菜单
out.println("<select name=\"cmbValeurs\" size=\"1\">");
for (int i=0;i<valeurs.length;i++){
out.println("<option>"+valeurs[i]+"</option>");
}//用于
out.println("</select>");
// 表单结束
out.println(HTML2+HTML3);
}//GET
// POST
public void doPost(HttpServletRequest request,HttpServletResponse response)
throws IOException, ServletException{
// 获取用户选择
String choix=request.getParameter("cmbValeurs");
if(choix==null) doGet(request,response);
// 准备响应
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>";
// 告知客户发送的文档类型
response.setContentType("text/html");
// 发送表单
PrintWriter out=response.getWriter();
out.println(réponse);
}//POST
}//分类
方法 doGet 用于生成表单。其中有一个动态部分,即列表内容,该内容来源于一个数组。方法 doPost 用于生成响应。在此,唯一的动态部分是用户在表单列表中所作选择的值。 该值通过 request.getParameter("cmbValeurs") 获取,其中 cmbValeurs 是列表的名称 HTML:
最后需注意以下几点:
- 由于 <form> 标签没有 <action> 属性,因此浏览器将表单的值发送给生成该表单的 Servlet。 在这种情况下,浏览器将表单中输入的数据发送给提供该表单的 URL。
- 标签 <form> 指定表单数据必须通过方法 POST 发送。因此,这些值由 Servlet 的方法 doPost 获取。
3.1.6.2. 表单的动态生成 - 2
我们沿用前面的示例,并按以下方式进行修改。提供的表单仍然是相同的:

响应是否不同:

在响应中,我们返回了表单,并在下方标注了用户选择的数字。此外,该数字也是列表显示时被选中的数字。用户随后可以选择另一个数字:

然后执行 Envoyer。他将获得以下响应:

名为 gener2.java 的 Servlet 代码如下:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class gener2 extends HttpServlet{
// 实例变量
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{
// 获取用户的选项(如有)
String choix=request.getParameter("cmbValeurs");
if(choix==null) choix="";
// 向客户说明所发送文档的类型
response.setContentType("text/html");
// 发送表单
PrintWriter out=response.getWriter();
// 开始
out.println(HTML1);
// 下拉菜单
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>");
}//用于
out.println("</select>");
// 表单后续
out.println(HTML2);
if(! choix.equals("")){
// 显示用户的选项
out.println("<hr>Vous avez choisi le nombre <h2>"+choix+"</h2>");
}//if
// 表单结束
out.println(HTML3);
}//GET
// POST
public void doPost(HttpServletRequest request,HttpServletResponse response)
throws IOException, ServletException{
// 跳转至 GET
doGet(request,response);
}//POST
}//类别
方法 doGet 负责所有操作:它生成表单并将其发送给客户端,同时处理客户端返回的值。需要注意的要点如下:
- 检查参数 cmbValeurs 是否具有值。
- 如果存在,在生成列表内容时,将列表中的每个元素与用户的选项进行比较,并将 selected 属性赋予用户选择的元素:<option selected>元素</option>。此外,在表单下方显示所选选项的值。
3.1.6.3. 表单动态生成 - 3
我们继续探讨前面的问题,但这次的值来自数据库。在本例中,该数据库名为 MySQL:
- 数据库名为 dbValeurs
- 其所有者为 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 已通过驱动程序 ODBC 供 MySQL 访问。 其名称 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{
// 页面标题
private final String title="Génération d'un formulaire";
// 列表值数据库
private final String DSNValeurs="odbc-valeurs";
private final String admDbValeurs="admDbValeurs";
private final String mdpDbValeurs="mdpDbValeurs";
// 列表值
private String[] valeurs=null;
// 错误消息
private String msgErreur=null;
// 代码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{
// 向客户指示所发送文档的类型
response.setContentType("text/html");
// 输出流
PrintWriter out=response.getWriter();
// Servlet 初始化是否成功?
if (msgErreur!=null){
// 发生错误 - 生成错误页面
out.println("<html><head><title>"+title+"</title></head>");
out.println("<body><h3>Application indisponible ("+msgErreur+
")</h3></body></html>");
return;
}//if
// 获取用户的选项(如有)
String choix=request.getParameter("cmbValeurs");
if(choix==null) choix="";
// 提交表单
// 开始
out.println(HTML1);
// 下拉菜单
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>");
}//用于
out.println("</select>");
// 表单后续
out.println(HTML2);
if(! choix.equals("")){
// 显示用户的选项
out.println("<hr>Vous avez choisi le nombre <h2>"+choix+"</h2>");
}//if
// 表单结束
out.println(HTML3);
}//GET
// POST
public void doPost(HttpServletRequest request,HttpServletResponse response)
throws IOException, ServletException{
// 跳转至 GET
doGet(request,response);
}//POST
// 初始化 Servlet
public void init(){
// 从数据库中填充值数组ODBC
// 名称为 DSN:DSNvaleurs
Connection connexion=null;
Statement st=null;
ResultSet rs=null;
try{
// 连接数据库 ODBC
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
// Statement 对象
st=connexion.createStatement();
// 执行 select 查询以检索值
rs=st.executeQuery("select valeur from Tvaleurs");
// 值已检索并放入动态数组
ArrayList lstValeurs=new ArrayList();
while(rs.next()){
// 将值记录到列表中
lstValeurs.add(rs.getString("valeur"));
}//while
// 将列表转换为数组
valeurs=new String[lstValeurs.size()];
for (int i=0;i<lstValeurs.size();i++){
valeurs[i]=(String)lstValeurs.get(i);
}
}catch(Exception ex){
// 问题
msgErreur=ex.getMessage();
}finally{
try{rs.close();}catch(Exception ex){}
try{st.close();}catch(Exception ex){}
try{connexion.close();}catch(Exception ex){}
}//try
}//初始化
}//类
需要注意的要点如下:
- 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");
}//删除
</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> 标签已定义如下:
浏览器将表单值“提交”至 URL http://localhost:8080/examples/servlet/parameters,该地址指向由 Tomcat 管理的 Servlet URL,该 Servlet 会显示前一个表单的值。 如果直接调用 parameters Servlet,将得到以下结果:

如果填写的表单 balises2.htm 是以下内容:

并点击“发送”按钮(类型为 submit),此时 parameters Servlet 将被带参数调用。随后它返回以下响应:

从该响应中可以清楚地看到表单中输入的值。Servlet 的代码如下:
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class parameters extends HttpServlet{
// 实例变量
String title="Récupération des paramètres d'un formulaire";
private String getParameter(HttpServletRequest request, String contrôle){
// 返回值 request.getParameter(检查)或 ""(若不存在)
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
{
// 首先获取表单参数
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");
// 指定文档内容
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>");
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
{
// 跳转至 GET
doGet(request,response);
}
}
此代码中运用了之前在另一个示例中介绍过的技术。需注意两点:
- 控件 lst2 是一个多选列表,因此可以选中多个元素。本例中就是这种情况,已选中了元素 liste1 和 liste3。 lst2 的值由浏览器以 lst2=liste1&lst2=liste3 的形式传输至服务器。 Java Servlet 可以通过 getParameterValues 方法从数组中获取这些值:此处 request.getParameterValues("lst2") 返回了一个包含 2 个字符串的数组 ["liste1","liste3"]。
- 控件 areaSaisie 是一个多行输入字段。request.getParameter("areaSaisie") 会将该字段的内容以单个字符串的形式返回。 如果想从中提取构成该字符串的各行,可以使用类 String 中的函数 split。以下代码
可提取输入字段中的各行。这些行以 \r\n 字符结尾(0D0A)。
为了进行测试,我们:
- 按照前文所述,构建并编译了 parameters Servlet(其中 JBuilder 作为其依赖)
- 将生成的类放置在 <tomcat>\webapps\examples\WEB-INF\classes 目录下,其中 <tomcat> 是 Tomcat 的安装目录。
- 访问了URL http://localhost:81/html/balises2.htm(其代码已在上文展示)
- 填写表单并点击了 Envoyer 按钮。
3.1.6.5. 获取来自 Web 客户端的 HTTP 头部
我们沿用之前的示例,但在响应发送表单值的Web客户端时,向其发送其同时发送的HTTP头部信息。我们在表单中仅进行一项修改:
表单数据将通过 GET 方法发送至位于 <tomcat>\webapps\examples\WEB-INF\classes 目录下的名为 headers 的 Java Servlet。 Servlet headers 已与 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
{
// 确定文档类型
response.setContentType("text/html");
// 获取写入流
PrintWriter out = response.getWriter();
// 显示标题列表 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
}
我们请求 URL http://localhost:81/html/balises2.htm,并生成 Envoyer,且未修改表单。得到以下响应:

值得注意的是,浏览器中Address字段中显示了已配置的URL,这展示了参数传输的方式(GET)。 我们沿用同一示例,但修改参数的发送方式(POST):
我们得到了以下新的响应:

请注意,HTTP、content-type 和 content-length 这几个表头是 POST 发送的特征。 此外,请注意在浏览器的 Address 字段中,表单的值不再显示。
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 极为相似。但其中包含 <%= expression %> 标签,这是 JSP 语言特有的。 JSP编译器会在HTML文本中将整个标签替换为expression的值。
- 此示例使用了 Java 对象 request 的方法,该对象即在 Servlet 学习中已提及的 request。因此,它是一个 HttpServletRequest 对象。 因此,在代码 HTML 中,标签 <%= request.getRemoteHost() %> 将被请求的 Web 客户端的机器名称所替换。
- 虽然使用 Servlet 也能达到相同的效果,但在此处网页的结构更为直观。
3.2.2. 获取 Web 客户端发送的参数
这里我们重新探讨之前用 Servlet 分析过的示例。浏览器中显示了一个表单:

作为对上述请求的响应,浏览器将收到以下页面:

页面 JSP 的代码如下:
<%
// 主过程的局部变量
String title="Récupération des paramètres d'un formulaire";
String firstName = request.getParameter("firstname");
String lastName = request.getParameter("lastname");
%>
<!-- 代码 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 的主方法。 因此,在页面 JSP 开头声明的 Java 变量,在散布于代码 HTML 中的其他代码片段 JSP 中也是已知的:这些变量和代码片段将属于同一个 Java 方法。 但如果我们的代码 JSP 包含方法,那么由于方法之间的隔离性,变量 title、firstname 和 lastname 在其中将不可见。 此时需要将它们设为全局变量,或者作为参数传递给方法。我们稍后会再讨论这一点。
- 要在代码 HTML 中包含动态内容,有两种方法:<%= 表达式 %> 或 out.println(表达式)。 对象 out 是一个输出流,类似于 Servlet 示例中出现的同名对象,但类型不同:它是 JspWriter 对象,而非 PrintWriter。 它允许使用方法 print 和 println 向流 HTML 写入数据。
- JSP 页面比等效的 Servlet 更能体现生成的 HTML 页面的结构。
3.2.3. JSP 标签
以下是 JSP 页面中可能出现的标签及其含义。
标签 | 含义 |
注释 HTML。发送给客户端。 | |
注释 JSP。未发送给客户。 | |
声明全局变量和方法。这些变量将在所有方法中可用 | |
表达式的值将嵌入到页面中,代替标签 | |
包含Java代码,该代码将成为页面主方法的一部分JSP | |
为页面 JSP 设置属性。例如: import="java.util.*,java.sql.*"用于指定页面 JSP 所需的库 extends="父类" 用于使 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:
<%
// 主过程的局部变量
String title="Récupération des paramètres d'un formulaire";
String firstName = request.getParameter("firstname");
String lastName = request.getParameter("lastname");
%>
<!-- 代码 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 的 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();
// 主过程的局部变量
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启动时会调用此方法。可以看到它接收两个参数:来自客户端的请求request,以及用于生成对Web客户端响应的对象response。
- 在主方法中,声明并初始化了一个名为 out 的 JspWriter 对象。正是通过该对象,利用 out.print("codeHTML") 语句将代码 HTML 发送给客户端。
- Java 代码
<%
// 主过程的局部变量
String title="Récupération des paramètres d'un formulaire";
String firstName = request.getParameter("firstname");
String lastName = request.getParameter("lastname");
%>
已被完整地复制到Servlet的主方法_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:
<%!
// 前一个标签开始全局变量和方法部分
// 该部分将原样复制到Servlet中
// 一个全局变量
String prenom="inconnu";
// 一个方法
private String sonChien(){
return "milou";
}//sonChien
// 另一个方法
private void afficheAmi(JspWriter out) throws Exception{
out.println("<p>Son ami s'appelle Haddock</p>");
}//afficheAmi
// Servlet全局部分结束
%>
<%
// 前面的标签表示后续代码将被保存
// 在 Servlet 的主方法中
// 主方法的局部变量
String nom="tintin";
%>
<%-- 代码 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>
<%
// 他朋友的名字
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>
<%
// 他朋友的名字
afficheAmi(out);
%>
上述代码行位于 <%..%> 标签内,因此将成为生成的 Servlet 的主方法 _jspService 的一部分。 它们如何访问变量 nom、prenom 以及方法 sonChien 和 afficheAmi?
是页面 JSP 主方法的局部变量,因此该方法中可以访问它 | |
是页面 JSP 的全局变量,因此在主方法中也是已知的 | |
是页面 JSP 的公共方法,因此可在主方法中访问 | |
是页面 JSP 的公共方法,因此可在主方法中访问。 请注意,我们将对象 out 作为参数传递给该方法。此处必须这样做。因为对象 out 是在 Servlet 的主方法中声明并初始化的,并非全局变量。 |
现在让我们看看从该页面生成的 Java Servlet 代码 JSP,在去除冗余代码后如下:
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 {
// 前一个标签开始全局变量和方法部分
// 该部分将原样保留在Servlet中
// 一个全局变量
String prenom="inconnu";
// 一个方法
private String sonChien(){
return "milou";
}//sonChien
// 另一个方法
private void afficheAmi(JspWriter out) throws Exception{
out.println("<p>Son ami s'appelle Haddock</p>");
}//afficheAmi
// 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");
// 前面的标签表示后续代码将被保存
// 在 Servlet 的主方法中
// 主方法的局部变量
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 ");
// 他朋友的名字
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 的主方法 _jspService。 因此,在此部分声明的变量属于实例变量,即对所有方法都有效,同时这里也可以定义除 _jspService 以外的其他方法。
// 这部分内容将原样保留在Servlet中
// 一个全局变量
String prenom="inconnu";
// 一个方法
private String sonChien(){
return "milou";
}//sonChien
// 另一个方法
private void afficheAmi(JspWriter out) throws Exception{
out.println("<p>Son ami s'appelle Haddock</p>");
}//afficheAmi
// 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 中的 URL 时,JSP 页面会被转换为 Java 源文件,随后由 Tomcat 进行编译。 此时可能会出现编译错误。Tomcat会在其返回给浏览器的响应中报告这些错误,并特别指出.java文件中出错的行号。错误可能由多种原因引起:
- 页面中的代码存在错误(例如,使用的 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.*" %>
<%!
// 应用程序的全局变量
// 页面标题
private final String title="Génération d'un formulaire";
// 列表值数据库
private final String DSNValeurs="odbc-valeurs";
private final String admDbValeurs="admDbValeurs";
private final String mdpDbValeurs="mdpDbValeurs";
// 列表值
private String[] valeurs=null;
// 错误消息
private String msgErreur=null;
// 页面初始化JSP - 仅执行一次
public void jspInit(){
// 从数据库填充值表ODBC
// 名称为 DSN:DSNvaleurs
Connection connexion=null;
Statement st=null;
ResultSet rs=null;
try{
// 连接数据库 ODBC
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
// Statement 对象
st=connexion.createStatement();
// 执行 select 查询以检索值
rs=st.executeQuery("select valeur from Tvaleurs");
// 值已检索并放入动态数组
ArrayList lstValeurs=new ArrayList();
while(rs.next()){
// 将值保存到列表中
lstValeurs.add(rs.getString("valeur"));
}//while
// 将列表转换为数组
valeurs=new String[lstValeurs.size()];
for (int i=0;i<lstValeurs.size();i++){
valeurs[i]=(String)lstValeurs.get(i);
}
}catch(Exception ex){
// 问题
msgErreur=ex.getMessage();
}finally{
try{rs.close();}catch(Exception ex){}
try{st.close();}catch(Exception ex){}
try{connexion.close();}catch(Exception ex){}
}//try
}//初始化
%>
<%
// _jspService 代码在每次客户端请求时执行
// 初始化页面 JSP 时是否发生错误?
if(msgErreur!=null){
%>
<!-- 代码 HTML -->
<html>
<head>
<title>Erreur</title>
</head>
<body>
<h3>Application indisponible (<%= msgErreur %></h3>
</body>
</html>
<%
// jspService 结束
return;
}//if
// 获取用户的可能选择
String choix=request.getParameter("cmbValeurs");
if(choix==null) choix="";
%>
<%-- 无错误 - 正常页面的代码为 HTML --%>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h3>Choisissez une valeur</h3>
<form method="POST">
<select name="cmbValeurs">
<%
// 数值动态显示
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>");
}//用于
%>
</select>
<input type="submit" value="Envoyer">
</form>
<%
// 是否选择了某个值?
if(! choix.equals("")){
// 显示用户的选项
%>
<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、valeurs 和 msgErreur,它们随后将在 JSP 代码中被使用。
- <% ... %> 标签包围的 Java 代码将被包含在 _jspService 方法中,该方法将在收到客户端请求时执行。
- 与 Servlet 类似,方法 _jspService 将首先检查变量 msgErreur 的值,以确定是否需要生成错误页面。 如果存在错误,则生成错误页面并终止(return)。
- 如果没有错误,则生成包含值列表的表单
- 完成此操作后,它会检查用户是否选择了数字,如果是,则在生成的页面中显示该数字
与Servlet相比,我们获得了什么?无疑是对生成的HTML代码更清晰的视图。但仍有大量Java代码“污染”了这一视图。 我们稍后将探讨另一种称为“委托”的方法,届时可将大部分Java代码置于Servlet中,而页面JSP仅保留代码HTML和JSP。 这样,处理部分与展示部分就得到了明确的分离。
3.3. 在 Tomcat 服务器上部署 Web 应用程序
接下来我们将介绍如何在 Tomcat 服务器上部署 Java Web 应用程序。虽然下文所述内容针对该服务器,但在其他容器中部署 Java Web 应用程序(如 J2EE)的特性将与下文描述的非常相似。
3.3.1. 配置文件 server.xml 和 web.xml
迄今为止,为了测试我们的 Servlet 和页面 JSP,我们已将
- 将Servlet放置在<tomcat>\webapps\examples\WEB-INF\classes文件夹中。当时可通过URL http://localhost:8080/examples/servlet/nomServlet
- 位于<tomcat>\webapps\examples\jsp目录树中的JSP页面。当时可通过URL访问这些页面:http://localhost:8080/examples/jsp/nomPageJSP
我们从未解释过为何如此。Tomcat 服务器的配置存储在一个名为 server.xml 的文本文件中,该文件位于 <tomcat>\conf 文件夹内:

该文本文件实际上是一个 XML 文件(eXtended 标记语言)。XML 文档与 HTML 文档一样,都是包含标签的文本文档。 然而,尽管 HTML 语言的标签定义明确,但 XML 语言的标签却未被明确定义。因此,以下文档即为 XML 文档:
XML 文档仅仅是一个“标记化”文档,遵循某些标记规则:
- 标记文本的形式为 <xx att1="val1" att2="val2" ....>文本</xx>
- 一个标签可以单独存在,形式为 <xx att1="val1" att2="val2" ..../>
字段“att”称为标签 xx 的属性,字段“val”则是与这些属性关联的值。某些 HTML 文档并非有效的 XML 文档。 例如,标签 <br> 并非有效的 XML 标签。为了符合“所有 XML 标签必须闭合”的规则,该标签应写为 <br/> 才算有效。 为了使所有 XHTML 文档成为有效的 XML 文档,创建了一个名为 XHTML 的 HTML 变体。 某些较新的浏览器能够显示 XML 文件。 因此,如果我们将上文示例中的 XML 文档命名为 personne.xml,并使用 IE6 进行查看,则会显示如下内容:

IE6 能够识别标签并为其着色。它还能通过标签识别文档的结构。因此,如果我们将以下文档命名为 personne2.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 的文档位于文件夹 examples 中(参见上文的 docBase)。 该名称相对于服务器 Web 目录树的根目录 c.a.d。<tomcat>\webapps。因此,这里指的是 <tomcat>\webapps\examples 文件夹。让我们进一步查看该文件夹:

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

该文件用于配置 Web 应用程序 examples。目前我们暂不深入探讨该文件的细节,因其过于复杂。我们仅关注以下几行代码:
<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 路径的剩余部分,在 Web 应用程序 examples 中定位该 Servlet。路径 /servlet/servletToJsp 表明它需要执行名为 servletToJsp 的 Servlet。 随后,Tomcat 将读取位于 <tomcat>\webapps\examples\WEB-INF 目录下的 examples 应用程序的配置文件 web.xml。 它将在该文件中发现,Servlet servletToJsp 关联了 Java 类 servletToJsp(参见上文的 web.xml 文件)。 随后,它将在 Web 应用程序 examples、c.a.d 的 WEB-INF\classes 文件夹中查找该类, 位于 <tomcat>\webapps\examples\WEB-INF\classes 目录中,并执行该类。

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

如上图浏览器中的Address字段所示,该Servlet的类文件名为gener3。根据前文的说明:
- URL /examples/servlet/gener3 表明这是一个名为 gener3 的 Servlet,属于 Web 应用程序 examples
- 在应用程序 examples 的文件 web.xml 中,找不到任何关于 Servlet gener3 的内容。 那么 Tomcat 是如何找到它的呢?尽管我已通读了整个 web.xml 文件,仍无法给出确切答案……这个问题依然悬而未决……
我们选择将 gener3.class Servlet 以 lstValeurs 的名称部署到一个名为 liste 的 Web 应用程序中,该应用程序位于 E:\data\serge\Servlets\lstValeurs 文件夹下:

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

我们通过在文件 server.xml 中,在定义 Web 应用程序 manager 的代码上方添加以下几行,来配置列表 Web 应用程序:
<!-- 个人:lstValeurs -->
<Context path="/liste" docBase="e:/data/serge/servlets/lstValeurs" />
<!-- Tomcat 管理器上下文 -->
<Context path="/manager" docBase="manager" debug="0" privileged="true" />
<!-- Tomcat 示例上下文 -->
<Context path="/examples" docBase="examples" debug="0" reloadable="true" crossContext="true">
........
定义应用程序列表的行表明,该应用程序位于文件夹 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 必须创建并保存到列表应用程序的 WEB-INF 文件夹中:

上图所示为 src 文件夹,其中放置了源文件 gener3.java。该文件夹可能并不存在,在本演示中也无实际用途。现在我们可以进行测试了:
- 停止并重新启动 Tomcat,使其重新读取配置文件 server.xml。此处我们使用的是 Windows 系统。在 Unix 系统中,可以强制 Tomcat 重新读取配置文件,而无需停止服务。
- 使用浏览器访问 URL http://localhost:8080/liste/servlet/lstValeurs

可以看到,前面的 URL 包含关键字 servlet,这与迄今为止使用的所有 URL servlet 一样。 我们可以通过在应用程序列表的 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 文件夹中。该页面可能是之前部署的 liste 应用程序的一个组件。我们将文件 listvaleurs.jsp 直接放置到该应用程序的文件夹中:

回顾文件 server.xml 中应用程序 liste 的配置:
所有以路径 /liste 开头的 URL 文件均被视为属于应用程序 liste 的一部分,并将从指定的文件夹中进行查找。 让我们使用浏览器访问 URL http://localhost:8080/liste/listvaleurs.jsp:

我们确实获得了预期的 JSP 页面。
3.3.4. Servlet 的初始化参数
我们已经看到,Servlet 通过文件 <application>\WEB-INF\web.xml 进行配置,其中 <application> 是其所属 Web 应用程序的文件夹。 可以在该文件中包含 Servlet 的初始化参数。让我们回到 Web 应用程序 liste 中的 Servlet lstValeurs,其配置文件如下:
<?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{
// 页面标题
private final String title="Génération d'un formulaire";
// 列表值数据库
private final String DSNValeurs="odbc-valeurs";
private final String admDbValeurs="admDbValeurs";
private final String mdpDbValeurs="mdpDbValeurs";
回顾一下上述四个常量的含义:
由Servlet生成的文档标题 HTML | |
Servlet 用于检索数据的数据库名称 DSN | |
对前述数据库拥有读取权限的用户名 | |
其密码 |
如果 DSNValeurs 数据库的管理员更改了用户 admDbValeurs 的密码,则必须修改 Servlet 的源代码并重新编译。这并不太方便。 Servlet 配置文件 web.xml 提供了一种替代方案,允许使用标签 <init-param> 定义 Servlet 的初始化参数:
用于定义参数名称 | |
定义与前一个参数关联的值 |
Servlet 可通过以下方法访问其初始化参数:
Servlet 类的方法,HttpServlet 类(用于 Web 编程)继承自该类。返回一个 ServletConfig 对象,该对象可访问 Servlet 的配置参数。 | |
ServletConfig 类的该方法返回初始化参数“paramètre”的值 |
我们使用以下新的 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>
在应用程序 liste 中,我们定义了一个名为 lstValeurs2 的第二个 Servlet,该 Servlet 与类文件 gener5 相关联。后者位于 <application>\WEB-INF\classes 目录下:

Servlet lstValeurs2 有四个初始化参数:title、DSNValeurs、admDbValeurs、mdpDbValeurs。 此外,已使用标签 <servlet-mapping> 为该 Servlet 定义了别名 /valeurs2。 因此,应用程序 liste 中的 Servlet lstValeurs2 将可通过 URL 和 http://localhost:8080/liste/valeurs2 访问。
Servlet 的源代码已按以下方式修改,以获取 Servlet 的初始化参数:
public class gener5 extends HttpServlet{
// 页面标题
private String title=null;
// 列表值数据库
private String DSNValeurs=null;
private String admDbValeurs=null;
private String mdpDbValeurs=null;
...............
// Servlet 初始化
public void init(){
// 获取 Servlet 的初始化参数
ServletConfig config=getServletConfig();
title=config.getInitParameter("title");
DSNValeurs=config.getInitParameter("DSNValeurs");
admDbValeurs=config.getInitParameter("admDbValeurs");
mdpDbValeurs=config.getInitParameter("mdpDbValeurs");
//是否已获取所有参数?
if(title==null || DSNValeurs==null || admDbValeurs==null
|| mdpDbValeurs==null){
msgErreur="Configuration incorrecte";
return;
}
// 从数据库中填充值数组 ODBC
// 名称为 DSN:DSNvaleurs
...............
要测试该Servlet,需重启Tomcat,以便其加载应用程序liste的新配置文件web.xml。 使用浏览器请求 URL 中的 http://localhost:8080/liste/valeurs2 Servlet:

如果 web.xml 文件中缺少 Servlet 所需的某个初始化参数,则会显示以下页面:

3.3.5. Web 应用程序的初始化参数
在上例中,只有 Servlet lstValeurs2 可以访问参数 title、DSNValeurs、admDbValeurs 和 mdpDbValeurs。 可以设想,同一应用程序中的另一个 Servlet(liste)需要使用与 Servlet lstValeurs2 相同的数据库中的数据。 此时,需要在该新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> 标签内。 该标签用于定义应用程序特有的信息,而非针对特定的 JSP Servlet 或页面。Java Servlet 如何访问这些通常被称为上下文参数的参数?用于获取上下文信息的可用方法与获取 Servlet 特定初始化参数的方法非常相似:
Servlet 类的方法,HttpServlet 类(用于 Web 编程)继承自该类。返回一个 ServletContext 对象,用于访问应用程序的配置参数 | |
ServletContext 类的该方法返回初始化参数“paramètre”的值 |
gener6.java 类对之前使用的 gener5.java 的 Java 代码仅进行了以下修改:
// 获取Servlet的初始化参数
ServletConfig config=getServletConfig();
title=config.getInitParameter("title");
ServletContext context=getServletContext();
DSNValeurs=context.getInitParameter("DSNValeurs");
admDbValeurs=context.getInitParameter("admDbValeurs");
mdpDbValeurs=context.getInitParameter("mdpDbValeurs");
//是否已获取所有参数?
if(title==null || DSNValeurs==null || admDbValeurs==null
|| mdpDbValeurs==null){
msgErreur="Configuration incorrecte";
return;
}
// 从数据库中填充值数组 ODBC
// 名称为 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.*" %>
<%!
// 应用程序的全局变量
// 页面标题
private final String title="Génération d'un formulaire";
// 列表值数据库
private final String DSNValeurs="odbc-valeurs";
private final String admDbValeurs="admDbValeurs";
private final String mdpDbValeurs="mdpDbValeurs";
.........
在应用程序的 web.xml 文件中,定义了四个常量:title、DSNValeurs、admDbValeurs 和 mdpDbValeurs。 常量 DSNValeurs、admDbValeurs 和 mdpDbValeurs 现已定义在应用程序级别,因此我们可以假设属于该应用程序的页面 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)与之关联。 因此,前几行代码定义了一个名为 JSPlstvaleurs 的 Servlet,并将其与页面 JSP /listvaleurs2.jsp 关联。 路径 /listvaleurs2.jsp 是相对于应用程序根目录测量的。因此,对于我们的应用程序 liste, 文件 listvaleurs2.jsp 将位于应用程序 liste 的文件夹 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 的根目录下,并关联到 Servlet 名称 JSPlstValeurs (servlet-name),该名称又与别名 /jspvaleurs(servlet-mapping)相关联。 因此,我们的页面 JSP 将可通过 URL http://localhost:8080/liste/jspvaleurs 访问。
初始页面 JSP(即 listvaleurs.jsp)被修改为 listvaleurs2.jsp,并在方法 jspInit() 中获取其四个初始化参数:
<%!
// 应用程序的全局变量
// 页面标题
private String title=null;
// 列表值数据库
private String DSNValeurs=null;
private String admDbValeurs=null;
private String mdpDbValeurs=null;
// 列表值
private String[] valeurs=null;
// 错误消息
private String msgErreur=null;
// 页面初始化 JSP - 仅执行一次
public void jspInit(){
// 获取 Servlet 的初始化参数
ServletConfig config=getServletConfig();
title=config.getInitParameter("JSPtitle");
ServletContext context=getServletContext();
DSNValeurs=context.getInitParameter("DSNValeurs");
admDbValeurs=context.getInitParameter("admDbValeurs");
mdpDbValeurs=context.getInitParameter("mdpDbValeurs");
//是否已获取所有参数?
if(title==null || DSNValeurs==null || admDbValeurs==null
|| mdpDbValeurs==null){
msgErreur="Configuration incorrecte";
return;
}
// 从数据库 ODBC 中填充值数组
// 名称为 DSN:DSNvaleurs
..............
页面 JSP 与 Servlet 一样,通过相同的方式获取其初始化参数。上述文件保存在 Web 应用程序 liste 的根目录下:

重启 Tomcat 服务器,以强制其重新读取应用程序的新配置文件 web.xml。随后即可请求 URL 和 http://localhost:8080/liste/jspvaleurs:

3.3.7. Web 应用程序中 Servlet 与页面的协作
当客户端向 Web 服务器发送请求时,响应可能由多个 Servlet 和页面 JSP 共同生成。此前,响应通常仅由单个 Servlet 或页面 JSP 生成。 我们看到,JSP页面使生成的HTML文档结构更易于阅读。然而,它通常也包含大量Java代码。可以通过将
- 将不生成响应中 HTML 代码的 Java 代码
- ,将生成发送给客户端的各种文档的代码放入 HTML 页面中
这样有望改善Java代码与HTML代码的分离。 我们将把这种新的结构应用于我们的应用程序 liste:一个 Java Servlet lstValeurs4 将负责在启动时读取数据库中的值,然后分析客户端的请求。 根据分析结果,客户端的请求将被重定向至错误页面 erreur.jsp 或数字列表显示页面 liste.jsp。 因此,应用程序 liste 将由一个 Servlet 和两个页面 JSP 组成。
Servlet 如何将从客户端接收到的请求转发给另一个 Servlet 或页面 JSP?我们将使用以下方法:
ServletContext 类的该方法会返回一个 RequestDispatcher 对象。参数 url 是我们要向其传递客户端请求的 URL 的名称。 此请求转发仅可在同一应用程序内部进行。因此,参数 url 是相对于该应用程序 Web 树结构的相对路径。 | |
RequestDispatcher 接口的方法,该方法将客户端的 request 请求以及用于生成响应的 response 对象传递给前一个 URL 方法。 | |
当一个 JSP Servlet 或页面将请求传递给另一个 JSP Servlet 或页面时,通常需要向后者传递除客户端请求之外的其他信息,这些信息源自其自身对该请求的处理。 ServletRequest 类的 setAttribute 方法允许以类似于 (属性, 值) 对的字典形式,向客户端的 request 对象添加属性 (属性,值)的字典形式,其中 attribut 是属性名称,valeur 是代表该属性值的任意对象。 | |
用于获取请求中属性的值。该方法将由接收请求的 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>
Servlet lstValeurs4 将拥有四个专属的初始化参数:
要生成的文档标题 HTML | |
错误页面 JSP 的 URL | |
包含数字列表的页面 JSP 对应的 URL | |
URL 与页面 JSPliste 所展示表单的 action 属性相关联。该 URL 将对应 servlet lstValeurs4 |
该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{
// 页面标题
private String title=null;
// 列表值数据库
private String DSNValeurs=null;
private String admDbValeurs=null;
private String mdpDbValeurs=null;
// 显示页面 JSP
private String JSPerreur=null;
private String JSPliste=null;
// Servlet 的 URL
private String URLservlet=null;
// 列表值
private String[] valeurs=null;
// 错误消息
private String msgErreur=null;
// -----------------------------------------------------------------
// GET
public void doGet(HttpServletRequest request,HttpServletResponse response)
throws IOException, ServletException{
// 将 msgErreur,title 放入请求属性中
request.setAttribute("msgErreur",msgErreur);
request.setAttribute("title",title);
request.setAttribute("URLservlet",URLservlet);
// 加载 Servlet 时是否发生错误?
if(msgErreur!=null){
// 跳转到错误页面 JSP
getServletContext().getRequestDispatcher(JSPerreur).forward(request,response);
// 结束
return;
}
// 未发生错误
// 将值列表放入请求头中
request.setAttribute("valeurs",valeurs);
// 获取用户的选项(如有)
String choix=request.getParameter("cmbValeurs");
if(choix==null) choix="";
request.setAttribute("choix",choix);
// 将控制权移交给列表展示页面 JSP
getServletContext().getRequestDispatcher(JSPliste).forward(request,response);
// 结束
return;
}//GET
// -----------------------------------------------------------------
// POST
public void doPost(HttpServletRequest request,HttpServletResponse response)
throws IOException, ServletException{
// 跳转至 GET
doGet(request,response);
}//POST
// -----------------------------------------------------------------
// 初始化 Servlet
public void init(){
// 获取 Servlet 的初始化参数
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");
//是否已获取所有参数?
if(title==null || DSNValeurs==null || admDbValeurs==null
|| mdpDbValeurs==null || JSPerreur==null || JSPliste==null || URLservlet==null){
msgErreur="Configuration incorrecte";
return;
}
// 从数据库中填充值数组 ODBC
// 名称为 DSN:DSNvaleurs
Connection connexion=null;
Statement st=null;
ResultSet rs=null;
try{
// 连接数据库 ODBC
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
// Statement 对象
st=connexion.createStatement();
// 执行 select 查询以检索值
rs=st.executeQuery("select valeur from Tvaleurs");
// 值已检索并放入动态数组
ArrayList lstValeurs=new ArrayList();
while(rs.next()){
// 将值保存到列表中
lstValeurs.add(rs.getString("valeur"));
}//while
// 将列表转换为数组
valeurs=new String[lstValeurs.size()];
for (int i=0;i<lstValeurs.size();i++){
valeurs[i]=(String)lstValeurs.get(i);
}
}catch(Exception ex){
// 问题
msgErreur=ex.getMessage();
}finally{
try{rs.close();}catch(Exception ex){}
try{st.close();}catch(Exception ex){}
try{connexion.close();}catch(Exception ex){}
}//try
}//初始化
}//类
该类的新特性在于:发生错误时将客户端请求转发至页面 JSPerreur,否则转发至页面 JSPliste。该类本身不生成响应。 这由页面 JSP、JSPerreur 和 JSPliste 负责。此前,Servlet 曾向客户端请求中添加了属性(setAttribute):
- 当页面 JSPerreur 出现错误时,显示错误信息 msgErreur
- 要显示的值(valeurs))、用户选择的值(choix)、表单标题(title)、URL (URLservlet)作为表单的 action 属性,指向页面 JSPliste
该类经过编译后被放入应用程序的类中:

显示错误消息的页面 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>
与该错误页面关联的文件名为 erreur.jsp,位于应用程序的根目录下:

其别名为 /JSPerreur,因此可通过 URL 访问 http://localhost:8080/liste/JSPerreur。 它有一个名为 mainServlet 的初始化参数,其值为前述主 Servlet 的别名。 需要注意的是,该别名是相对于应用程序根目录 liste, 的,否则将变为 /liste/valeurs4.。页面 erreur.jsp 的代码如下:
<%
// _jspService 的代码
// 获取初始化参数 mainServlet
String servletListValeurs=config.getInitParameter("mainServlet");
// 获取属性 msgErreur
String msgErreur=(String)request.getAttribute("msgErreur");
// 属性有效吗?
if(msgErreur!=null){
%>
<!-- 代码 HTML -->
<html>
<head>
<title>Erreur</title>
</head>
<body>
<h3>Application indisponible (<%= msgErreur %>)</h3>
</body>
</html>
<%
} else { // 属性 msgErreur 无效 - 返回主 Servlet
%>
<jsp:forward page="<%= servletListValeurs %>" />
<%
}
%>
该页面通常应由前一个 Servlet 调用,并向其传递 msgErreur 属性。不过,如果知道其 URL,也可以直接调用它。 此外,如果发现缺少 msgErreur 属性,则将请求转发给主 Servlet。在此,我们使用一个专用于 JSP 页面的标签,其语法为:
其中 URL 是将客户端请求转发至该 Servlet 的 URL。如果存在 msgErreur 属性,则显示错误页面。
显示数字列表的页面 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>
与该错误页面关联的文件名为 liste.jsp,位于应用程序的根目录下:

该Servlet的别名为/JSPliste,因此可通过http://localhost:8080/liste/JSPliste访问。 它有一个名为 mainServlet 的初始化参数,其值为主要 Servlet 的别名。页面 liste.jsp 的代码如下:
<%-- 值列表显示页面 --%>
<%
// 代码为 jspService
// 获取初始化参数
String servletListValeurs=config.getInitParameter("mainServlet");
// 获取来自主Servlet的请求属性
String title=(String) request.getAttribute("title");
String[] valeurs=(String[]) request.getAttribute("valeurs");
String choix=(String) request.getAttribute("choix");
String URLservlet=(String) request.getAttribute("URLservlet");
// 属性有效吗?
if(title==null || valeurs==null || choix==null){
// 存在无效属性 - 将控制权移交给 Servlet
%>
<jsp:forward page="<%= servletListValeurs %>" />
<%
}//if
%>
<%-- 代码 HTML --%>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h3>Choisissez une valeur</h3>
<form method="POST" action="<%= URLservlet %>">
<select name="cmbValeurs">
<%
// 动态显示值
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>
<%
// 是否选择了某个值?
if(! choix.equals("")){
// 显示用户的选项
%>
<hr>Vous avez choisi le nombre<h2><%= choix %></h2>
<%
}//if
%>
</body>
</html>
此页面的处理方式与页面 erreur.jsp 相同。 它通常由 /liste/valeurs4 servlet 调用,并接收 title、valeurs 和 choix 属性。 如果其中任何一个参数缺失,则将控制权移交给 URLservlet(/liste/valeurs4)Servlet。如果所有参数均存在,则显示数字列表以及用户选择的数字(如果用户选择了某个数字)。
若调用主Servlet的URL方法,将得到以下结果:

其源代码如下(View/Source):
<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、valeurs、URLservlet 这些属性已成功获取。
关于 JSP Servlet 与页面的协作,我们注意到 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>
<%!
// 页面的全局变量和方法 JSP
// 实例变量
int compteur;
// 递增计数器的方法
public int getCompteur(){
// 递增计数器
int myCompteur=compteur;
myCompteur++;
compteur=myCompteur;
// 将其归零
return compteur;
}
// 页面初始加载时执行的方法
public void jspInit(){
// 初始化计数器
compteur=100;
}
%>
上一页 JSP 将 jspInit 中的计数器初始化为 100。此后对该 Servlet 的每次请求都会递增该计数器,并显示其值:
第一次:

第二次:

从上文可以清楚地看到,在两次请求之间,Servlet 并未被卸载,否则第二次请求时计数器应为 101。当 Servlet 被卸载时,方法
(若存在)。对于页面 JSP,则执行方法
public void jspDestroy(){
}
在这些方法中,例如可以关闭数据库连接,这些连接是在相应的 init 方法中打开的。
3.4.2. 与 Servlet 的请求同步
让我们回到前面的 JSP 页面,该页面会递增一个计数器并将结果返回给 Web 客户端。假设存在 2 个并发请求。此时会创建两个线程来执行它们,这些线程将使用同一个 Servlet 实例,因此这里使用的是同一个计数器。回顾一下递增计数器的代码:
public int getCompteur(){
// 计数器递增
int myCompteur=compteur;
myCompteur++;
compteur=myCompteur;
// 返回
return compteur;
}
计数器的递增操作是故意写得比较笨拙的。假设两个线程的执行过程如下:
![]() |
- 在时间点 T1,线程 TH1 被执行。它读取了 myCompteur 中的计数器(=145),随后被中断并失去了处理器控制权。 因此,它来不及对 myCompteur 进行递增,也来不及将新值复制到 compteur 中。
- 在 T2 时间点,线程 TH2 被执行。它读取了 myCompteur 中的计数器(=145),随后被中断并失去处理器控制权。 需要注意的是,这两个线程的 myCompteur 变量不同。它们仅共享实例变量,即方法的全局变量。
- 在时间点 T3,线程 TH1 重新获得控制权并结束。因此,它向其客户端返回 146。
- 在时间戳 T4 时,线程 TH2 重新获得控制权并结束。它同样向其客户端返回了 146,而本应返回 147。
这是一种线程同步问题。当 TH1 想要递增计数器时,必须阻止其他任何线程同时进行此操作。为了突出这个问题,我们将 JSP 页面重写如下:
<html>
<head>
<title>Compteur synchronisé</title>
</head>
<body>
Compteur= <%= getCompteur() %>
</body>
</html>
<%!
// 页面的全局变量和方法 JSP
// 实例变量
int compteur;
// 递增计数器的方法
public int getCompteur(){
// 读取计数器
int myCompteur=compteur;
// 暂停 10 秒
try{
Thread.sleep(10000);
}catch (Exception ignored){}
// 计数器递增
compteur=myCompteur+1;
// 返回计数器
return compteur;
}
// 页面初始加载时执行的方法
public void jspInit(){
// 初始化计数器
compteur=100;
}
%>
在此,我们强制该线程在读取计数器后10秒停止运行。因此,它将释放处理器,另一个线程便能接替读取该计数器,而此时计数器尚未被递增。当我们通过浏览器进行请求时,除了需要等待10秒才能获得结果外,我们不会察觉到任何差异。

现在,如果我们打开两个浏览器窗口,并在时间上足够接近的情况下发出两个请求:


我们得到的计数器值是相同的。与手动操作的浏览器相比,使用编程客户端可以更清楚地凸显这个问题。下面是一个名为
程序 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){
// 数据
String syntaxe="Syntaxe : pg URL nbAppels";
// 参数验证
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
// 调用次数
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
// 参数正确——可以连接到 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
}//主函数
private static void getCompteurs (URL urlCompteur, int nbAppels)
throws Exception {
// 将 nbAppels 发送到 URL urlCompteur
// 每次显示Web服务器返回的计数器值
// 从 urlCompteur 中提取连接税务服务器所需的信息
String path=urlCompteur.getPath();
if(path.equals("")) path="/";
String host=urlCompteur.getHost();
int port=urlCompteur.getPort();
if(port==-1) port=urlCompteur.getDefaultPort();
// 调用 URL
Socket[] clients=new Socket[nbAppels];
for(int i=0;i<nbAppels;i++){
// 连接到服务器
clients[i]=new Socket(host,port);
// 创建向服务器的写入流
PrintWriter OUT=new PrintWriter(clients[i].getOutputStream(),true);
// 请求 URL - 发送头信息 HTTP
OUT.println("GET " + path + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
OUT.println("Connection: close");
OUT.println("");
}//用于
// 本地数据
String réponse=null; // 服务器响应
// 在服务器的 HTML 响应中查找的模板
Pattern modèleCompteur=Pattern.compile("^\\s*Compteur= (\\d+)");
// 正确响应的模板
Pattern réponseOK=Pattern.compile("^.*? 200 OK");
// 与模板的比对结果
Matcher résultat=null;
for(int i=0;i<nbAppels;i++){
// 每个客户端读取服务器发送给它的响应
// 创建客户端的输入-输出流 TCP
BufferedReader IN=new BufferedReader(new InputStreamReader(clients[i].getInputStream()));
// 读取响应的第一行
réponse=IN.readLine();
// 将该行 HTTP 与正确响应的模板进行比对
résultat=réponseOK.matcher(réponse);
if(! résultat.find()){
// URL 出现问题
throw new Exception("Client n° " + i + " - Le serveur a répondu : URL ["+ urlCompteur + "] inconnue");
}//if
// 读取响应直至标题结束
while((réponse=IN.readLine())!=null && ! réponse.equals("")){
}//while
// 头信息读取完毕 HTTP - 转到代码 HTML
// 用于获取计数器值
boolean compteurTrouvé=false;
while((réponse=IN.readLine())!=null){
// 将该行与计数器模板进行比对
if(! compteurTrouvé){
résultat=modèleCompteur.matcher(réponse);
if(résultat.find()){
// 找到电表
System.out.println("Compteur="+résultat.group(1));
compteurTrouvé=true;
}//if
}//if
}//while
// 结束
clients[i].close();
}//for
}//getCompteurs
}//类
让我们来解释一下上面的代码:
- 该程序接受两个参数:
- 计数器中页面 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>
<%!
// 页面的全局变量和方法 JSP
// 实例变量
int compteur;
Object verrou=new Object();
// 计数器递增方法
public int getCompteur(){
// 同步关键区
synchronized(verrou){
// 读取计数器
int myCompteur=compteur;
// 暂停 10 秒
try{
Thread.sleep(10000);
}catch (Exception ignored){}
// 递增计数器
compteur=myCompteur+1;
}//已同步
// 释放
return compteur;
}//getCompteur
// 页面初始加载时执行的方法
public void jspInit(){
// 初始化计数器
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 的多个实例。在这种情况下,之前的同步机制将不再有效,因为变量 verrou 是某个实例的局部变量,因此其他实例无法访问它。变量 compteur 也是如此。 要使这些变量对所有实例都有效,应编写如下代码:
// 类变量
static int compteur;
static Object verrou=new Object();
其余代码保持不变。





