java web 开发快速宝典 ------电子书

 

http://www.educity.cn/jiaocheng/j10259.html

 

1.2.1  JDk 简介

  JDK是Sun公司在1995年推出的一套可以跨操作系统平台编译和运行Java程序的开发包。JDK包括JRE(Java的运行环境)、Java的编译环境、Java工具集和Java类库。根据JDK的使用领域,还可以分为Java SE、Java EE和Java ME三套开发包。

其中Java SE主要用于桌面程序、服务类程序的开发;

Java EE用于企业应用程序的开发(如Web、EJB等);

Java ME用于编写在移动设备、便携式设备上运行的程序。

这三套开发包都使用相同的Java编译环境和运行环境(也就是说,都可以在操作系统上使用相同的JDK进行开发),它们的区别是所带的Java类库不同。如Java EE包含了一些在企业应用中所需要的类库,如Servlet API、JSP API等。

  在2006年,Sun已经逐步将JDK开源了。 目前JDK的最高版本为JDK1.7.这个版本可以从如下的地址下载:

  http://download.java.net/jdk7/binaries/

  从JDK1.7开始,JDK完全由开源社区进行维护和开发。因此,JDK1.7将是完全由开源社区发布的第一个JDK版本。但由于目前使用JDK1.7的开发人员和企业并不多,因此,本书的Java运行环境仍然使用比较成熟的JDK1.6.该JDK版本可以从如下的地址下载:http://java.sun.com/javase/downloads/index.jsp

 

 

  1.2.2  安装和配置JDK

  (1)在下载完JDK后,直接执行安装程序进行安装即可。

  (2)在安装完后,需要将环境变量JAVA_HOME的值设为JDK的安装目录。

  (3)假设JDK被安装在C:\java\jdk6目录中,则在"系统属性"对话框中选中"高级"选项卡,单击"环境变量"按钮,出现"环境变量"对话框,在"系统变量"列表框中添加一个JAVA_HOME环境变量,并将其值设为C:\java\jdk6,如图1.1所示。

图1.1  添加JAVA_HOME环境变量

  (4)除了设置JAVA_HOME环境变量外,还需要在PATH环境变量中添加JDK的bin目录。该路径为<JDK安装目录>\bin.设置PATH环境变量的方法与设置JAVA_HOME环境变量的方法类似。

 
 
 
 

 

测试JDK

 

  1.2.3  测试JDK

  在安装完JDK后,在Windows控制台中输入如下的命令来显示JDK的当前版本:

  java -version

  在执行完上面的命令后,如果在Windows控制台中输出如下的信息,则说明JDK已经安装成功了。

  java version "1.6.0_14-ea"

  Java(TM) SE Runtime Environment (build 1.6.0_14-ea-b04)

  Java HotSpot(TM) Client VM (build 14.0-b13, mixed mode, sharing)

  下面来编写一个简单的Java程序来测试一下JDK的编译和运行环境。在C盘根目录新建一个TestJDK.java文件,代码如下:

  public class TestJDK

  {

  public static void main(String[] args)

  {

  System.out.println("JDK安装成功,已通过测试!");

  }

  }

  执行如下的命令编译上面的程序:

  javac TestJDK.java

  如果在C盘根目录生成了TestJDK.class文件,则执行下面的命令运行TestJDK.class:

  javac -classpath . TestJDK

  如果正常输出"JDK安装成功,已通过测试!",则表明JDK编译和运行环境没有问题。

  要注意的是,在上面的javac命令中加入了"-classpath .",这是因为在当前操作系统中可能未将当前的目录加入CLASSPATH环境变量,在这时如果不加入"-classpath .",javac是不会自动在当前目录来寻找TestJDK.class文件的,因此,也就会抛出未找到TestJDK类的异常。当然,也可以直接在CLASSPATH环境变量中加入当前的路径,这样就不需要在javac后面加-classpath命令行参数了。

 

 

 

 

  1.2.3  测试JDK

  在安装完JDK后,在Windows控制台中输入如下的命令来显示JDK的当前版本:

  java -version

  在执行完上面的命令后,如果在Windows控制台中输出如下的信息,则说明JDK已经安装成功了。

  java version "1.6.0_14-ea"

  Java(TM) SE Runtime Environment (build 1.6.0_14-ea-b04)

  Java HotSpot(TM) Client VM (build 14.0-b13, mixed mode, sharing)

  下面来编写一个简单的Java程序来测试一下JDK的编译和运行环境。在C盘根目录新建一个TestJDK.java文件,代码如下:

  public class TestJDK

  {

  public static void main(String[] args)

  {

  System.out.println("JDK安装成功,已通过测试!");

  }

  }

  执行如下的命令编译上面的程序:

  javac TestJDK.java

  如果在C盘根目录生成了TestJDK.class文件,则执行下面的命令运行TestJDK.class:

  javac -classpath . TestJDK

  如果正常输出"JDK安装成功,已通过测试!",则表明JDK编译和运行环境没有问题。

  要注意的是,在上面的javac命令中加入了"-classpath .",这是因为在当前操作系统中可能未将当前的目录加入CLASSPATH环境变量,在这时如果不加入"-classpath .",javac是不会自动在当前目录来寻找TestJDK.class文件的,因此,也就会抛出未找到TestJDK类的异常。当然,也可以直接在CLASSPATH环境变量中加入当前的路径,这样就不需要在javac后面加-classpath命令行参数了。

  

 

 

 

 

Tomcat简介

 

  1.3  架设Tomcat

  1.3.1  Tomcat简介

  Tomcat是一个免费开源的Web服务器。由Apache组织负责开发和维护。目前Tomcat的最新版本是Tomcat6.0.18.该版本支持Servlet和JSP的最新规范。目前Servlet规范的最新版本是Servlet2.5、JSP规范的最新版本是JSP2.1。

  与传统的桌面应用程序不同,在Tomcat中运行的应用程序是由若干。class、。jsp等文件及war文件组成的。这些文件不能单独运行,必须依靠Tomcat中内置的Servlet容器才能运行。在Tomcat中发布程序的方法很多,最简单方法的就是直接将war文件复制到<Tomcat安装目录>\webapps目录中就可以对应用程序进行发布。Tomcat的默认端口号是8080,在发布Web程序后,可以通过该端口访问相应的Web程序。

  

安装和测试Tomcat

 

  1.3.2  安装和测试Tomcat

  Tomcat的安装文件有3种形式:zip包、tar.gz包和Windows安装程序,读者可以下载任何一种安装包进行安装。

  如果下载的是zip或tar.gz包,解压后,可运行<Tomcat安装目录>\bin目录中的startup.bat命令启动Tomcat.如果下载的是Windows安装程序,需要先安装,然后再启动Tomcat.

  启动Tomcat后,在浏览器地址栏中输入如下的URL:

  http://localhost:8080

  如果在浏览器中显示如图1.2所示的页面,则说明Tomcat已安装成功。

图1.2  Tomcat的主页面

  安装和配在本书中使用了Tomcat的最新版本6.0.18.读者可以从下面的网址下载Tomcat:

  http://tomcat.apache.org/download-60.cgi

 
 
 

 

Eclipse简介

 

  1.4  Eclipse的搭建

  1.4.1  Eclipse简介

  Eclipse是一种可扩展的开放源代码IDE.在2001年11月,IBM公司捐出了价值4000万美元的Eclipse源代码,同时组建了Eclipse基金会,并由该基金会中的成员负责这种工具的后续开发。虽然Eclipse基金会由IBM创建,但该基金会是一家非赢利机构,并独立于IBM公司。

  Eclipse最强大,也是最有魅力的地方就是支持插件扩展。Eclipse的内核非常小,而Eclipse上的各种功能丰富且强大的应用都是由Eclipse插件来完成的。由于Eclipse可以使用插件进行扩展,因此,Eclipse不仅可以做为Java的开发工具,而且也可以作为其他语言(如C/C++、PHP、Ruby、Python等)的开发工具。除此之外,Eclipse还支持报表、Web、EJB、性能测试等功能。因此可以看出,Eclipse已经用其强大的插件资源构筑了一个强大的开放平台。

 
 
 

 

安装和配置Eclipse

 

  1.4.2  安装和配置Eclipse

  (1)下载完Eclipse后,直接解压安装包(一个zip文件),并运行<Eclipse安装目录>中的eclipse.exe命令即可启动Eclipse.

  (2)为了在Eclipse中使用Tomcat,需要进行配置。选择Window | Preferences菜单项,出现Preferences对话框,在左侧的选项树中单击Service | Runtime Environments选项,单击右侧的Add按钮,并选择Apache Tomcat v6.0, 如图1.3所示。

图1.3  选择Tomcat服务器

  (3)单击Next按钮,进入下一个页面来设置Tomcat的安装目录,如图1.4所示。

图1.4  设置Tomcat的安装目录

  (4)在进行上面的设置后,会在Server Runtime Environments列表框中显示刚才选择的Tomcat服务器,如图1.5所示。

图1.5  Server Runtime Environments列表框

  (5)除此之外,在Server视图中也会显示刚才选择的Tomcat服务器,如果在服务器可发布的工程中没有读者需要发布的工程,可以通过选择Tomcat服务器右键菜单的Add and Remove Projects菜单项添加相应的Eclipse工程。如果想启动、停止、调试Tomcat,只需单击Tomcat服务器右键菜单中相应的菜单项即可。图1.6是本书所涉及到的在Server视图中的四个Eclipse工程。

图1.6  Server视图

  本书使用的是Eclipse 3.4.2的Java EE版本,可以从如下的网址下载这个Eclipse版本:

  http://www.eclipse.org/downloads/

 
 
 
 

 

下载和安装MySQL

 

  1.5  下载和安装MySQL

  在下载完MySQL的安装程序后,直接运行exe程序安装即可。由于本书中的示例使用了MySQL的root用户进行登录,而且密码都为1234,因此,在安装MySQL的过程中,需要将MySQL的root用户的密码设为1234.

  在本书的示例中使用的是MySQL 5.0,可以从如下的网址下载MySQL:

  http://dev.mysql.com/downloads/mysql/5.0.html#downloads

  虽然目前MySQL的企业版本是收费的,但可以下载MySQL的社区版本。

 
 
 
 

第 1 章:搭建开发环境作者:李宁    来源:希赛网    2014年03月07日

 

安装和运行本书的示例程序

 

  1.6  安装和运行本书的示例程序

  本书涉及如下4个Eclipse工程:

  demo:该工程包含了除了21章、22章和23章的综合实例外的所有示例程序。

  entry:该工程是第21章的"用户登录和注册系统"的源代码。

  album:该工程是第22章的"电子相册"的源代码。

  blog:该工程是第23章的"Blog系统"的源代码。

  对于Web应用程序,所有相关的jar包需要直接复制WEB-INF\lib目录中,而对于非Web程序(如控制台程序),需要在工程属性对话框中对这些包进行引用,如图1.8所示。

图1.8  工程属性对话框

  在随书光盘中有一个lib目录,该目录中包含了本书示例使用的所有jar包。读者可以在demo工程(其他3个工程都是Web应用程序,不需要引用这些包)中直接引用这些jar包。在完成上面的工作后,可以使用1.4节的方法将这4个工程添加到Server视图的Tomcat服务器上。然后启动Tomcat,就可以在浏览器地址栏中输入相应的URL来运行本书的Web应用程序了。

  除此之外,随书光盘还有一个tomcat目录,该目录中包含了同样的示例代码,读者可以将这些代码直接复制到<Tomcat安装目录>\webapps目录中,启动Tomcat后,即可运行本书提供的Web应用程序。

 
 
 
 

第 1 章:搭建开发环境作者:李宁    来源:希赛网    2014年03月07日

 

小结

 

  1.7  小结

  本章介绍了本书示例中所使用的各种软件的下载、安装和配置方法。这一章只针对不了解这些软件的读者。如果读者机器上已经有这些软件,或对这些软件已经非常熟悉,可以直接从第2章开始学习。本书提供了4个Eclipse工程,这些工程包含了本书所有示例的源代码,读者可以按着1.6节介绍的方法来运行本书提供的示例程序。

 
 
 
 

第 2 章:JDBC基础作者:李宁    来源:希赛网    2014年03月07日

 

第一个JDBC程序

 

  第2章  JDBC基础

  JDBC全称是Java DataBase Connectivity Standard,它是一种基于Java的数据库访问接口。它本身并不能操作数据库,而必须依赖于数据库厂商提供的符合JDBC规范的JDBC驱动程序才可以操作数据库。 由于JDBC是Java操作数据库的核心,所以很多基于Java的数据持久框架(如Hibernate、IBATIS等)在底层都是用JDBC实现的。因此,在学习数据持久化框架之前,理解并掌握JDBC是非常必要的。本章结合了大量的实例,对JDBC的主要知识点进行了详细的讲解。通过对本章的学习,可以使读者比较全面地了解JDBC操作数据库的方方面面,并为学习后面的内容打下基础。

  2.1  第一个JDBC程序

  不管是什么开发语言或技术,操作数据库的基本步骤都是类似的。在本节首先介绍了操作数据库的基本步骤,然后将JDBC操作数据库的步骤和这些基本步骤进行对比,最后给出了一个例子来演示如何使用JDBC来操作数据库。

  

操作数据库的一般步骤

 

  2.1.1  操作数据库的一般步骤

  尽管不同的数据库产品的使用方法不同,但操作各种数据库的基本步骤总是十分类似。就拿网络数据库(如SQL Server、Oracle、等)来说,操作数据库一般分为如下4步:

  (1)装载数据库驱动(可选)

  这一步并不是必须的,在某些操作系统中,数据库驱动的相关信息在驱动程序安装时已经被保存到指定的位置(如在Windows中,SQL Server数据库驱动信息被保存到注册表中),因此,在编写程序时,一般并不需要开发人员来完成这一步。而这一步通常是由所使用的语言或开发工具(如C#、Delphi等)自动完成的。当然,对于JDBC驱动这一步还是必须要做的。

  (2)建立数据库连接(就是获得Connection对象)

  对于网络数据库,建立数据库连接一般要提供下面5种信息:

  数据库服务器名(可以是机器名、域名、或IP地址)

  端口号

  数据库名

  用户名

  密码

  上面五种信息根据数据库产品的不同略有差异,如数据库使用的是默认端口。在连接数据库时,端口号一般可以省略。

  (3)获得用于进行数据操作的对象

  这一步对于不同的数据库产品虽然有很大的差异,但这些差异大多都是表现形式上的,而它们的作用都类似。获得这些对象后,就可以进行数据库查询、对数据库的增、删、改以及执行存储过程等操作。

  (4)关闭数据库

  在操作完数据库后,显式地将数据库关闭是一个好的习惯(尽量不要通过退出程序的方式来关闭数据库)。 

JDBC操作数据库的步骤

 

  2.1.2  JDBC操作数据库的步骤

  在上一节介绍了操作数据库的一般步骤。本节就以JDBC为例来一一对照这些步骤操作MySQL数据库。JDBC操作数据库的步骤如下:

  (1)装载数据库驱动

  这一步对于JDBC来说是必须的。用JDBC装载数据库驱动有2种方法。

  ① 使用Class.forName方法

  forName方法是Class类的一个静态方法,返回Class对象。它有一个字符串类型的参数,需要传入一个JDBC驱动类名,如下面代码所示:

  Class.forName("com.mysql.jdbc.Driver");

  其中com.mysql.jdbc.Driver为MySQL的JDBC驱动类名。

  ② 静态创建JDBC驱动类实例

  不仅可以使用forName方法动态装载JDBC驱动类,也可以直接使用new关键字静态创建JDBC驱动类对象,代码如下:

  Driver myDriver = new com.mysql.jdbc.Driver();

  DriverManager.registerDriver(myDriver);

  其中registerDriver方法是DriverManager类的静态方法,用于注册创建的JDBC驱动类对象。

  (2)建立数据库连接

  在JDBC中,可以使用DriverManager类的getConnection方法获得数据库连接对象。在获得数据库连接对象之前,需要知道如下5种信息:

  数据库服务器名:localhost

  端口号:省略

  数据库名:jdbcdemo

  用户名:root

  密码:1234

  由于MySQL使用了默认的端口号(3306),因此,端口号信息可被省略。MySQL的连接字符串格式如下:

  jdbc:mysql://servername/dbname?parameter

  按着上面的信息依次填入这个连接字符串,填完后的连接字符串如下:

  jdbc:mysql://localhost/jdbcdemo?user=root&password=1234&characterEncoding=UTF8

  由于需要在数据库中处理中文,所以在连接字符串的最后需要加上characterEncoding=UTF8,以保证正确处理中文。获得数据连接对象的代码如下:

  String connStr = "jdbc:mysql://localhost/mydb?" +

  "user=root&password=1234&characterEncoding=UTF8";

  Connection conn = DriverManager.getConnection(connStr);

  我们也可以不在连接字符串中指定用户名和密码,而使用getConnection方法的另外一个重载形式传递用户名和密码,代码如下:

  String connStr = "jdbc:mysql://localhost/mydb?characterEncoding=UTF8";

  Connection conn = DriverManager.getConnection(connStr, "root", "1234");

  除此之外,也可以使用Connection类的setCatalog方法改变当前数据库,代码如下:

  conn.setCatalog("newdb");

  (3)获得用于进行数据操作的对象

  在JDBC中可以使用Statement对象和PreparedStatement对象来操作数据库。在本节只介绍Statement对象,PreparedStatement对象将在2.4.2节介绍。Statement对象可以通过Connection接口的createStatement方法创建,createStatement方法有三种重载形式,在本节中只介绍一种无参数的重载形式。创建Statement对象的代码如下:

  Statement stmt = conn.createStatement();

  通过Statement对象可以对数据库进行查询、增、删、改等操作。在本节只介绍Statement接口的两个方法:execute和executeQuery.Statement接口的其他方法将在后面的章节介绍。

  execute方法一般用于执行DDL(CREATE、DROP等)语句,或是执行INSERT、UPDATE、DELETE等语句,如下面代码所示:

  Statement stmt = conn.createStatement();

  stmt.execute("DROP TABLE IF EXISTS t_books");

  executeQuery一般用于执行SELECT语句。这个方法通过一个ResultSet对象返回查询结果,代码如下:

  Statement stmt = conn.createStatement();

  ResultSet result = stmt.executeQuery("SELECT * FROM t_books");

  (4)关闭数据库

  最后一步就是关闭数据库。也就是关闭Connection对象。但建议在关闭Connection对象之前,应先关闭Statement对象,代码如下:

  stmt.close();

  conn.close();

 
 
 
 

第 2 章:JDBC基础作者:李宁    来源:希赛网    2014年03月07日

 

JDBC执行SQL语句

 

  2.1.3  JDBC执行SQL语句

  下面给出一个例子来演示一下如何使用JDBC来执行各种SQL语句,其中包括DDL语句(建立数据库和数据表)、INSERT语句和SELECT语句。

  【实例2.1】  JDBC执行SQL语句

  本程序首先创建一个mydb数据库(如果存在就不创建),然后创建一个用于保存图书信息的表t_books(如果存在,删除后再创建),最后向表中插入两条记录,并查询和显示其中的第2条记录。

  package chapter2;

  import java.sql.*;

  public class ExecuteDemo

  {

  public static void main(String[] args) throws Exception

  {

  // 装载JDBC驱动

  Class.forName("com.mysql.jdbc.Driver");

  // 获得Connection对象

  Connection conn = DriverManager.getConnection("jdbc:mysql://local host/?characterEncoding=UTF8","root","1234");

  // 获得Statement对象

  Statement stmt = conn.createStatement();

  String createDB = "CREATE DATABASE IF NOT EXISTS mydb DEFAULT CHARACTER SET UTF8";

  String dropTable = "DROP TABLE IF EXISTS mydb.t_books";

  String createTable = "CREATE TABLE  mydb.t_books (id int unsigned NOT NULL"+ " auto_increment, name varchar(50) NOT NULL,isbn varchar(20)"+"NOT NULL, author varchar(20) NOT NULL,price int unsigned,"+"PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=UTF8";

  String insertData1="INSERT INTO mydb.t_books(name,isbn,author,price) values(" + "'人月神话', '6787102165345', '布鲁克斯', 52)";

  String insertData2="INSERT INTO mydb.t_books(name,isbn,author,price) values("+ "'Ajax基础教程', '5643489212407', '阿斯利森', 73)";

  String selectData = "SELECT * FROM mydb.t_books where id=2";

  // 建立数据库、表,并插入数据

  stmt.execute(createDB);

  stmt.execute(dropTable);

  stmt.execute(createTable);

  stmt.execute(insertData1);

  stmt.execute(insertData2);

  // 从数据库中查询数据

  ResultSet result = stmt.executeQuery(selectData);

  // 显示查询结果

  while (result.next())

  {

  System.out.print(result.getString("id") + "   ");

  System.out.print(result.getString("name") + "   ");

  System.out.print(result.getString("isbn") + "   ");

  System.out.print(result.getString("author") + "   ");

  System.out.println(result.getInt("price"));

  }

  // 关闭Statement和Connection对象

  stmt.close();

  conn.close();

  }

  }

  在上面的代码中由于数据库mydb可能不存在,所以在连接字符串中并没有指定数据库名。因此,在使用mydb中的表时必须指定数据库名,如代码中的mydb.t_books。

 
 
 
 

第 2 章:JDBC基础作者:李宁    来源:希赛网    2014年03月07日

 

使用JDBC查询数据

 

  2.2  使用JDBC查询数据

  查询数据几乎是每一个基于数据库的系统所必须的功能。在JDBC中提供了execute和executeQuery方法来执行查询SQL语句(SELECT语句),其中execute方法更加灵活。这个方法不仅可以执行查询SQL语句,而且还可以执行非查询SQL语句(将在2.3节详细讲解)。而且可以执行多条SQL语句,并且这些SQL语句执行的顺序可以是任意的。

 
 
 
 

第 2 章:JDBC基础作者:李宁    来源:希赛网    2014年03月07日

 

使用executeQuery查询数据

 

  2.2.1  使用executeQuery查询数据

  executeQuery方法用于执行产生单个结果集的SQL语句,如SELECT语句。executeQuery方法不能执行INSERT、UPDATE、DELETE以及DDL语句,如果执行这些语句,executeQuery将抛出SQLException异常。executeQuery方法的定义如下:

  ResultSet executeQuery(String sql) throws SQLException;

  从executeQuery方法的定义上可看出,executeQuery方法返回了一个ResultSet对象,可以通过这个对象来访问查询结果集。对结果集最常用的操作就是扫描结果集。可以使用ResultSet的next方法完成这个任务。next方法做了如下两件事:

  1. 判断是否还有下一条记录。如果还有下一条记录,返回true,否则返回false.

  2. 如果有下一条记录,将当前记录指针向后移一个位置。下面的代码演示了如何使用next方法来扫描查询结果集:

  …

  //  获得Connection对象

  Connection conn = DriverManager.getConnection(url, "user", "password");

  Statement stmt = conn.createStatement();

  //  执行SQL语句(必须是SELECT语句),并返回ResultSet对象

  ResultSet rs = stmt.executeQuery(sql);

  //  通过next方法对记录集中每一条记录进行扫描

  while(rs.next())

  {

  // 处理当前行记录

  }

  stmt.close();

  conn.close();

  …

  在读取数据时可以根据列索引和列名来定义字段。读数据的方法的一般形似为getXxx.getXxx方法可以在读取数据时将数据转换为其他的数据类型。

  通过列名读取数据的部分getXxx方法定义如下:

  String getString(String columnName);

  boolean getBoolean(String columnName);

  byte getByte(String columnName);

  short getShort(String columnName);

  int getInt(String columnName);

  long getLong(String columnName);

  float getFloat(String columnName);

  下面的代码演示了如何使用getXxx方法来获得字段的值:

  …

  Connection conn = DriverManager.getConnection(url, "user", "password");

  Statement stmt = conn.createStatement();

  ResultSet rs = stmt.executeQuery(sql);

  while(rs.next())

  {

  System.out.print(rs.getString("id"));//  根据列id获得字段的值

  System.out.print(rs.getInt(1));//  取当前记录的第1个字段的值

  System.out.println(rs.getDate(2));//  取当前记录的第2个字段的值

  }

  stmt.close();

  conn.close();

  …

  在使用getXxx方法时应注意如下3点:

  1.  列索引从1开始,也就是说,第一列的索引为1.在通过列索引读取数据时要注意这一点。

  2.  可以在读取数据时进行类型转换。如字段类型是String,在读取时可以使用getInt或其他的getXxx方法。但字段值的前面部分必须是合法的整型或其他类型的值。如一个String类型的字段值是123abc,调用getInt()后,将返回123.如果类型不合法,将抛出java.sql.SQLException异常。

  3.  并不是每个getXxx方法都被当前的JDBC驱动程序支持。因此,在调用getXxx方法前需要确认当前的JDBC驱动程序是否支持要使用的getXxx方法,如果当前的JDBC驱动程序不支持某个getXxx方法,那么调用这个getXxx方法就会抛出一个异常(对于MySQL来说,将抛出com.mysql.jdbc.NotImplemented异常)。

  在某些情况下并不需要得到全部的查询结果。这时可以使用Statement接口的setMaxRows方法限制返回的记录数。还可以使用getMaxRows()方法获得最大返回记录数。这两个方法的定义如下:

  void setMaxRows(int max) throws SQLException;

  int getMaxRows() throws SQLException;

  其中max参数表示最大返回记录数。如果max的值大于等于实际查询到的记录数,则按实际查询到的记录数返回,如果max的值小于实际查询到的记录数,则返回的记录数为max.

  ResultSet类还有一些其他的功能,如下面两个方法分别用来判断字段值是否为NULL和确定某一列是否存在:

  1.  wasNull方法

  这个方法用于判断最后一次使用getXxx方法读出的字段值是否为NULL.假设name字段的值为NULL,则下面的代码将输出true:

  ResultSet rs = stmt.executeQuery(sql);

  rs.getString("name");

  System.out.println(rs.wasNull());

  2.  findColumn方法

  如果要知道某个列名在列集合中的位置,那么ResultSet接口的findColumn方法正好派上用场。findColumn方法的定义如下:

  int findColumn(String columnName) throws SQLException;

 
 
 
 

第 2 章:JDBC基础作者:李宁    来源:希赛网    2014年03月07日

 

使用execute查询数据

 

  2.2.2  使用execute查询数据

  由于execute方法可以执行任何SQL语句,因此,execute方法并不直接返回ResultSet对象,而是通过一个boolean类型的值确定执行的是返回结果集的SQL语句(如SELECT),还是不返回结果集的SQL语句(如INSERT、UPDATE等)。execute方法的定义如下:

  boolean execute(String sql) throws SQLException;

  其中参数sql表示要执行的SQL语句。当sql为返回结果集的SQL语句时,execute方法返回true,否则返回false.当返回true时,可以通过getResultSet()方法返回ResultSet对象,如果返回false,可以通过getUpdateCount()方法返回被影响的记录数。这两个方法的定义如下:

  ResultSet getResultSet() throws SQLException;

  int getUpdateCount() throws SQLException;

  用execute方法执行不返回结果集的SQL语句将在2.3.1节详细讲解,本节只介绍如何用execute执行返回结果集的SQL语句。

  【实例2.2】  使用execute查询数据

  本实例演示了如何使用execute方法查询数据,并显示查询结果。在这个例子中使用SQL语句查询t_books表中的所有数据,并使用execute方法来执行这条查询语句,最后通过Statement接口的getResultSet方法获得查询后返回的ResultSet对象。示例的实现代码如下:

  package chapter2;

  import java.sql.*;

  public class ResultSetFromExecute

  {

  public static void main(String[] args) throws Exception

  {

  Class.forName("com.mysql.jdbc.Driver");

  Connection conn = DriverManager.getConnection(

  "jdbc:mysql://localhost/mydb?characterEncoding=UTF8",

  "root", "1234");

  Statement stmt = conn.createStatement();

  String selectData = "SELECT name FROM t_books";

  //  执行查询语句

  if (stmt.execute(selectData))

  {

  //  获得返回的ResultSet对象

  ResultSet rs = stmt.getResultSet();

  while (rs.next())

  {

  System.out.println(rs.getString("name"));

  }

  }

  }

  }

  除了使用execute方法的返回值判断执行的是哪类SQL语句外,还可以通过getResultSet方法判断,如果执行的SQL语句不返回结果集,getResultSet方法返回null。

 
 
 
 

第 2 章:JDBC基础作者:李宁    来源:希赛网    2014年03月07日

 

处理多个结果集

 

  2.2.3  处理多个结果集

  execute方法不仅可以执行单条查询语句,而且还可以执行多条查询语句,不同查询语句之间用分号(;)隔开。在给出例子之前,先使用如下SQL建立一个图书销售表t_booksale,并向其中插入三条记录。

  建立t_booksale表

  DROP TABLE IF EXISTS mydb.t_booksale;

  CREATE TABLE  mydb.t_booksale (

  id int(10) unsigned NOT NULL auto_increment,

  bookid int unsigned NOT NULL,

  amount int unsigned NOT NULL,

  saledate datetime NOT NULL,

  PRIMARY KEY  (id),

  KEY bookid (bookid)

  ) ENGINE=InnoDB DEFAULT CHARSET=UTF8;

  向t_booksale表插入三条记录

  INSERT INTO mydb.t_booksale(bookid,amount,saledate) values(

  1, 23, '2007-02-04');

  INSERT INTO mydb.t_booksale(bookid,amount,saledate) values(

  1, 120, '2007-05-16');

  INSERT INTO mydb.t_booksale(bookid,amount,saledate) values(

  2, 218, '2007-06-08');

  要想处理所有的结果集,需要使用ResultSet接口的getMoreResults()方法来判断是否存在下一个结果集。getMoreResults()方法的定义如下:

  boolean getMoreResults() throws SQLException;

  getMoreResults()方法和ResultSet接口的next()方法不同。当execute方法返回多结果集时,当前位置就处于第一个结果集上,因此,应该使用do…while语句将getMoreResults方法作为while的条件,而不能使用while语句来扫描查询结果集。如下面的代码将不能获得第一个结果集:

  …

  while(stmt.getMoreResults())

  {

  rs = stmt.getResultSet();  // 只能获得第2个及后面的结果集

  }

  …

  下面给出一个完整的实例来演示如何使用execute方法返回并处理多个结果集。

  【实例2.3】  JDBC执行多条查询语句

  在这个程序中,使用execute方法执行两条SELECT语句,这两条SELECT语句分别查询t_books和t_booksale表的所有记录。这两条SELECT语句之间使用分号(;)分隔。

  public class MultiResultSet

  {

  public static void main(String[] args) throws Exception

  {

  //  装载mysql驱动

  Class.forName("com.mysql.jdbc.Driver");

  //  获得Connection对象

  Connection conn = DriverManager

  .getConnection("jdbc:mysql://localhost/mydb?characterEncoding=UTF8&allowMultiQueries=true", "root", "1234");

  Statement stmt = conn.createStatement();

  String selectData = "SELECT id,name, author FROM t_books;"

  + "SELECT bookid, amount, saledate FROM t_booksale";

  //  执行两条SELECT语句

  if (stmt.execute(selectData))

  {

  ResultSet rs = null;

  do

  {

  //  依次获得执行两条SELECT语句返回的ResultSet对象

  rs = stmt.getResultSet();

  //  输出当前记录集中的记录

  while (rs.next())

  {

  System.out.print(rs.getString(1) + "  ");

  System.out.print(rs.getString(2) + "  ");

  System.out.println(rs.getString(3));

  }

  }

  while (stmt.getMoreResults());//  判断是否还有下一个记录集

  }

  }

  }

  在使用execute方法执行多条SQL语句时应注意如下两点:

  1.  由于MySQL JDBC驱动在默认时不支持多结果集,因此,要想使用execute方法执行多条查询SQL语句,必须在连接字符串中加上allowMultiQueries=true,如本例中的实现代码所示。

  2.  当产生多个结果集时,execute方法只根据多条SQL语句中的第一条的类型来返回true或false.如果第一条SQL语句是查询语句,则getResultSet方法会返回一个ResultSet对象,execute方法返回true.而如果第一条SQL语句是不返回结果集的语句(如INSERT、UPDATE等),execute方法返回false.因此,如果在使用execute方法执行SQL语句前已经确定多条SQL语句都是查询语句时,可以不使用execute方法的返回值来判断SQL语句的类型,但如果是混合形式(就是SELECT和INSERT、UPDATE等语句混合成的SQL语句)的,就不能使用execute方法的返回值来判断是否会返回了结果集。在2.3.2节将介绍如何处理混合形式的SQL语句。

 
 
 
 

第 2 章:JDBC基础作者:李宁    来源:希赛网    2014年03月07日

 

使用JDBC对数据库进行更新

 

  2.3  使用JDBC对数据库进行更新

  对数据库的另一类重要的操作就是数据库的更新,主要包括插入数据(INSERT语句)、更新数据(UPDATE语句)和删除数据(DELETE语句)。在JDBC中有很多方法可以执行这些SQL语句,如execute方法、executeUpdate方法等,在本节将讲解如何使用这些方法来执行单条SQL语句以及和多条混合SQL语句。

 
 
 
 

第 2 章:JDBC基础作者:李宁    来源:希赛网    2014年03月07日

 

用execute方法执行混合形式的SQL语句

 

  2.3.1  用execute方法执行混合形式的SQL语句

  execute方法不仅能执行查询语句,还可以执行不返回结果集的SQL语句,甚至可以同时执行这些SQL语句的混合形式,如下面的代码所示:

  String insertData = "INSERT INTO jdbcdemo.t_books(name,isbn,author,price) values("

  + " '人月神话',  '6787102165345',  '布鲁克斯',  52)";

  selectData = "SELECT * FROM jdbcdemo.t_books";

  stmt.execute(insertData + ";" + insertData + ";" + selectData);

  如果用execute方法执行上面代码所示的混合形式的SQL语句,就不能简单地使用execute方法的返回值或getMoreResults()方法来处理每条SQL语句的执行结果,而是要使用一个getUpdateCount()方法。如果当前执行的语句是返回结果集的SQL语句,getUpdateCount()方法返回-1,否则,返回实际的更新记录数。只有当getMoreResults()方法返回false,并且getUpdateCount()返回-1时,才表明所有的结果都被处理到了,如下面代码所示:

  do

  {

  // 响应的处理代码

  }

  while (!(stmt.getMoreResults() == false && stmt.getUpdateCount() == -1));

  下面给出一个实例来演示如何使用execute方法执行混合SQL语句(包括SELECT、INSERT、UPDATE、DELETE等SQL语句)。

  【实例2.4】  用execute方法执行混合SQL语句

  用户可以通过本程序输入一个或多个SQL语句,如果输入多个SQL语句,中间用分号(;)分隔。在输入完SQL语句后,按回车后,系统将执行这些SQL语句,并输出执行的结果。如果执行的是SELECT语句,就会输出查询结果,否则,会输出记录的更新数。

  本系统是一个控制台程序,用户可以通过控制台输入SQL语句。当输入q时,系统退出。例子的实现代码如下:

  public class DBConsole

  {

  // 为字符串补齐空格

  private static String fillSpace(String s, int length)

  {

  int spaceCount = length - s.getBytes()。length;

  for (int i = 0; i < spaceCount; i++)

  s += " ";

  return s;

  }

  // 处理用户输入的SQL语句或命令

  private static boolean processCommand(String cmd, Statement stmt)

  throws Exception

  {

  // 输入q,退出程序

  if (cmd.equals("q"))

  {

  stmt.close();

  return false;

  }

  stmt.execute(cmd);// 执行sql语句(可能是多条)

  do

  {

  ResultSet rs = null;

  rs = stmt.getResultSet();//  获得执行的第一条SQL语句的ResultSet对象

  //  如果返回的不是空,则说明执行的第一条SQL语句是SELECT语句

  if (rs != null)

  {

  //  得到当前记录集的列数

  int columnCount = rs.getMetaData()。getColumnCount();

  //  输出列名,每列宽度是20个字符

  for (int i = 1; i <= columnCount; i++)

  System.out.print(fillSpace(rs.getMetaData()

  .getColumnName(i), 20));

  System.out.println();

  //  输出记录集中的记录

  while (rs.next())

  {

  for (int i = 1; i <= columnCount; i++)

  {

  System.out.print(fillSpace(rs.getString(i), 20));

  }

  System.out.println();

  }

  }

  //如返回的ResultSet对象是null,说明执行的第一条SQL语句是非SELECT语句

  else

  System.out.println("更新记录数:" + stmt.getUpdateCount());

  }

  //  判断是否处理完了所有的执行结果

  while (!(stmt.getMoreResults() == false && stmt.getUpdateCount() == -1));

  return true;

  }

  public static void main(String[] args) throws Exception

  {

  String serverName, dbName, userName, password;

  //  定义服务器名、数据库名、用户名和密码

  serverName = "localhost";

  dbName = "mydb";

  userName = "root";

  password = "1234";

  //  定义连接字符串

  String url = "jdbc:mysql://" + serverName + "/" + dbName

  + "?characterEncoding=UTF8&allowMultiQueries=true";

  //  装载mysql驱动

  Class.forName("com.mysql.jdbc.Driver");

  //  获得Connection对象

  Connection conn = DriverManager.getConnection(url, userName, password);

  Statement stmt = conn.createStatement();

  String cmd = "";

  do

  {

  java.io.InputStreamReader isr = new java.io.InputStreamReader(

  System.in);

  java.io.BufferedReader br = new java.io.BufferedReader(isr);

  System.out.print("sql>");

  cmd = br.readLine();  // 从控制台读入用户输入的命令

  if(cmd.equals("")) continue;//  如果输入空串,重新循环

  try

  {

  //  开始处理输入的SQL语句,如果输入的是q,则返回false,并退出系统

  if (!processCommand(cmd, stmt))

  break;

  }

  catch (Exception e)

  {

  System.out.println(e.getMessage());

  }

  }

  while (true);

  conn.close();

  }

  }

  使用如下的命令运行DBConsole:

  java chapter2.DBConsole

  在控制台中输入如下的SQL语句:

  select name, author, price from t_books;delete from t_books where price=52

  在输入上面的SQL语句后,按"回车键",在控制台中将输出如图2.1所示的运行结果。

图2.1  DBConsole运行界面

  在使用execute方法执行多条SQL语句时,由于这些SQL语句可能是SELECT语句,也可能是UPDATE、INSERT等不返回结果集的SQL语句,因此,要想处理所有SQL语句执行的结果(处理SELECT语句返回的结果集,获得不返回结果集的SQL语句的更新记录数),必须要同时满足以下两个条件,才表示所有的执行结果都处理完毕了:

  stmt.getMoreResults() == false

  stmt.getUpdateCount() == -1

 
 
 
 

第 2 章:JDBC基础作者:李宁    来源:希赛网    2014年03月07日

 

用executeUpdate方法更新数据

 

  2.3.2  用executeUpdate方法更新数据

  除了使用execute方法执行不返回结果集的SQL语句外,还可以使用executeUpdate方法来完成同样的工作。executeUpdate()方法的定义如下:

  int executeUpdate(String sql) throws SQLException;

  在2.2.1节曾讲到executeQuery方法不能执行象INSERT、UPDATE一样的不返回查询结果的语句。但这种说法并不严谨。这种说法对于一条SQL语句是没有任何问题的,但如果对于多条SQL语句同时执行的情况下,就不够准确。更严谨的说法应该是"executeQuery方法执行的第一条SQL语句必须是返回结果集的SQL语句,而后面跟着的其他SQL语句可以是任何正确的SQL语句,其中包括INSERT、DELETE、UPDATE、CREATE TABLE等".也就是说,下面的SQL语句是可以使用executeQuery方法成功执行的:

  String sql = "SELECT name, author, price FROM t_books; DELETE FROM t_booksale WHERE id = 1";

  stmt.executeQuery(sql);

  如果executeQuery方法执行的是多条SQL语句,并且第一条是查询语句,仍然能正确返回ResultSet对象。

  executeUpdate方法和executeQuery方法类似,也就是说,executeUpdate方法在执行多条SQL语句时,第一条SQL语句必须是不返回结果集的SQL语句,而第2条及以后的SQL语句可以是任何正确的SQL语句。因此,实例2-4中的execute方法可以用executeUpdate或executeQuery代替,但是输入SQL时就会有限制。如果用executeUpdate方法代替,所输入的第一条SQL语句必须是不返回结果集的SQL语句,而用executeQuery方法代替时,正好相反。

 
 
 
 

第 2 章:JDBC基础作者:李宁    来源:希赛网    2014年03月07日

 

获得自增字段的值

 

  2.3.3  获得自增字段的值

  有很多数据库都支持自增类型字段,但在插入数据时,同时获得自增字段的值是比较麻烦的。但如果使用JDBC,就非常容易做到这一点。

  在Statement接口中提供了一个getGeneratedKeys方法,可以获得最近一次插入数据后自增字段的值。getGeneratedKeys()方法的定义如下:

  ResultSet getGeneratedKeys() throws SQLException;

  getGeneratedKeys方法返回一个ResultSet对象,第一个字段的值就是自增字段的值。如果同时插入多条记录,可使用next()方法对ResultSet对象进行扫描。

  使用execute方法和executeUpdate方法都可以获得自增字段的值,如果执行多条INSERT语句,则只返回第一条INSERT语句生成的自增字段的值。

  【实例2.5】  获得自增字段的值

  下面的代码演示了如何用getGeneratedKeys()方法获得自增字段的值:

  public class AutoGeneratedKeyValue

  {

  public static void main(String[] args) throws Exception

  {

  Class.forName("com.mysql.jdbc.Driver");

  Connection conn = DriverManager.getConnection(

  "jdbc:mysql://localhost/mydb?characterEncoding=UTF8",

  "root", "1234");

  Statement stmt = conn.createStatement();

  String insertData1 = "INSERT INTO t_booksale(bookid, amount, saledate) VALUES(1, 20, '2004-10-10')";

  String insertData2 = "INSERT INTO t_booksale(bookid, amount, saledate) SELECT bookid,amount,saledate FROM t_booksale";

  stmt.execute(insertData1);

  ResultSet rs = stmt.getGeneratedKeys();// 获得单个递增字段值

  if (rs.next())

  {

  System.out.println("自增自段的值: " + rs.getString(1));

  System.out.println("-------------------------------");

  }

  stmt.executeUpdate(insertData2);

  rs = stmt.getGeneratedKeys();// 获得多个递缯字段值

  while (rs.next())

  {

  System.out.println("自增自段的值: " + rs.getString(1));

  }

  stmt.close();

  conn.close();

  }

  }

  除此之外,还可以通过execute和executeUpdate方法来控制是否可以获得自增字段的值,代码如下:

  // 无法获得自增字段的值

  stmt.execute(insertData1, Statement.NO_GENERATED_KEYS);

  // 可以获得自增字段的值

  stmt.execute(insertData1, Statement. RETURN_GENERATED_KEYS);

  // 无法获得自增字段的值

  stmt.executeUpdate(insertData1, Statement.NO_GENERATED_KEYS);

  // 可以获得自增字段的值

  stmt.executeUpdate(insertData1, Statement. RETURN_GENERATED_KEYS);

  对于MySQL数据库来说,使用NO_GENERATED_KEYS和RETURN_GENERATED_KEYS都可以获得自增字段值,也就是说MySQL JDBC忽略了这个参数。而对于其它数据库的JDBC驱动,就未必是这个结果。如SQL Server2005 JDBC就必须使用Statement. RETURN_GENERATED_KEYS才可以获得自增字段的值。读者在使用这一特性获得自增字段值时应注意这一点。

 
 
 
 

第 2 章:JDBC基础作者:李宁    来源:希赛网    2014年03月07日

 

JDBC高级技术

 

  2.4  JDBC高级技术

  JDBC不仅仅能执行SQL语句,它还能做更多的事情,例如,调用存储过程、通过参数动态执行SQL语句、进行事务管理等。通过这些高级的数据库操作技术,可以开发出更强大的系统。在本节将就这些技术进行讲解。

 
 
 
 

第 2 章:JDBC基础作者:李宁    来源:希赛网    2014年03月07日

 

调用存储过程

 

  2.4.1  调用存储过程

  在JDBC中调用存储过程需要使用Connection接口的prepareCall方法。prepareCall方法的定义如下:

  CallableStatement prepareCall(String sql) throws SQLException;

  其中sql参数表示调用存储过程的SQL语句,如果存储过程含有参数,需要使用"?"作为占位符,并使用CallableStatement接口的setXxx方法为参数赋值。setXxx方法可以使用参数名或参数索引来确定参数的位置。

  在使用prepareCall方法之前,先用如下的SQL语句建立一个存储过程,这个存储过程有一个输入参数:id,一个输出参数:total.存储过程的功能是统计t_booksale表中bookid字段的值等于id的图书销售总量。建立存储过程的SQL语句如下:

  DROP PROCEDURE IF EXISTS mydb.p_myproc;

  DELIMITER //

  CREATE PROCEDURE mydb.p_myproc(IN id int, OUT total int)

  begin

  SELECT sum(amount) INTO total FROM mydb.t_booksale WHERE bookid = id;

  end //

  DELIMITER ;

  对于存储过程的输出参数,需要使用registerOutParameter方法进行注册,registerOutParameter方法的定义如下:

  void registerOutParameter(int parameterIndex,int sqlType)throws SQLException;

  void registerOutParameter(String parameterName, int sqlType) throws SQLException;

  从方法定义可以看出,registerOutParameter方法也可以使用参数索引或参数名来注册输出参数。其中sqlType参数表示输出参数的类型,它的值是java.sql.Types类定义的类型值中的一个。最后,可以使用getXxx方法获得输出参数返回的值。getXxx方法和setXxx方法类似,也可以通过参数索引或参数名来指定参数。

  【实例2.6】  调用存储过程

  下面是一个调用存储过程的例子,代码如下:

  public class StoredProcedure

  {

  public static void main(String[] args) throws Exception

  {

  Class.forName("com.mysql.jdbc.Driver");

  Connection conn = DriverManager.getConnection(

  "jdbc:mysql://localhost/mydb?characterEncoding=UTF8",

  "root", "1234");

  CallableStatement cstmt = conn.prepareCall("call p_myproc(?, ?)");

  // 向输入参数传值

  cstmt.setString(1, "2");

  // 注册输出参数

  cstmt.registerOutParameter(2, java.sql.Types.INTEGER);

  // 调用存储过程

  cstmt.executeUpdate();

  // 获得输出参数返回的值

  System.out.println(cstmt.getInt(2));

  cstmt.close();

  conn.close();

  }

  }

 
 
 
 

第 2 章:JDBC基础作者:李宁    来源:希赛网    2014年03月07日

 

使用PreparedStatement对象执行动态SQL

 

  2.4.2  使用PreparedStatement对象执行动态SQL

  动态SQL实际上就是带参数的SQL.通过PreparedStatement对象可以执行动态的SQL.由于动态SQL没有参数名,只有参数索引,因此,PreparedStatement接口的getXxx方法和setXxx方法只能通过参数索引来确定参数。PreparedStatement对象和Statement对象的使用方法类似,所不同的是Connection对象使用prepareStatement方法创建PreparedStatement对象。在创建PreparedStatement对象时,必须使用prepareStatement方法指定一个动态SQL.

  【实例2.7】  执行动态SQL

  下面是一个执行动态SQL语句的例子,代码如下:

  public class DynamicSQL

  {

  public static void main(String[] args) throws Exception

  {

  Class.forName("com.mysql.jdbc.Driver");

  Connection conn = DriverManager.getConnection(

  "jdbc:mysql://localhost/mydb?characterEncoding=UTF8",

  "root", "1234");

  String selectData = "SELECT name, author FROM t_books WHERE id = ?";

  //  获得PreparedStatement对象

  PreparedStatement pstmt = conn.prepareStatement(selectData);

  pstmt.setInt(1, 1);// 赋参数值

  ResultSet rs = pstmt.executeQuery();

  //  输出返回的结果集

  while (rs.next())

  {

  System.out.println("书名:" + rs.getString("name"));

  System.out.println("作者:" + rs.getString("author"));

  System.out.println("---------------------");

  }

  pstmt.close();//  关闭PreparedStatement对象

  conn.close();

  }

  }

  既然有了Statement对象,为什么JDBC又要引入PreparedStatement呢?其主要的原因有如下四点:

  1.  提高代码可读性和可维护性

  虽然使用PreparedStatement对象从表面上看要比使用Statement对象多好几行代码,但如果使用的SQL是通过字符串连接生成的,那么使用Statement对象的代码的可读性就会变得很差,如下面代码如示:

  使用Statement对象的代码:

  stmt.executeQuery("SELECT id, name, isbn, author,price FROM t_books WHERE id >" + id + " and name like '%" + subname + "%' and author = '" + author + "'");

  使用PreparedStatement对象的代码:

  pstmt = conn.prepareStatement ("SELECT id, name, isbn, author,price FROM t_books WHERE id >? and name like ? and author = ?");

  pstmt.setString(1,  id);

  pstmt.setString(2,  subname);

  pstmt.setString(3,  author);

  pstmt.executeQuery();

  从上面的代码可以看出,使用PreparedStatment对象的代码虽然多了几行,但显得更整洁。

  2.  有助于提高性能

  由于PreparedStatement对象在创建时就指定了动态的SQL,因此,这些SQL被DBMS编译后缓存了起来,等下次再执行相同的预编译语句时,就无需对其再编译,只需将参数值传入即可执行。由于动态SQL使用了"?"作为参数值占位符,因此,预编译语句的匹配几率要比Statement对象所使用的SQL语句大的多,所以,在多次调用同一条预编译语句时,PreparedStatement对象的性能要比Statement对象高得多。

  3.  提高可复用性

  动态SQL和存储过程十分类似,可以只写一次,然后只通过传递参数进行多次调用。这在执行需要不同条件的SQL时非常有用。

  4.  提高安全性

  在前面的章节讲过,execute、executeQuery和executeUpdate方法都可以执行多条SQL语句。那么这就存在一个安全隐患。如果使用Statement对象,并且SQL通过变量进行传递,就可能会受到SQL注入攻击,看下面的代码:

  String author = "阿斯利森";

  String selectData = "SELECT * FROM jdbcdemo.t_books where author = '" + author + "'";

  stmt.executeQuery(selectData);

  上面的代码并没有任何问题,但如果将author的值改为如下的字符串,就会在执行完SELECT语句后,将t_booksale删除。

  "';drop table jdbcdemo.t_booksale;";

  而如果使用PreparedStatement对象,就不会发生这样的事情。因此,在程序中应尽量使用PreparedStatement对象,并且在连接数据库时,除非必要,否则在连接字符串中不要加"allowMultiQueries=true"来打开执行多条SQL语句的功能。

 
 
 
 

第 2 章:JDBC基础作者:李宁    来源:希赛网    2014年03月07日

 

存取BLOB字段值

 

  2.4.3  存取BLOB字段值

  许多数据库都支持二进制字段,对这类字段的处理相对繁琐一些,在读取时需要使用Statement,而在写入时,必须使用PreparedStatement对象的setBinaryStream方法。

  【实例2.8】  向BLOB类型字段中写入数据

  下面程序演示了如何向BLOB类型字段中写入数据,代码如下:

  public class BlobView

  {

  public static void main(String[] args) throws Exception

  {

  Class.forName("com.mysql.jdbc.Driver");

  Connection conn = DriverManager.getConnection(

  "jdbc:mysql://localhost/mydb?characterEncoding=UTF8","root", "1234");

  //  打开D盘上的image.jpg文件

  java.io.File picFile = new java.io.File("d:\\image.jpg");

  //  获得该图像文件的大小

  int fileLen = (int) picFile.length();

  //  获得这个图像文件的InputStream对象

  java.io.InputStream is = new java.io.FileInputStream(picFile);

  //  获得PreparedStatement对象

  PreparedStatement pstmt = conn

  .prepareStatement("INSERT INTO t_image(name, image) VALUES(?, ?)");

  pstmt.setString(1, "mypic");//  为第一个参数设置参数值

  pstmt.setBinaryStream(2, is, fileLen);//  为第二个参数设置参数值

  pstmt.executeUpdate();//  更新数据库

  pstmt.close();

  conn.close();

  }

  }

 
 
 
 

第 2 章:JDBC基础作者:李宁    来源:希赛网    2014年03月07日

 

事务管理

 

  2.4.4  事务管理

  数据库的事务就是将任意多个SQL语句看作一个整体,只有这些SQL语句都成功执行,DBMS才会保存这些SQL语句对数据库的修改(事务提交)。否则,数据库将恢复到执行SQL语句之前的状态(事务回滚)。大多数DBMS都支持两种事务模式:隐式模式和显式模式。当执行每一条SQL语句时,无需进行事务提交,就可以直接将修改结果保存到数据库中。这叫做隐式事务模式。显式模式必须使用相应的语句或命令开起事务、提交事务和回滚事务。

  在使用JDBC时,默认情况下是隐式事务模式。但JDBC提供了setAutoCommit方法,可以将隐式模式改为显式模式。setAutoCommit方法的定义如下:

  void setAutoCommit(boolean autoCommit) throws SQLException;

  当autoCommit参数值为false时,JDBC工作在显式事务模式下,也就是说,只有使用commit方法进行提交,对数据库的修改才能生效。

  【实例2.9】  事件的提交和回滚

  下面的代码演示了如何在JDBC中使用事务:

  public class Transaction

  {

  public static void main(String[] args) throws Exception

  {

  Class.forName("com.mysql.jdbc.Driver");

  Connection conn = DriverManager.getConnection(

  "jdbc:mysql://localhost/mydb?characterEncoding=UTF8",

  "root", "1234");

  try

  {

  conn.setAutoCommit(false);//  开始事务

  Statement stmt = conn.createStatement();

  System.out.println("更新记录数:" + stmt

  .executeUpdate("UPDATE t_books SET price = price - 3"));

  stmt.close();

  PreparedStatement pstmt = conn

  .prepareStatement("INSERT INTO t_booksale(bookid, amount, saledate) VALUES(?, ?, ?)");

  pstmt.setInt(1, 2);//  设置第一个参数值

  pstmt.setInt(2, 206);//  设置第二个参数值

  pstmt.setString(3, "2007-12-25");//  设置第三上参数值

  System.out.println("更新记录数:" + pstmt.executeUpdate());

  pstmt.close();

  conn.commit();//  提交事务

  }

  catch (Exception e)

  {

  conn.rollback();//  回滚事务

  }

  conn.close();

  }

  }

  在上面的代码中如果不使用commit方法提交事务,输出的更新记录数和使用common方法时一样,但t_books和t_booksale表中的数据并未改变。因此,在显式事务模式下,通过更新记录数并不能确定是否已经将数据保存到数据库中,这一点在使用中应注意。

  在catch块中使用了rollback方法将事务回滚。可以将上面代码中的INSERT语句做一下更改,如将amount改为amount1,这样,在执行这条INSERT语句时就会抛出异常,然后程序将进入catch块中执行rollback方法进行事务回滚。在执行完程序后,从数据库中可以看到,t_books和t_booksale表中的数据均未改变。

 
 
 
 

第 2 章:JDBC基础作者:李宁    来源:希赛网    2014年03月07日

 

获得元数据

 

  2.5  获得元数据

  元数据(MetaData),也称为数据的数据。在数据库中,可以直接看到表、视图、存储过程中的内容,这就是数据。而这些数据还需要另外一些信息来描述,例如,字段类型、字段长度、字段名、存储过程中的参数类型(IN、OUT、INOUT)、参数数据类型等信息。这些用来描述数据本身的信息就叫做元数据。JDBC支持三种元数据:数据库元数据、结果集元数据和参数元数据。

 
 
 
 

第 2 章:JDBC基础作者:李宁    来源:希赛网    2014年03月07日

 

数据库元数据

 

  2.5.1  数据库元数据

  数据库元数据就是和数据库本身及其子项(表、视图等)相关的数据,使用Connection接口的getMetaData方法可以获得JDBC提供的所有的元数据。getMetaData方法的定义如下:

  DatabaseMetaData getMetaData() throws SQLException;s

  DatabaseMetaData接口为我们提供了很多用于访问数据库元数据的方法,如数据库版本、JDBC驱动名、JDBC驱动版本、表信息、视图信息、存储过程信息等。下面是一些常用的获得数据库元数据的方法:

  String getDatabaseProductName();

  String getDatabaseProductVersion() ;

  String getDriverName();

  String getDriverVersion();

  ResultSet getCatalogs();

  ResultSet getTables(String catalog,  String schemaPattern,  String tableNamePattern,  String types[]);

  ResultSet getProcedures(String catalog,  String schemaPattern,  String procedureNamePattern);

  下面的代码演示了如何使用上面的方法来获得数据库元数据,这个程序将列出一些和数据库相关的信息,以及服务器中所有的数据库名、mydb数据库中的表名、视图名、存储过程名和函数名,

  【实例2.10】  获得数据库元数据

  实例的代码如下:

  public class DBMetaData

  {

  public static void main(String[] args) throws Exception

  {

  Class.forName("com.mysql.jdbc.Driver");

  //  获得Connection对象

  Connection conn =

  DriverManager.getConnection("jdbc:mysql://localhost/mydb?" +

  characterEncoding=UTF8", "root", "1234");

  //  获得DatabaseMetaData对象

  DatabaseMetaData dbmd = conn.getMetaData();

  //  开始输出和数据库有关的元数据

  System.out.println("数据库产品名:" + dbmd.getDatabaseProductName());

  System.out.println("数据库版本:" + dbmd.getDatabaseProductVersion());

  System.out.println("JDBC驱动名:" + dbmd.getDriverName());

  System.out.println("JDBC驱动版本:" + dbmd.getDriverVersion());

  System.out.println("--------------数据库-------------");

  //  获得数据库列表

  ResultSet databases = dbmd.getCatalogs();

  //  输出数据库名

  while(databases.next())

  {

  System.out.println(databases.getString("TABLE_CAT"));

  }

  System.out.println("--------------表-------------");

  //  获得mydb数据库中的表名

  ResultSet tables = dbmd.getTables("mydb", null, null, new String[]{"table"});

  while(tables.next())

  {

  System.out.println(tables.getString("TABLE_NAME"));

  }

  System.out.println("--------------视图-------------");

  //  获得mydb数据库中的表名

  ResultSet views = dbmd.getTables("mydb", null, null, new String[]{"view"});

  while(views.next())

  {

  System.out.println(views.getString("TABLE_NAME"));

  }

  System.out.println("--------------存储过程、函数-------------");

  //  获得mydb数据库中的存储过程

  ResultSet procfun = dbmd.getProcedures("mydb", null, null);

  while(procfun.next())

  {

  System.out.println(procfun.getString("PROCEDURE_NAME"));

  }

  conn.close();

  }

  }

 
 
 
 

第 2 章:JDBC基础作者:李宁    来源:希赛网    2014年03月07日

 

结果集元数据

 

  2.5.2  结果集元数据

  在前面讲过,execute、executeQuery和executeUpdate方法都可以返回ResultSet对象。通过ResultSet接口的next方法可以对数据进行扫描,但要获得ResultSet对象的元数据(列数、列名、字段类型等),就需要使用ResultSet接口的getMetaData方法,getMetaData方法的定义如下:

  ResultSetMetaData getMetaData() throws SQLException;

  可以通过ResultSetMetaData接口的getXxx和isXxx方法获得ResultSet对象的元数据,下面是部分getXxx和isXxx方法的定义代码:

  int getColumnCount() ;

  String getColumnName(int column);

  String getColumnTypeName(int column);

  String getColumnClassName(int column);

  int getColumnDisplaySize(int column);

  boolean isAutoIncrement(int column);

  下面的例子演示了如何使用这些getXxx和isXxx方法来获得结果集元数据。

  【实例2.11】  获得结果集元数据

  实例的代码如下:

  public class RSMetaData

  {

  public static void main(String[] args) throws Exception

  {

  Class.forName("com.mysql.jdbc.Driver");

  Connection conn = DriverManager.getConnection(

  "jdbc:mysql://localhost/mydb?characterEncoding=UTF8",

  "root", "1234");

  Statement stmt = conn.createStatement();

  stmt.setMaxRows(1);                         // 只返回一条数据

  ResultSet rs = stmt.executeQuery("SELECT * FROM t_books");

  //  获得返回结果集的元数据

  ResultSetMetaData rsmd = rs.getMetaData();

  //  输出结果集的元数据

  for(int i = 1; i <= rsmd.getColumnCount(); i++)

  {

  System.out.println("列名:" + rsmd.getColumnName(i));

  System.out.println("SQL类型:" + rsmd.getColumnTypeName(i));

  System.out.println("对应的Java类型:" + rsmd.getColumnClassName(i));

  System.out.println("列尺寸:" + rsmd.getColumnDisplaySize(i));

  System.out.println("自增字段:" + rsmd.isAutoIncrement(i));

  System.out.println("----------------------------");

  }

  conn.close();

  }

  }

 
 
 
 

第 2 章:JDBC基础作者:李宁    来源:希赛网    2014年03月07日

 

参数元数据

 

  2.5.3  参数元数据

  在JDBC中可以通过PreparedStatement接口 和CallableStatement接口的getParamterMetaData方法获得参数元数据(参数个数、参数类型等)。getParamterMetaData方法的定义如下:

  ParameterMetaData getParameterMetaData() throws SQLException;

  下面的实例演示了如何使用ParameterMetaData接口的getXxx方法获得参数元数据。

  【实例2.12】  获得参数元数据

  实例的代码如下:

  public class PMetaData

  {

  public static void main(String[] args) throws Exception

  {

  Class.forName("com.mysql.jdbc.Driver");

  Connection conn = DriverManager.getConnection(

  "jdbc:mysql://localhost/mydb?characterEncoding=UTF8",

  "root", "1234");

  //  获得CallableStatement对象

  CallableStatement cstmt = conn.prepareCall("call p_myproc(?, ?)");

  //  获得参数元数据

  ParameterMetaData pmd = cstmt.getParameterMetaData();

  //  输出参数元数据

  for (int i = 1; i <= pmd.getParameterCount(); i++)

  {

  switch (pmd.getParameterMode(i))

  {

  case ParameterMetaData.parameterModeIn:

  System.out.println("参数模式:IN");

  break;

  case ParameterMetaData.parameterModeOut:

  System.out.println("参数模式:OUT");

  break;

  case ParameterMetaData.parameterModeInOut:

  System.out.println("参数模式:INOUT");

  break;

  default:

  System.out.println("参数模式:Unknown");

  }

  System.out.println("参数类型:" + pmd.getParameterTypeName(i));

  System.out.println("参数类名:" + pmd.getParameterClassName(i));

  System.out.println("------------------------------");

  }

  cstmt.close();

  conn.close();

  }

  }

 
 
 
 

第 2 章:JDBC基础作者:李宁    来源:希赛网    2014年03月07日

 

小结

 

  2.6  小结

  本章介绍了操作数据库的一般步骤,然后结合JDBC的操作流程给出了一个简单的操作数据库的例子,以使读者一开始就能体会到JDBC的强大和便利。接下来从JDBC的常用功能(如对数据的查询、更新等)进行讲解,逐渐从浅入深,最后讨论了JDBC的一些高级特性(如调用存储过程、建立动态SQL语句等),从而使读者对JDBC有一个较为全面的了解。

 
 
 
 

第 3 章:Java Web程序的Helloworld作者:李宁    来源:希赛网    2014年03月07日

 

JSP与Servlet简介

 

  第3章  Java Web程序的Helloworld

  本章给出了一个简单的例子来演示如何用Servlet和JSP技术开发一个简单的Web程序。这个程序的功能是通过JSP页面选择要查询的项(书名、作者、ISBN),并输入要查询的信息,然后通过Servlet返回查询结果,最后在另一个JSP页面中显示查询结果。通过学习本章的示例子,读者可以掌握使用JSP和Servlet技术开发Web程序的基本过程,并为后面的学习打下基础。

  3.1  JSP与Servlet简介

  JSP(JavaServet Pages)是Sun公司于上个世纪末(1999年)推出的一种动态网页技术。JSP技术和ASP技术非常类似,JSP在传统的静态网页文件(。htm,.html)中插入Java代码段和JSP标签(tag),从而形成了JSP文件(*.jsp)。

  在JSP页面中可以使用由Java语言编写的标签和Java代码来封装产生动态网页的处理逻辑。这种标签的语法类似于XML,在运行JSP时,JSP页面中的标签被转换成Java语句的调用。JSP还可以通过标签和Java代码访问服务端的资源。JSP将网页逻辑与表现层分离,支持可重用的基于组件的设计与实现,使基于Web的应用程序的开发变得迅速和容易。

  JSP是在服务器端执行的,它返回给客户端的都是一些客户端代码(如HTML、JavaScript等),因此,客户端只要有Web浏览器,就可以访问基于JSP和Servlet的Web程序。

  由于JSP是基于Java的,因此,JSP也拥有和Java一样的跨平台能力,也就是说,JSP不仅可以在Windows中运行,而且还可以任何支持Java的操作系统平台上运行,如Linux、Unix等。

  Servlet也是Sun公司推出的一种服务端技术,这种技术推出的时间要比JSP早一点(1998年),Servlet并不象JSP一样可以很容易地设计用户页面。实际上,Servlet技术一般被用来处理客户端请求,然后通过JSP将处理后的结果呈现给客户端浏览器。

  从本质上讲JSP是基于Servlet实现的,也就是说,JSP页面在第一次访问时,被编译成了Servlet,当再次访问这个JSP页面时,就和Servlet没有任何区别了,因此,JSP在运行效率上要比ASP快得多。

  综合上述,JSP有如下优势:

  1.  一次编写,到处运行。这也是Java的优势之一。如果要将JSP程序移植到其他操作系统平台上,JSP代码并不需要做任何修改。

  2.  操作系统平台的多样性。由于Java支持大量的操作系统平台,理所当然,JSP也同样跟着沾光。只要是Java程序能运行的平台,JSP就同样也可以在这种平台上运行。

  3.  可伸缩性。JSP不仅可以通过一个小小的jar文件或单独的。jsp文件来运行,还可以在多台服务器组成的集群中运行,达到负载均衡。

  4.  运行效率高。由于JSP页面在第一次访问时就会被编译成了Servlet,因此,在运行效率上,JSP和Servlet是一样的。

 
 
 
 

第 3 章:Java Web程序的Helloworld作者:李宁    来源:希赛网    2014年03月07日

 

编写用于查询信息的Servlet

 

  3.2  编写用于查询信息的Servlet

  在本节建立的Servlet是这个例子的核心。这个Servlet负责接收客户端的请求消息,并通过请求消息从数据库中查询相应的信息,并将查询到的信息保存在request域中,以便负责显示查询结果的JSP页面读取并显示这些信息。下面通过IDE来建立一个Servlet程序。

  选中demo工程,在右键菜单中单击New | Servlet菜单项,出现Create Servlet对话框,并在Java package文本框中输入chapter3,在Class name文本框中输入QueryBook,如图3.1所示。

图3.1  Create Servlet对话框的第一步

  单击Next按钮进入下一步,在这一步不需要做任何修改,再次单击Next按钮进入Create Servlet对话框的第三步。选中service复选框,并取消Constructors from superclass复选择,如图3.2所示。

图3.2  【Create Servlet】对话框的第三步

  在进行完上面的设置后,单击Finish按钮建立Servlet.

  在生成的QueryBook.java文件中输入如下的代码:

  package chapter3;

  import java.io.*;

  import java.sql.*;

  import javax.servlet.*;

  import javax.servlet.http.*;

  import java.util.*;

  public class QueryBook extends HttpServlet

  {

  //  用于处理GET、POST等HTTP请求的方法

  protected void service(HttpServletRequest request,

  HttpServletResponse response) throws ServletException, IOException

  {

  try

  {

  //  获得queryField请求参数的值

  String queryField = request.getParameter("queryField")。toString();

  //  获得queryText请求参数的值

  String queryText = request.getParameter("queryText")。toString();

  //  为了解决乱码问题,必须进行编码转换

  queryText = new String(queryText.getBytes("ISO-8859-1"), "UTF-8");

  //  装载mysql的驱动

  Class.forName("com.mysql.jdbc.Driver");

  //  建立数据库连接,获得Connection对象

  Connection conn = DriverManager.getConnection(

  "jdbc:mysql://localhost/mydb?characterEncoding=UTF8",

  "root", "1234");

  //  使用带参数的SQL语句进行查询

  PreparedStatement pStmt = conn

  .prepareStatement("select * from t_books where "

  + queryField + " like ?");

  //  设置查询参数值

  pStmt.setString(1, "%" + queryText + "%");

  //  执行查询语句,并返回ResultSet对象

  ResultSet rs = pStmt.executeQuery();

  //  定义一个用于保存查询结果的List<String[]>对象

  List<String[]> result = new java.util.ArrayList<String[]>();

  //  循环处理查询结果

  while (rs.next())

  {

  String[] row = new String[4];

  row[0] = rs.getString("name");

  row[1] = rs.getString("author");

  row[2] = rs.getString("isbn");

  row[3] = rs.getString("price");

  //  将查询结果放到result对象中

  result.add(row);

  }

  pStmt.close();//  关闭PreparedStatement对象

  conn.close();//  关于Connection对象

  //  将查询结果保存在request域中,以便在显示查询结果的JSP页面中使用

  request.setAttribute("result", result);

  RequestDispatcher rd = request

  .getRequestDispatcher("/chapter3/result.jsp");

  //  转入result.jsp页面

  rd.forward(request, response);

  } catch (Exception e)

  {

  }

  }

  }

  在编写上面的代码时,应注意以下几点:

  1.  Servlet类必须从HttpServlet类及其子类继承。

  2.  service方法是HttpServlet类的一个方法,用来处理各种HTTP请求。关于这个方法的细节,将在第4章介绍。

  3.  在本程序中仍然使用在第2章建立的mydb数据库和t_books表。

  4.  在QueryBook类中读取了两个请求参数:queryField和queryText,这两请求参数分别代表查询的类别(书名、作者和ISBN)和查询的内容。其中queryField请求参数的可取值有三个:name、author和isbn,这三个值分别和数据库中的t_books表的字段相对应。这两个请求参数值将通过用于输入查询信息的JSP页面提供。

  5.  在读取queryText请求参数值后,又对其进行了编码转换,这是为了解决乱码问题。由于客户端可能提交中文信息,而提交的又是UTF-8编码,因此,需要将编码以UTF-8编码格式再转换成Java的内部编码格式。关于Java的乱码问题的系列结果方案,将在后面的内容详细讲解。

  6.  本程序采用了带参数的SQL语句进行查询,这样做可以有效地避免SQL注入攻击,也可提高程序的运行效率。

  7.  在程序的最后,将通过RequestDispatcher转入result.jsp页面,以显示查询结果。

  在本程序中所涉及到的技术,例如,转发Web资源,request域等,将在后面的章节详细介绍,在这里读者只要知道它们的功能即可。

  在建立QueryBook类的同时,IDE会自动在web.xml文件中添加如下的内容:

  <servlet>

  <!--  定义Servlet名字  -->

  <servlet-name>QueryBook</servlet-name>

  <!--  指定Servlet的类名  -->

  <servlet-class>chapter3.QueryBook</servlet-class>

  </servlet>

  <servlet-mapping>

  <servlet-name>QueryBook</servlet-name>

  <!--  指定访问Servlet的URL  -->

  <url-pattern>/QueryBook</url-pattern>

  </servlet-mapping>

  上面的配置代码对于Servlet是必须的,其主要内容就是通过一个Servlet名将Servlet类和访问Servlet的URL联系起来。也就是说,使用上面的配置,就可以通过URL来找到与之对应的Servlet类,并执行它。

 
 
 
 

第 3 章:Java Web程序的Helloworld作者:李宁    来源:希赛网    2014年03月07日

 

编写用于输出查询结果的JSP页面

 

  3.3  编写用于输出查询结果的JSP页面

  在这一节将建立一个用于显示查询结果的result.jsp页面。在IDE中建立JSP页面非常简单。在WebContent目录中建立一个chapter3目录,选中chapter3目录后,在右键菜单中单击New | JSP菜单项,打开New JavaServer Page对话框,在File name文本框中输入result.jsp,如图3.3所示。

图3.3  建立JSP页面

  在完成上面的操作后,单击Finish按钮建立result.jsp文件。打开result.jsp文件,并输入如下的代码:

  <%@ page language="java" contentType="text/html; charset=UTF-8"

  pageEncoding="UTF-8"%>

  <!--  引用JSTL的core标签库  -->

  <%@ taglib uri="http://java.sun.com/jsp/jstl/core"  prefix="c"%>

  <html>

  <head>

  <title>查询结果</title>

  </head>

  <body>

  <!--  以表格形式显示查询结果  -->

  <table border="1">

  <!--  显示表头  -->

  <tr align="center">

  <td>书名</td>

  <td>作者</td>

  <td>ISBN</td>

  <td>价格</td>

  </tr>

  <!--  使用JSTL读取查询结果  -->

  <c:forEach var="row" items="${result}">

  <tr>

  <td>${row[0]}</td>

  <td>${row[1]}</td>

  <td>${row[2]}</td>

  <td>${row[3]}</td>

  </tr>

  </c:forEach>

  </table>

  </body>

  </html>

  在上面的代码中使用了JSTL的core标签库。通过这个标签库中的<c:forEach>标签来从request域中读取查询结果,并动态生成HTML代码来显示查询结果。关于JSTL的内容将在后面的章节详细介绍。

  下面在IE地址栏中输入如下的URL来测试QueryBook类和result.jsp:

  http://localhost:8080/demo/QueryBook?queryField=name&queryText=ajax

  在访问上面的URL后,在IE中将显示如图3.4的输出结果。

图3.4  测试QueryBook的显示结果

 
 
 
 

第 3 章:Java Web程序的Helloworld作者:李宁    来源:希赛网    2014年03月07日

 

编写用于输入查询信息的JSP页面

 

  3.4  编写用于输入查询信息的JSP页面

  在本节将实现用于输入查询信息的query.jsp页面。在chapter3目录中建立一个query.jsp文件,并输入如下所示的代码:

  <%@ page language="java" contentType="text/html; charset=UTF-8"

  pageEncoding="UTF-8"%>

  <html>

  <head>

  <title>输入查询信息</title>

  </head>

  <body>

  <!--  通过form提交查询信息  -->

  <form action="/QueryBook" method="post">

  <!--  使用table来控制页面元素的位置  -->

  <table style="font-size: 14px">

  <tr>

  <td width="100px" align="right">

  选择查询项:

  </td>

  <td>

  <!--  显示查询项选择框  -->

  <select name="queryField" style="width:100px">

  <option value="name">书名</option>

  <option value="author">作者</option>

  <option value="isbn">ISBN</option>

  </select>

  </td>

  </tr>

  <tr>

  <td align="right">

  输入查询内容:

  </td>

  <td>

  <!--  显示查询内容文本框  -->

  <input type="text" name="queryText"/>

  </td>

  </tr>

  <tr>

  <td></td>

  <td>

  <input type="submit" value="查询"/>

  </td>

  </tr>

  </table>

  </form>

  </body>

  </html>

  在编写上面的代码时应注意如下几点:

  1.  通过form元素的action属性指定了"/QueryBook"来访问QueryBook.由于QueryBook的访问路径是/demo/QueryBook,而query.jsp的访问路径是/demo/chapter3/QueryBook,类此,需要action属性值需要加上""以加到上一层路径。

  2.  在<select>元素的<option>子元素中使用value属性指定queryField请求参数的值。也就是说,在QueryBook中获得的queryField请求参数值就是相应的<option>元素的value属性值。

  在IE地址栏中输入如下的URL来测试query.jsp页面:

  http://localhost:8080/demo/chapter3/query.jsp

  在访问上面的URL后,将在IE中显示如图3.5所示的界面:

图3.5  query.jsp页面

  在"选择查询项"下拉列表框中选择"作者",并在"输入查询内容"文本框中输入"布鲁克斯",如图3.6所示。

图3.6  输入查询信息

  在输入完查询信息后,单击"查询"按钮后,将会显示如图3.7所示的查询结果。

图3.7  显示查询结果

 
 
 
 

第 3 章:Java Web程序的Helloworld作者:李宁    来源:希赛网    2014年03月07日

 

小结

 

  3.5  小结

  本章简要介绍了JSP和Servlet技术的特点和优势,并以一个简单的信息查询程序作为例子来逐步演示如何在IDE中开发JSP和Servlet程序。在本章的例子中涉及到了很多JSP和Servlet中常用的技术,如JSTL、转发Web资源、request域等。这些知识和技术都将在后面的章节详细介绍。而本章的目的就是使读者了解开发Java Web程序的基本步骤和流程。

 

 

 

 

 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

Web程序

 

  第4章  Servlet开发基础

  在本章将介绍Servlet的一些基础知识。由于Servlet必须运行在Web服务器中,因此,在本章介绍了如何在Tomcat中配置Servlet以及数据库连接池的配置。除此之外,在本章还着重介绍了三个Servlet API,它们是HttpServlet类、ServletConfig接口和ServletContext接口。其中HttpServlet类是Servlet的核心,所有的Servlet类都要从这个HttpServlet类继承。

  4.1  在Tomcat中的配置Web程序

  在本节将介绍Java Web程序的一些基本配置方法。主要涉及到如何配置web.xml文件,以及如何在Tomcat中配置数据库连接池。在第3章给出了一个例子来演示开发Java Web程序的过程,这个例子是通过IDE进行开发的,虽然这种方式可以大大提高程序开发的效率,但却将某些步骤隐藏了起来。这对于初学者来说并不利于充分理解Java Web程序开发的全过程,因此,在本节还给出了一个例子来介绍如何脱离IDE来开发Java Web程序。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

编写web.xml文件

 

  4.1.1  编写web.xml文件

  在Web服务器中运行Servlet的部分被称为Servlet容器。Servlet要想在Servlet容器中正常运行,必须要使用web.xml(在WEB-INF目录中)文件进行配置(虽然使用Java IDE在大多数情况下是不需要手工配置web.xml的,但理解和掌握web.xml的常用配置将会有助于更进一步学习Java Web技术)。web.xml是一个标准的XML格式文件。下面是一个标准的web.xml配置文件的内容:

  <?xml version="1.0" encoding="UTF-8"?>

  <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

  id="WebApp_ID" version="2.5">

  <welcome-file-list>

  <welcome-file>index.html</welcome-file>

  <welcome-file>default.jsp</welcome-file>

  </welcome-file-list>

  <servlet>

  <description></description>

  <display-name>QueryBook</display-name>

  <servlet-name>QueryBook</servlet-name>

  <servlet-class>chapter3.QueryBook</servlet-class>

  </servlet>

  <servlet-mapping>

  <servlet-name>QueryBook</servlet-name>

  <url-pattern>/QueryBook</url-pattern>

  </servlet-mapping>

  </web-app>

  在上面的web.xml文件中有四个主要的元素:

  1.  <web-app>:最顶层的元素。所有的web.xml文件都必须拥有这个元素。<web-app>主要描述了当前使用的Servlet的版本以及其他一些文档类型声明,如上面的web.xml文件中描述了Servlet的版本是2.5

  2.  <servlet>:用于定义和Servlet相关的信息,这个元素含有4个子元素:

  (1)<servlet-class>:用来定义Servlet和哪一个具体的类对应,如本例中定义的是chapter3.QueryBook.

  (2)<servlet-name>:用于定义Servlet的唯一标识(也就是Servlet名)。如本例中定义了QueryBook.<servlet>元素可以有多个,但是每个<servlet>的<servlet-name>元素的值不能重复,否则Tomcat在启动时会抛出异常。

  (3)<description>:该元素提供了用于描述Servlet的文本信息。

  (4)<display-name>:该元素提供了一些GUI工具可以显示的Servlet的文本信息。

  3.  <serlvet-mapping>:该元素一般和<servlet>元素成对出现。用于将Servlet映射成用户可访问的Web路径。其中<url-pattern>定义了可访问的Web路径,但要注意,这个Web路径必须以"/"开头,否则Tomcat在启动时会抛出异常。在第3章访问QueryBook的URL是http://localhost:8080/demo/QueryBook.而在<url-pattern>中定义的就是/QueryBook部分。当然,也可以将其定义成其他的形式,甚至可以将其模拟成其他语言的Web程序,如将<url-pattern>元素的值设为如下形式:

  <url-pattern>/abc.php</url-pattern>

  在IE地址栏中只要输入http://localhost:8080/demo/abc.php就可以访问QueryBook了。<url-pattern>中的<servlet-name>与<servlet>中的<servlet-name>完全一样,表示当前的<servlet-mapping>要映射的Servlet名。<servlet-mapping>和<servlet>是多对一的关系。也就是说,多个<servlet-mapping>可以对应一个<servlet>,这样就可以为一个Servlet定义多个可访问的Web路径。如下面的配置代码所示:

  <servlet>

  <servlet-name>QueryBook</servlet-name>

  <servlet-class>chapter3.QueryBook</servlet-class>

  </servlet>

  <servlet-mapping>

  <servlet-name>QueryBook</servlet-name>

  <url-pattern>/QueryBook</url-pattern>

  </servlet-mapping>

  <servlet-mapping>

  <servlet-name>QueryBook</servlet-name>

  <url-pattern>/querybook.abc</url-pattern>

  </servlet-mapping>

  如果使用上面的配置,就可以同时通过如下两个URL来访问QueryBook:

  http://localhost:8080/demo/QueryBook

  http://localhost:8080/demo/querybook.abc

  4.  <webcome-file-list>:该元素其实就相当于IIS中的默认页。也就是说,如果在浏览器中只访问http://localhost:8080/demo,而不指定具体的Servlet或其他Web资源的路径,系统会自动访问<webcome-file-list>元素中<webcome-file>子元素所指定的文件或Web路径。要注意的是,<webcome-file>元素只能是相对于当前Web工程的相对路径,不能是绝对路径,如http://www.sina.com.cn是不合法的。<webcome-file>元素的值可以是任何形式的相对路径,但前面不能加"/",这一点和<url-pattern>元素恰恰相反。如<webcome-file>元素的值可以是"index.jsp",但不能是"/index.jsp",否则将无法访问。<webcome-file-list>元素可以有多个<webcome-file>子元素,如果第一个<webcome-file>元素所指的相对路径无法访问,系统就会访问第二个<webcome-file>元素所指的相对路径,以此类推。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

手工编写Servlet

 

  4.1.2  手工编写Servlet

  在本节将给出一个如何通过手工方式编写Servlet的例子。这个例子完全脱离IDE,只使用记事本和Java编译器来完成Servlet的编写、编译和发布工作。

  【实例4-1】  手工编写Servlet

  实现本示例的步骤如下:

  1.  建立目录结构

  在编写Servlet之前,需要建立Servlet所在的目录结构。读者可按如下3步来建立Servlet目录结构。

  (1)在<Tomcat安装目录>\webapps目录中建立一个mydemo目录

  (2)在<Tomcat安装目录>\webapps\mydemo目录中建立一个WEB-INF目录。

  (3)在<Tomcat安装目录>\webapps\mydemo\WEB-INF目录中建立一个classes目录。

  2.  编写Servlet类

  在<Tomcat安装目录>\webapps\mydemo目录中建立一个MyDoGet.java文件,代码如下:

  package chapter4;

  import java.io.*;

  import javax.servlet.*;

  import javax.servlet.http.*;

  publi

  public class MyDoGet extends HttpServlet

  {

  //  只处理HTTP GET请求

  public void doGet(HttpServletRequest request, HttpServletResponse response)

  throws ServletException, IOException

  {

  //  设置Content-Type字段值

  response.setContentType("text/html;charset=UTF-8");

  PrintWriter out = response.getWriter();

  //  向客户端输出信息

  out.println("doGet方法被调用!");

  }

  }

  上面的代码使用HttpServletResponse接口的getWriter方法获得了一个PrintWriter对象,用来向客户端输出文本信息。并通过HttpServletResponse接口的setContextType方法设置了HTTP响应头的Content-Type字段值。

  3.  编译Servlet类

  编译MyDoGet类需要一个servlet-api.jar文件,这个文件可以在<Tomcat安装目录>\lib目录中找到,为了方便,可以将这个文件复制到<Tomcat安装目录>\webapps\mydemo目录中。然后打开"Windows控制台",并进入<Tomcat安装目录>\webapps\mydemo目录,然后输入如下的命令来编译MyDoGet.java:

  javac -classpath .;servlet-api.jar  -d WEB-INF/classes  MyDoGet.java

  在成功执行上面的命令后,读者将会在<Tomcat安装目录>\webapps\mydemo\WEB-INF\classes\chapter4目录中看到一个MyDoGet.class文件。

  4.  配置Servlet类

  这是手工编写Servlet程序的最后一步。在<Tomcat安装目录>\webapps\mydemo\WEB-INF目录中建立一个web.xml文件,并输入如下的内容:

  <?xml version="1.0" encoding="UTF-8"?>

  <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

  id="WebApp_ID" version="2.5">

  <!--  开始配置MyDoGet  -->

  <servlet>

  <servlet-name>MyDoGet</servlet-name>

  <servlet-class>chapter4.MyDoGet</servlet-class>

  </servlet>

  <servlet-mapping>

  <servlet-name>MyDoGet</servlet-name>

  <url-pattern>/ MyDoGet</url-pattern>

  </servlet-mapping>

  </web-app>

  上面的配置代码中的开头部分(尤其是<web-app>标签的属性)很复杂,不过读者并不需要记这些东西,只需要找一个已经配置完的Java Web程序的例子,将web.xml文件中的相关内容复制过来即可。如在Tomcat中提供了一些Servlet的例子,读者可以在<Tomcat安装目录>\webapps\examples\WEB-INF目录找到一个已经配置完的web.xml文件。

  在完成上面的几步后,可以通过<Tomcat安装目录>\bin\startup.bat命令来启动Tomcat,然后在IE地址栏中输入如下的URL来测试MyDoGet:

  http://localhost:8080/mydemo/MyDoGet

  在访问上面的URL后,将在IE中输出如图4.1所示的信息。

图4.1  MyDoGet的输出结果

  5.  程序总结

  在本例中将程序目录放在了<Tomcat安装目录>\webapps目录中,实际上,这是最简单的发布Java Web程序的方式。读者也可以将程序目录放在任何位置,如将mydemo目录放在D盘的根目录,然后打开<Tomcat安装目录>\conf\server.xml文件,找到<Host>元素,并使用<Context>子元素来发布程序,配置代码如下:

  <Host name="localhost"  appBase="webapps" unpackWARs="true" autoDeploy="true">

  … …

  <Context path="/newdemo" docBase="d:\mydemo" debug="0" />

  … …

  </Host>

  重新Tomcat后,可以通过http://localhost:8080/newdemo/MyDoGet来访问MyDoGet.在<Context>元素中,path属性表示Web程序的上下文路径,如果path属性值为空串,则表示Web站点的根目录。如下面的配置代码所示:

  <Host name="localhost"  appBase="webapps" unpackWARs="true" autoDeploy="true">

  … …

  <Context path="" docBase="d:\mydemo" debug="0" />

  … …

  </Host>

  如果使用上面的配置代码,可以通过http://localhost:8080/ MyDoGet来访问MyDoGet.要注意的是,要想设置Web程序的上下文路径为Web站点的根目录,path属性值必须为空串,而不能为"/".

  <Context>元素的docBase属性表示一个在磁盘上的实际存在的Web工程目录(可以是相对路径,也可以是绝对路径),或是一个*.war文件(也就是war包)。 如果docBase属性值是相对路径,那么这个路径将相对<Host>元素的appBase属性值所指的目录而言。在本例中,appBase属性值所指向的是<Tomcat安装目录>\webapps在<Host>元素中的unpackWARs属性值如果为true,所有放到webapps目录中的war包在发布时都会自动解压。而autoDeploy属性值如果为true,Tomcat在不重启的情况下,所复制到webapps目录中的Web工程目录或war包都会自动发布。

  除了可以将<Context>作为<Host>的子元素外,还可以将<Context>元素提出来放到xml文件中。这些xml文件必须被放到<Tomcat安装目录>\conf\<引擎名>\<主机名>中。在Tomcat中,<引擎名>为Catalina,<主机名>就是<Host>元素中name属性的值,也就是localhost.因此,xml文件的存放目录为<Tomcat安装目录>\conf\Catalina\localhost.而且xml文件名就是上下文路径名,而<Context>目录的path属性将失效。如将hello.xml文件放到<Tomcat安装目录>\conf\Catalina\localhost目录中,内容如下:

  <Context path="" docBase="d:\mydemo" debug="0" />

  在IE地址栏中输入http://localhost:8080/hello/MyDoGet,就可以访问MyDoGet了。

  在使用<Context>发布Web程序时应注意以下两点:

  (1)<Context>元素必须是<Host>的子元素。

  (2)<Context>元素在<Host>中可以存在多个,但每个<Context>元素中的path属性的值不能有重复,否则Tomcat在启动时将出现异常。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

配置数据库连接池

 

  4.1.3  配置数据库连接池

  由于基于HTTP协议的Web程序是无状态的,因此,在应用程序中使用JDBC时,每次处理客户端请求时都会重新建立数据库连接。如果客户端的请求非常频繁,服务端在处理数据库时将会消耗非常多的资源。因此,在Tomcat中提供了数据库连接池技术。数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个数据库连接。在使用完一个数据库连接后,将其归还数据库连接池,以备其他程序使用。

  在Tomcat中配置数据库连接池有两种方法:

  1.  配置全局数据库连接池

  (1)打开<Tomcat安装目录>\conf\server.xml文件,并从中找到<GlobalNamingResources>元素,然后加入一个子元素<Resource>,这个子元素的配置代码如下:

  <Resource name="jdbc/mydb" auth="Container"

  type="javax.sql.DataSource"

  driverClassName="com.mysql.jdbc.Driver"

  url="jdbc:mysql://localhost:3306/mydb?characterEncoding=UTF8"

  username="root"

  password="1234"

  maxActive="200"

  maxIdle="50"

  maxWait="3000"/>

  上面的配置代码有几个和数据库连接池性能有关的属性需要说明一下:

  maxActive:连接池可以存储的最大连接数,也就是应用程序可以同时获得的最大连接数。这个属性值一般根据Web程序的最大访问量设置。

  maxIdle:最大空闲连接数。当应用程序使用完一个数据库连接后,如果连接池中存储的连接数小于maxIdle,这个数据库连接并不马上释放,而是仍然存储在连接池中,以备其他程序使用。这个属性值一般根据Web程序的平均访问量设置。

  maxWait:暂时无法获得数据库连接的等待时间(单位:毫秒)。如果Web程序从数据库连接池中获得的数据库连接数已经等于maxActive,而且都没有归还给连接池,这时再有程序想获得数据库连接,就会等待maxWait所指定的时间。如果超过maxWait所指定的时间还无法获得数据库连接,就会抛出异常。

  (2)在<Tomcat安装目录>\conf\Catalina\localhost中建立一个demo.xml文件(文件名要和path属性值一致),然后输入如下内容:

  <Context path="/demo" docBase="demo" debug="0">

  <ResourceLink name="jdbc/mydb" global="jdbc/mydb" type="javax.sql.DataSource"/>

  </Context>

  2.  配置局部数据库连接池

  在<Tomcat安装目录>\conf\Catalina\localhost中建立一个demo.xml文件,然后输入如下内容:

  <Context path="/demo" docBase="demo" debug="0">

  <Resource name="jdbc/mydb" auth="Container"

  type="javax.sql.DataSource"

  driverClassName="com.mysql.jdbc.Driver"

  url="jdbc:mysql://localhost:3306/mydb?characterEncoding=UTF8"

  username="root"

  password="1234"

  maxActive="200"

  maxIdle="50"

  maxWait="3000"/>

  </Context>

  在配置完数据库连接池后,可以在Servlet的service方法或其他处理HTTP请求的方法中使用如下代码来连接数据库:

  javax.naming.Context ctx = new javax.naming.InitialContext();

  //  获得DataSource对象

  javax.sql.DataSource ds = (javax.sql.DataSource)

  ctx.lookup("java:/comp/env/jdbc/mydb");

  //  获得Connection对象

  Connection conn = ds.getConnection();

  在获得Connection对象后的操作就和不使用数据库连接池操作数据库是一样的了。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

Generic Servlet与Http Servlet类

 

  4.2  Generic Servlet与Http Servlet类

  Generic Servlet类封装了Servlet的基本特征和功能,该类在javax.servlet包中。Http Servlet类是Generic Servlet的子类,也在javax.servlet包中,该类提供了处理HTTP协议的基本架构。如果Servlet想充分利用HTTP协议的功能,就应该从Http Servlet类继承。Generic Servlet类实现了Servlet和Servlet Config接口。在继承Http Servlet的Servlet类中不仅可以使用HttpServlet类中提供的方法,而且还可以使用在Servlet、Servlet Config接口和GenericServlet类中定义的一些方法。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

service方法

 

  4.2.1  service方法

  service方法是Servlet接口的方法,该方法负责处理客户端的所有HTTP请求。service方法是在Servlet接口中定义的一个方法,该方法在GenericServlet中定没有实现,而是在HttpServlet类中实现的这个方法。service方法的定义如下:

  public void service(ServletRequest req, ServletResponse res)

  throws ServletException, IOException;

  由于service方法的两个参数类型分别是ServletRequest和ServletResponse,因此,这两个参数并没有处理HTTP消息的特殊功能。为了在service方法中处理HTTP消息,需要使用HttpServletRequest和HttpServletResponse接口中定义的方法。所以在service方法中需要分别将ServletRequest和ServletResponse类型的参数转换成HttpServletRequest和HttpServletResponse,代码如下:

  public void service(ServletRequest req, ServletResponse res)

  throws ServletException, IOException

  {

  HttpServletRequest request = (HttpServletRequest)req;

  HttpServletResponse response = (HttpServletResponse)res;

  response.getWriter()。println("test");

  … …

  }

  为了简化这一过程,在HttpServlet中又提供了另一个service方法的重载形式,代码如下:

  protected void service(HttpServletRequest req, HttpServletResponse resp)

  throws ServletException, IOException;

  从上面的代码可以看出,这个重载形式的参数类型是HttpServletRequest和HttpServletResponse,这个重载形式被第一个service方法的重载形式调用,代码如下:

  public void service(ServletRequest req, ServletResponse res)

  throws ServletException, IOException

  {

  HttpServletRequest  request;

  HttpServletResponse  response;

  try

  {

  request = (HttpServletRequest) req;

  response = (HttpServletResponse) res;

  }

  catch (ClassCastException e)

  {

  throw new ServletException("non-HTTP request or response");

  }

  service(request, response);

  }

  如果在Servlet类中覆盖了service方法的第二个重载形式,那么在service方法中就无需再进行两个参数的类型转换了,代码如下:

  public void service(HttpServletRequest req, HttpServletResponse res)

  throws ServletException, IOException

  {

  res.getWriter()。println("test");

  … …

  }

  实际上,虽然service的第二个重载形式可以给开发人员带来方便,但这个方法并不是Servlet接口中定义的方法。在Servlet接口中只定义了service的第一个重载形式。因此,Servlet引擎在调用时只会调用service方法的第一个重载形式。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

doXxx方法

 

  4.2.2  doXxx方法

  在Servlet类中除了可以使用service方法来处理HTTP请求外,也可以使用doXxx方法来处理某一个指定的HTTP方法的请求,如doGet方法可以处理HTTP GET请求,doPost方法可以处理HTTP POST请求。这些doXxx方法都是在HttpServlet类中定义的,在HttpServlet类中定义的doXxx方法如下:

  doGet:用于处理HTTP GET请求。

  doPost:用于处理HTTP POST请求。

  doHead:用于处理HTTP HEAD请求。

  doPut:用于处理HTTP PUT请求。

  doDelete:用于处理HTTP DELETE请求。

  doTrace:用于处理HTTP TRACE请求。

  doOptions:用于处理HTTP OPTIONS请求。

  doXxx的使用方法和service方法完全一样,所不同的是service方法可以处理所有的HTTP请求,而doXxx方法只能处理特定的HTTP请求。对于只需要处理某些HTTP方法的请求的Servlet类,可以使用相应的doXxx方法,代码如下:

  //  处理HTTP POST请求

  public void doPost(HttpServletRequest request, HttpServletResponse response)

  throws ServletException, IOException

  {

  response.getWriter()。println("test");

  … …

  }

  在一般情况下,Servlet只需要处理HTTP GET和HTTP POST请求,因此,只需要覆盖doGet和doPost方法即可。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

init和destroy方法

 

  4.2.3  init和destroy方法

  init和destroy方法分别在Servlet容器建立Servlet对象和销毁Servlet对象时调用。而且这两个方法只在Servlet的生命周期里调用一次。在Servlet接口中定义了这两个方法,在GenericServlet类中提供了这两个方法的默认实现。init方法有一个ServletConfig类型的参数,可以通过这个参数获得配置信息(也就是在web.xml文件中配置的内容),关于ServletConfig接口的内容将在4.4节详细介绍。destroy方法一般用于在Servlet对象被销毁时释放一些全局的资源,如数据库连接、网络资源等。

  init方法和destroy方法的定义如下:

  public void init(ServletConfig config) throws ServletException;

  public void destroy();

  有很多开发人员在编写Servlet类时往往直接覆盖了init方法来完成初始化工作。这么做一般没什么问题。但却将GenericServlet中init方法的默认实现覆盖了。先看看GenericServlet类中相关方法的实现代码:

  public abstract class GenericServlet

  implements Servlet, ServletConfig, java.io.Serializable

  {

  private transient ServletConfig config;

  //  获得ServletConfig对象

  public ServletConfig getServletConfig()

  {

  return config;

  }

  //  实现Servlet接口中的init方法

  public void init(ServletConfig config) throws ServletException

  {

  this.config = config;

  this.init();

  }

  //  GenericServlet类中提供了空参数的init方法

  public void init() throws ServletException

  {

  }

  }

  从上面的代码可以看出,带参数的init方法的第1行将config参数值赋给了类变量config.并且getServletConfig方法是通过config变量来返回ServletConfig对象的。如果开发人员覆盖了带参数的init方法,而又未调用super.init(config)语句,那么通过getServletConfig方法就无法再获得ServletConfig对象了

  虽然开发人员也可以在Servlet类的init方法中将ServletConfig对象保存起来,但这实在是多此一举。当然,如果即不调用super.init(config)语句,也不保存ServletConfig对象,那么这个ServletConfig对象将会丢失,也就是说,在service、doXxx等方法中将无法获得并使用ServletConfig对象了。为了避免这个尴尬,在GenericServlet类中提供了一个不带参数的init方法,并且在带参数的init方法中调用该init方法,如上面的代码所示。

  如果开发人员在Servlet类中覆盖这个不带参数的init方法,那么就仍然可以通过getServletConfig方法来获得ServletConfig对象。因此,笔者建议尽量在Servlet类中覆盖不带参数的init方法来初始化Servlet,如果要在init方法中使用ServletConfig对象,可以使用getServletConfig方法来获得ServletConfig对象。

  下面的例子演示了init和destroy方法在Servlet的生命周期中的调用情况。

  【实例4-2】  init和destroy方法调用演示

  1.  实例说明

  在本例中的Servlet类中覆盖了init和destroy方法。在第一次访问Servlet时,将调用init方法。然后通过重新发布Servlet的方式使Servlet引擎调用该Servlet类的destroy方法。然后再次访问这个Servlet,Servlet引擎再次调用init方法。最后通过停止Tomcat的方式使Servlet引擎再次调用destroy方法。也就是说,在本例中,Servlet引擎会调用两次init方法和两次destroy方法。

  2.  编写Servlet类

  本例中Servlet类的实现代码如下:

  public class InitDestroyServlet extends HttpServlet

  {

  //  覆盖无参数的init方法

  public void init() throws ServletException

  {

  System.out.println("init方法被调用!");

  }

  //  覆盖destroy方法

  public void destroy()

  {

  System.out.println("destroy方法被调用");

  }

  //  处理客户端的HTTP请求

  protected void service(HttpServletRequest request,

  HttpServletResponse response) throws ServletException, IOException

  {

  response.setContentType("text/html;charset=UTF-8");

  response.getWriter()。println("测试init和destroy方法");

  }

  }

  3.  测试程序

  通过测试来观察init和destroy方法的调用情况在IE地址栏中输入如下的URL:

  http://localhost:8080/demo/InitDestroyServlet

  这时在控制台中将输出"init方法被调用!"的信息。这时在Eclipse中稍微修改一下InitDestroyServlet类(可以加一个空格,并保存),这时Eclipse会自动重新发布Web应用,在控制台中就会输出"destroy方法被调用"信息。在等待Web应用重新发布成功后,再次访问上面的URL,并且在Eclipse中停止Tomcat,这时,Servlet引擎会再次调用Servlet类的destroy方法。以上测试过程输出的信息如图4.2所示。

图4.2  init和destroy方法的调用情况

  4.  程序总结

  从本例可以看出,Servlet类的destroy方法会在Web应用重新发布时,或Web服务端(如Tomcat)停止时调用。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

getServletConfig方法

 

  4.2.4  getServletConfig方法

  getServletConfig方法用于获得ServletConfig对象,这个对象是通过带参数的init方法传入GenericServlet对象的。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

getServletInfo方法

 

  4.2.5  getServletInfo方法

  该方法用于返回Servlet的描述信息,这些信息可以是Servlet的作者,版本、版权信息等。在默认情况下,这个方法返回空串。开发人员可以覆盖这个方法来返回有意义的信息。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

getLastModified方法

 

  4.2.6  getLastModified方法

  HTTP响应消息头有一个Last-Modified字段,这个字段表示服务器内容最新修改时间。如果请求消息头中包含If-Modificed-Since字段,并且该字段的时间比Last-Modified字段的时间早。或是请求消息头中没有If-Modificed-Since字段。service方法就会调用doGet方法来重新获得服务端内容。但这有一个前提,就是getLastModified方法必须返回一个正数。但在默认情况下,getLastModified方法返回-1.因此,service方法调用用doGet方法的规则如下:

  当getLastModified返回-1时,service方法总会调用doGet方法。

  当getLastModified返回正数时,如果HTTP请求消息头中没有If-Modified-Since字段,或者If-Modified-Since字段中的时间比Last-Modified字段中的时间早,service方法会调用doGet方法。浏览器在下次访问该Servlet时,If-Modified-Since字段的值就是上一次访问该Servlet的Last-Modified字段的值。

  当getLastModified方法返回正数时,如果If-Modified-Since字段中的时间比Last-Modified字段中的时间晚,或者这两个字段的时间相同,service将不会调用doGet方法,而是向浏览器反回一个304(Not Modified)状态码来通知浏览器继续使用以前缓冲过的内容。

  下面的例子演示了如何通过getLastModified方法控制浏览器是否使用被缓存的内容。

  【实例4-3】  用getLastModified方法控制浏览器使用缓冲内容

  1.  实例说明

  本程序通过使getLastModified方法返回不同的值来决定service方法是否执行doGet方法。如果service方法不执行doGet方法,虽然Servlet被成功调用,但是并没有执行doGet方法,因此,Servlet并没有返回新的服务端内容。

  2.  编写Servlet类

  在CacheServlet类中覆盖了getLastModifed方法,并返回了当前的时间(以毫秒为单位),代码如下:

  public class CacheServlet extends HttpServlet

  {

  //  覆盖HttpServlet类的getLastModified方法

  protected long getLastModified(HttpServletRequest req)

  {

  ong now = System.currentTimeMillis();

  //  返回当前时间

  return System.currentTimeMillis();

  }

  protected void doGet(HttpServletRequest request,

  HttpServletResponse response) throws ServletException, IOException

  {

  response.getWriter()。println(System.currentTimeMillis());

  System.out.println(request.getHeader("if-Modified-Since"));

  }

  }

  3.  测试程序

  通过测试程序来观察客户端输出的时间是否变化。在IE地址栏中输入如下的URL:

  http://localhost:8080/demo/CacheServlet

  读者会看到在IE中输出当前服务器的时间(毫秒值)。当使用F5不断刷新页面时,会看到这个值也在不断地变化。从而可以断定,CacheServlet的doGet方法被调用了。现在将服务器的时间往回调整一下(如调整到前一天),再次按F5刷新,这时页面的毫秒时间就不会再发生变化了。这是因为目前的服务器时间比HTTP请求消息头的If-Modifed-Since字段值指定的时间早,因此,service就不会调用doGet方法了,当然也就不会输出当前的服务器时间了。实际上,浏览器使用的是被缓存的内容。

  读者可以从IE的缓存目录(C:\Documents and Settings\Administrator\Local Settings\Temporary Internet Files)找到CacheServlet,并将其删除,再次按F5刷新页面。就会看到页面的时间变化了,当再次按F5时,又不变化了。这是因为当把IE的相关缓存删除后,由于IE找不到缓存内容,因此,无法设置HTTP请求消息头的If-Modified-Since段,这也正符合上述第二个规则中调用doGet方法的条件。因此,第一次刷新页面时CacheServlet返回了时间信息,当再次刷新页面时,则CacheServlet又被缓存了,所以当再次发送HTTP请求时,If-Modifed-Since字段中的时间和Last-Modified字段中的时间相等,因此,service方法会返回304状态码,这时IE就会使用被缓存的CacheServlet.

  4.  程序总结

  在service方法中只有doGet方法考虑了If-Modified-Since和Last-Modified字段,其他的方法,如doPost,并不涉及到这两个字段,因此,除了doGet方法,其他的doXxx方法总会被调用。

  对于需要实时获得返回结果的Servlet,笔者并不建议覆盖getLastModified方法。因为如果是这样,浏览器可能会在一定时间内使用浏览器缓存的内容。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

ServletConfig接口

 

  4.3  ServletConfig接口

  在发布Servlet时除了要配置必须的Servlet名、Servlet类名和访问Servlet的URL外,有时还会配置一个或多个Servlet初始化参数。Servlet引擎将这些和Servlet相关的信息都封装在了ServletConfig对象中。通过ServletConfig接口的getter方法,可以很容易地获得与Servlet相关的配置信息。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

getInitParameterNames方法

 

  4.3.1  getInitParameterNames方法

  在web.xml文件中可以为Servlet设置多个初始化参数。可以通过getInitParameterNames方法来获得这些初始化参数的名称。该方法返回一个Enumeration对象,初始化参数的名称可以从Enumeration对象中获得。假设一个名为MyServletConfig的Servlet的配置代码如下:

  <servlet>

  <servlet-name>MyServletConfig</servlet-name>

  <servlet-class>chapter4.MyServletConfig</servlet-class>

  <!--  配置Servlet初始化参数  -->

  <init-param>

  <param-name>product</param-name>

  <param-value>洗衣机</param-value>

  </init-param>

  <init-param>

  <param-name>price</param-name>

  <param-value>300</param-value>

  </init-param>

  </servlet>

  通过getInitParameterNames方法返回的Eenumeration对象中就包含了上面代码中的两个初始化参数名称:product和price。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

getInitParameter方法

 

  4.3.2  getInitParameter方法

  getInitParameter方法用于根据初始化参数名称返回web.xml文件中初始化参数的值。如果指定的初始化名称不存在,则返回null.如通过上面代码中初始化参数名称product,getInitParameter方法可以得到与其对应的初始化参数值"洗衣机"。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

getServletName方法

 

  4.3.3  getServletName方法

  get Servlet Name方法用于返回Servlet在web.xml文件中配置的名称,如上面的代码中按着MyServletConfig的配置,在MyServlet Config中通过该方法所获得的Servlet名称就是My Servlet Config。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

get Servlet Context方法

 

  4.3.4  get Servlet Context方法

  每一个Java Web应用程序都用一个ServletContext对象来表示,通过ServletConfig接口的getServletContext方法可以得到ServletContext对象。从ServletContext对象中可以获得更丰富的与Web相关的信息。ServletContext实际上也是一个接口,关于该接口的信息,将在4.4节详细介绍。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

Servlet Context接口

 

  4.4  Servlet Context接口

  Servlet Context对象表示一个Web应用程序。Servlet Context接口定义了很多方法。通过这些方法,Servlet可以和Servlet容器进行通讯。Servlet引擎会为每一个Web应用程序创建一个Servlet Context对象,Servlet Context对象被包含在Servlet Config对象中,通过Servlet Config接口的getServlet Context方法可以获得Servlet Context对象。与Servlet API中的其他接口一样,Servlet引擎也为Servlet Context接口提供了默认的实现。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

获取Web应用程序的初始化参数

 

  4.4.1  获取Web应用程序的初始化参数

  在server.xml文件和web.xml文件中都可以设置Web应用程序的初始化参数。通过设置Web应用程序的初始化参数,可以在不需要修改程序的前提下,改变Web应用程序的某些设置。如一个Web应用程序可能不只运行在一家公司,如果将该程序部署在某一家公司,而且公司名称被设置成为Web应用程序的初始化参数。这时直接修改初始化参数就可以将公司名设置成这家公司的名称。

  在ServletContext接口中定义了getInitParameter方法和getInitParameterNames方法来访问Web应用程序的初始化参数,其中getInitParameter方法可以通过初始化参数名获得参数值,而getInitParameterNames可以获得保存所有初始化参数名的Enumeration对象。

  在web.xml文件中配置Web应用程序的初始化参数需要使用<context-param>元素,下面是一个在web.xml文件中配置初始化参数的例子:

  <web-app  … >

  <context-param>

  <param-name>companyName</param-name>

  <param-value>Sun公司</param-value>

  </context-param>

  … …

  </web-app>

  如果想在server.xml文件中配置Web应用程序的初始化参数,需要在当前Web应用程序的<Context>元素中使用<Parameter>子元素来配置初始化参数,代码如下:

  <Context docBase="demo" path="/demo" reloadable="true"

  source="org.eclipse.jst.jee.server:demo">

  <!--  配置Web应用程序的初始化参数  -->

  <Parameter name = "myParam" value = "newValue " override="true" />

  </Context>

  其中override属性值如果为true,表示web.xml文件中的初始化参数可以覆盖server.xml文件中的同名初始化参数,也就是说,当override属性为true时,如果web.xml文件和server.xml文件中有同名的初始化参数,以web.xml文件中的初始化参数为准。当override属性为false时,以server.xml文件中的同名初始化参数为准。override属性的默认值是true.

  【实例4-4】  读取Web应用程序的初始化参数

  1.  配置Web应用程序的初始化参数

  由于本书使用的IDE是Eclipse IDE for Java EE,这个IDE使用了自己的server.xml文件,因此,如果在该IDE中测试本例的程序,不能在<Tomcat安装目录>\conf\server.xml文件中设置Web应用程序的初始化参数,而应该在IDE所使用的server.xml文件中设置这些参数。

  如果在Eclipse IDE for Java EE配置了Tomcat作为Web服务器,那么会在Project Explorer页中添加一个Servers工程。其中该IDE所使用的server.xml文件就在其中的Tomcat v6.0 Server-config节点中,如图4.3所示。

 

图4.3  server.xml文件的位置

  双击server.xml打开该文件后,找到如下的配置代码:

  <Context docBase="demo" path="/demo" reloadable="true"

  source="org.eclipse.jst.jee.server:demo"/>

  将上面的配置代码修改成下面的形式:

  <Context docBase="demo" path="/demo" reloadable="true"

  source="org.eclipse.jst.jee.server:demo">

  <Parameter name = "myParam" value = "newValue" override = "false" />

  <Parameter name = "myParam1" value = "newValue1" override = "true" />

  </Context>

  下面来配置web.xml中的初始化参数,打开web.xml文件,在<web-app>元素中添加如下的配置代码:

  <!--  配置第一个Web应用程序的初始化参数  -->

  <context-param>

  <param-name>companyName</param-name>

  <param-value>Sun公司</param-value>

  </context-param>

  <!--  配置第二个Web应用程序的初始化参数  -->

  <context-param>

  <param-name>myParam</param-name>

  <param-value>myParamValue</param-value>

  </context-param>

  在server.xml和web.xml文件中有一个同名的初始化参数myParam,由于在server.xml文件中的<Parameter>元素的override属性值为false,因此,使用getInitParameter方法读出来的是在server.xml文件中配置的参数值newValue.

  2.  编写ContextParamServlet类

  在ContextParamServlet类中通过getInitParameterNames方法得到保存所有初始化参数名的Enumeration对象,并逐个扫描初始化参数名,并通过getInitParameter方法获得相应的初始化参数值。ContextParamServlet类的代码如下:

  public class ContextParamServlet extends HttpServlet

  {

  protected void service(HttpServletRequest request,

  HttpServletResponse response) throws ServletException, IOException

  {

  response.setContentType("text/html; charset=UTF-8");

  PrintWriter out = response.getWriter();

  out.println("Web应用程序的初始化参数列表<p/>");

  ServletContext context = getServletContext();

  //  获得所有的初始化参数名称

  Enumeration<String> params = context.getInitParameterNames();

  while(params.hasMoreElements())

  {

  //  获得当前初始化参数名

  String key = params.nextElement();

  //  获得当前初始化参数值

  String value = context.getInitParameter(key);

  out.println(key + "&nbsp;=&nbsp;" + value + "<br/>");

  }

  }

  }

  3.  测试ContextParamServlet类

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/ContextParamServlet

  浏览器显示的结果如图4.4所示。

 

图4.7  Web应用程序的初始化参数列表

  4.  程序总结

  从图4.4的显示结果可以看出,使用getInitParameterNames方法获得的初始化参数列表既包括在server.xml文件中配置的初始化参数,也包括在web.xml文件中配置的初始化参数。其中myParam1参数只在server.xml文件中配置,并未在web.xml中配置。而myParam参数同时在server.xml和web.xml文件中配置,但由于<Parameter>元素的override属性值为false,因此,myParam参数的值以server.xml文件中的配置为准。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

application域

 

  4.4.2  application域

  一个Web应用程序中的所有Servlet共享一个ServletContext对象,所以,ServletContext对象在JSP中也被称为application对象(application是JSP的9个内置对象之一)。在application对象内部有一个Map对象,用来保存Web应用程序中使用的key-value对,保存在application对象中的每一个key-value对也被称为application对象的属性。由于Web应用程序中的所有Servlet共享一个application对象,因此,application对象中的属性也可被称为application域范围内的属性,application域范围内的属性往往被当成Web应用程序的全局变量使用(如整个网站的访问计数器就可以作为application对象的属性被保存在application域中)。在ServletContext接口中定义了如下4个方法来操作application对象的属性:

  1.  getAttributeNames方法

  该方法返回一个Enumeration对象,通过这个对象可以获得application对象中所有属性的key值。getAttributeNames方法的定义如下:

  public Enumeration getAttributeNames();

  2.  getAttribute方法

  该方法返回一个指定application域属性的值。getAttribute方法的定义如下:

  public Object getAttribute(String name);

  3.  removeAttribute方法

  该方法用于删除application对象中的属性。

  4.  setAttribute方法

  向application对象添加一个属性,如果该属性存在,则替换这个属性。如果设置的属性值为null,则相当于调用removeAttribute方法来删除该属性。

  Servlet引擎会为每一个Web应用程序创建一个application对象,一个Servlet程序可以被发布到不同的Web应用程序中,而在不同的Web应用程序中该Servlet所对应的application对象是不同的。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

访问资源文件

 

  4.4.3  访问资源文件

  ServletContext接口定义了三个方法来访问当前Web应用程序的资源文件,这3个方法如下:

  1.  getResourcePaths方法

  该方法返回指定Web应用程序目录中的所有子目录和文件,这些返回的目录文件不包括嵌套目录和文件。这些返回的子目录和文件都封装在该方法返回的一个Set对象中。getResourcePaths方法的定义如下:

  public Set getResourcePaths(String path);

  其中path参数表示Web应用程序中的目录,必须以斜杠(/)开头,表示Web应用程序的根目录。如要得到WEB-INF目录中的所有目录和文件,可以使用如下的代码:

  Set paths = getServletContext()。getResourcePaths("/WEB-INF");

  2.  getResource方法

  该方法返回指定Web应用程序中某个资源的URL对象,getResource方法的定义如下:

  public URL getResource(String path) throws MalformedURLException;

  其中path表示资源的路径,必须以斜杠(/)开头,表示Web应用程序的根目录。如要得到封装web.xml文件路径的URL对象,可以使有下面的代码:

  URL url = getServletContext()。getResource("/WEB-INF/web.xml");

  3.  getResourceAsStream方法

  该方法返回某个资源的InputStream对象,getResourceAsStream方法实际上打开的是getResource方法返回的URL所指的资源文件。该方法的定义如下:

  public InputStream getResourceAsStream(String path);

  其中path参数的含义和getResource方法中的path参数相同。

  除了使用ServletContext定义的方法来访问Web应用程序中的资源外,还可以使用如下的两种方法来访问资源文件:

  1.  使用FileInputStream来访问资源文件

  使用这种方法非常直接,但要通过ServletContext接口的getRealPath方法获得当前Web应用程序的本地目录。如打开web.xml文件的代码如下:

  String resourceFileName =

  getServletContext()。getRealPath("/WEB-INF/web.xml");

  FileInputStream fis = new FileInputStream(resourceFileName);

  要注意的是,FileInputStream可以打开以绝对路径指定的资源文件,也可以打开以相对路径指定的资源文件。如果使用相对路径,该相对路径是相对于当前路径而言的。Web服务器的当前路径可以使用如下的代码获得:

  String path = System.getProperty("user.dir");

  假设上面的代码返回的路径是D:\eclipse,如果在该目录下有一个abc子目录,并且在abc子目录中有一个xyz.properties目录,也就是说,xyz.properties文件的绝对路径是D:\eclipse\abc\xyz.properties,那么使用绝对路径和相对路径访问xyzproperties文件的代码如下:

  //  使用绝对路径访问xyz.properties文件

  FileInputStream fis1 = new

  FileInputStream("D:\eclipse\abc\xyz.properties");

  //  使用相对路径访问xyz.properties文件

  FileInputStream fis1 = new FileInputStream("abc\xyz.properties");

  2.  使用Class.getResourceAsStream方法访问资源文件

  Class的getResourceAsStream方法和ServletContext接口的getResourceAsStream方法虽然方法名相同,但却有一定的差异。

  Class的getResourceAsStream方法的定义如下:

  public InputStream getResourceAsStream(String name);

  其中name表示资源的路径,也是以斜杠(/)开头,但这个斜杠是指WEB-INF\classes目录,而ServletContext接口中定义的getResourceAsStream方法的path参数中的斜杠是指Web应用程序的根目录。这一点在使用时要注意。

  假设在WEB-INF\classes\chapter4目录中有一个abc.properties文件,使用Class.getResourceAsStream方法访问该文件的代码如下:

  InputStream is =

  getClass()。getResourceAsStream("/chapter4/abc.properties");

  一个良好的编程习惯是将动态的或可能变化的信息(如连接数据库的信息、连接网络的信息等)保存在资源文件中,以便更容易修改和维护这些资源。

  在下面的示例中演示了如何使用上述的三种方法来访问Web应用程序中的资源文件,并从中读取信息。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

Web应用程序之间的访问

 

  4.4.4  Web应用程序之间的访问

  当前的Web应用程序不仅可以通过ServletContext对象访问自己的资源,而且还可以访问其他Web应用程序中的资源。假设在server.xml中配置了如下两个Web应用程序:

  <!--  本例所在的应用程序  -->

  <Context docBase="demo" path="/demo" reloadable="true"

  crossContext="true" source="org.eclipse.jst.jee.server:demo">

  <Parameter name="myParam" override="false" value="newValue" />

  <Parameter name="myParam1" override="true" value="newValue1" />

  </Context>

  <!--  要访问的Web应用程序  -->

  <Context docBase="mydemo" path="/mydemo" reloadable="true"

  source="org.eclipse.jst.jee.server:mydemo">

  <Parameter name="mydemo.param" value="mydemo.value"

  override="false" />

  </Context>

  上面的代码使用了两个<Context>元素分别配置了两个Web应用程序,而且在demo应用程序中的<Context>元素中使用了crossContext属性,如果该属性为true,则在当前Web应用程序中可以访问其他的Web应用程序,否则,将无法访问其他的Web应用程序,也就是无法获得其他Web应用程序的ServletContext对象。

  在mydemo工程中配置了一个mydemo.param参数,同时在mydemo应用程序中的web.xml中也配置了一个初始化参数,代码如下:

  <context-param>

  <param-name>name</param-name>

  <param-value>超人</param-value>

  </context-param>

  在demo应用程序中建立一个Servlet,并且在该Servlet中获得mydemo应用程序的ServletContext对象,并输出上述的两个初始化参数。该Servlet的代码如下:

  public class OtherContextServlet extends HttpServlet

  {

  protected void service(HttpServletRequest request,

  HttpServletResponse response) throws ServletException, IOException

  {

  response.setContentType("text/html;charset=UTF-8");

  PrintWriter out = response.getWriter();

  //  获得mydemo应用程序的ServletContext对象

  ServletContext context = this.getServletContext()。getContext("/mydemo");

  if(context != null)

  {

  //  输出mydemo应用程序中两个初始化参数值

  out.println("mydemo.param="

  + context.getInitParameter("mydemo.param") + "<br/>"); out.println("name=" + context.getInitParameter("name"));

  }

  }

  }

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/OtherContextServlet

  浏览器显示的信息如图4.5所示。

图4.9  显示其他Web应用程序的初始化参数

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

ServletContext接口定义的其他的方法

 

  4.4.5  ServletContext接口定义的其他的方法

  在ServletContext接口中还定义了一些其他的方法,这些方法如下:

  1.  getMajorVersion方法

  该方法得到当前Servlet引擎所支持的Servlet规范的主版本号。由于本书使用的是Servlet2.5,因此,getMajorVersion方法返回2.

  2.  getMinorVersion方法

  该方法得到当前Servlet引擎所支持的Servlet规范的次版本号。由于本书使用的是Servlet2.5,因此,getMinorVersion方法返回5.

  3.  getMimeType方法

  该方法返回Web应用程序中文件的MIME类型,如要返回web.xml文件的MIME类型,可以使用如下的代码:

  System.out.println(getServletContext()。getMimeType("/WEB-INF/web.xml"));

  上面的代码将输出application/xml.

  4.  getServerInfo方法

  该方法返回Web服务器的名称和版本号,如Apache Tomcat/6.0.18.

  5.  getServletContextName方法

  得到当前Web应用程序的上下文名称。也就是<Context>元素中path属性值的斜杠(/)后面的内容,既demo。

  6.  getContextPath方法

  得到当前Web应用程序的上下文路径。也就是<Context>标签中path属性值,既"/demo"。

 
 
 
 

第 4 章:Servlet开发基础作者:李宁    来源:希赛网    2014年03月07日

 

小结

 

  4.5  小结

  本章主要讲解了Servlet的基础知识。Servlet类必须要实现Servlet接口,但为了尽量减少代码量,Servlet API又提供了GenericServlet和HttpServlet类来实现Servlet接口中的方法。这样开发人员在开发Servlet时就无需编写大量的代码了。而在Servlet类中处于核心地位的是service方法。该方法可以处理所有的HTTP请求。但为了可以单独处理不同的HTTP请求,Servlet API提供了一些doXxx方法,如doGet方法只能处理HTTP GET请求。这些doXxx方法的调用都将依赖于service方法。

  在Servlet API中提供了一些接口,在这些接口中定义了很多可以访问Web应用程序的方法。如ServletConfig、ServletContext等,其中ServletConfig对象可以访问当前Servlet的配置信息,如Servlet名称 、Servlet的初始化参数等。而ServletContext对象则更为强大,它不仅可以获得当前Web应用程序的各种信息,如Web应用程序的初始化信息,Web资源的本地目录、访问application域等,还可以获得其他Web应用程序的ServletContext对象。

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

Http Servlet Response的应用

 

  第5章  Servlet高级技术

  本章将介绍Servlet的一些高级技术。在Servlet中,HttpServletResponse和HttpServletRequest接口是最常用的两个接口。在Servlet引擎中分别实现了这两个接口。在本章介绍了如何使用HttpServletResponse和HttpServletRequest对象来完成更高级的功能,如在HTTP响应消息头中传输中文、禁止浏览器的缓存、定时刷新网页、包含和转发Web资源等。除此之外,本章还介绍了Web应用程序中经常使用到的Cookie和Session的原理和应用,并给出了Cookie和Session的应用实例,如在Cookie中如何保存中文信息,通过URL重定向来跟踪Session等。读者通过对本章的学习,可以掌握Servlet的很多高级技术,并可以编写更复杂的Web应用程序。

  5.1  HttpServletResponse的应用

  Web服务器发送给客户端浏览器的信息有3部分:状态行、消息头和消息正文。为了更方便地操作这些信息,Servlet API提供了一个HttpServletResponse接口,在该接口中定义了很多方法来访问和控制这些信息,Servlet引擎必须实现这个接口,并在调用Servlet接口的service方法时传入HttpServletResponse对象。因此,开发人员可以在service方法以及doXxx方法中通过HttpServletResponse对象处理Web服务器发送给客户端的HTTP响应消息。

  HttpServletResponse接口继承了ServletResponse接口,ServletResponse接口中定义了处理响应消息的基本方法,如最常用的getWriter和getOutputStream方法,可以通过这两个方法向客户端输出响应正文。HttpServletResponse接口在ServletResponse接口的基础上添加了一些和HTTP协议相关的方法,如与Cookie相关的方法、访问HTTP响应消息头的方法等。

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

产生响应状态行

 

  5.1.1  产生响应状态行

  HTTP响应消息的响应状态行分为3部分:HTTP版本、状态码和状态信息,如下所示:

  HTTP/1.1  200  OK

  其中HTTP版本可以是HTTP/1.1或HTTP/1.0,这由Web服务器所支持的HTTP版本决定。状态信息的内容和状态相关,如404状态码所对应的HTTP1.1规范中的状态信息是Not Found.由于HTTP版本一般是基本固定的,而状态信息是随着状态码的变化而变的。因此,在HTTP响应状态行中,只有状态码是经常需要变化的。

  HTTP的状态响应码可分为如下5类:

  100 ~ 199:表示服务端成功接收HTTP请求,但要求客户端继续提交下一次HTTP请求才能完成全部处理过程。

  200 ~ 299:表示服务端已成功接收HTTP请求,并完成了全部处理过程。

  300 ~ 399:表示客户端请求的资源已经移动了别的位置,并向客户端提供一个新的地址,一般这个新地址由HTTP响应消息头的Location字段指定。

  400 ~ 499:表示客户端的请求有错误。

  500 ~ 599:表示服务端出现错误。

  HttpServletResponse接口定义一些可以修改HTTP状态码的方法,这些方法的描述如下:

  1.  setStatus方法

  setStatus方法可以设置状态码,并生成响应状态行。由于响应状态行中的协议版本和状态信息是由Web服务器设置的,因此,只需设置响应状态码就可以了。setStatus方法的定义如下:

  public void setStatus(int sc);

  其中sc参数表示响应状态码,该参数值可以直接使用整数形式,也可以使用在HttpServletResponse接口中定义的常量(建议使用这种方式)。如状态码200的常量为HttpServletResponse.SC_OK.

  2.  sendRedirect方法

  虽然setStatus方法可以随意设置响应状态吗,但HttpServletResponse接口还定义了一个sendRedirect方法,该方法可以更方便地将响应状态码设置成302.在300 ~ 399区间内的状态码需要客户端重定向URL(由HTTP响应消息头的Location字段指定的地址)。sendRedirect方法的定义如下:

  public void sendRedirect(String location) throws IOException;

  通过sendRedirect方法可以将当前的Servlet重定向到其他的Web资源上,这个URL可以是绝对路径(如http://www.csdn.net),也可以是相对路径(如/samples/test.html)。

  3.  sendError方法

  sendError方法用于设置表示错误消息的状态码(也就是400 ~ 599之间的状态码)。而且还可以设置状态消息。sendError方法的定义如下:

  public void sendError(int sc) throws IOException;

  public void sendError(int sc, String msg) throws IOException;

  其中sc参数表示响应状态码(一般是404,但也可以是其他的状态响应码,如500)、msg表示状态消息。

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

设置响应消息头

 

  5.1.2  设置响应消息头

  在HttpServletResponse接口中定义了若干设置HTTP响应消息头的方法,如addHeader方法可以添加响应消息头字段;addIntHeader方法可以添加整数值的响应消息头字段;setContextType方法可以设置Context-Type字段值。

  HTTP响应消息头是由若干key-value对组成的,其中key表示字段名,value表示字段值,中间用冒号(:)分隔。如下面的内容就是一个标准的HTTP响应消息头:

  Content-Length:1024

  Content-Type:text/html

  Content-Location:http://nokiaguy.blogjava.net

  Accept-Ranges:bytes

  Server:Microsoft-IIS/6.0

  X-Powered-By:ASP.NET

  Date:Tue, 30 Dec 2008 11:49:53 GMT

  从上面的内容可以看出,每一行都由一个字段和字段值组成,字段和字段值之间用冒号分隔(如Content-Length: 1024)。当使用Servlet向客户端发送响应消息时,为了完成某个功能或动作,如通知浏览器使用何种字符集显示网页;指定响应正文的类型等,需要对某些响应消息头进行设置。这就需要使用下面设置响应消息头的方法:

  1.  addHeader与setHeader方法

  addHeader和setHeader方法可用于设置HTTP响应消息头的所有字段。这两个方法的定义如下:

  public void addHeader(String name, String value);

  public void setHeader(String name, String value);

  其中name表示响应消息头的字段名,value表示响应消息头的字段值。这两个方法都会向响应消息头增加一个字段。但它们的区别是如果name所指的字段名已经存在,setHeader方法会用value来覆盖旧的字段值,而addHeader会增加一个同名的字段(HTTP响应消息头允许存在多个同名的字段)。在设置时,name不区分大小写。如设置Content-Type时可使用下面两行代码中的任意一行:

  response.setHeader("Content-Type", "image/png");

  response.setHeader("content-type", "image/png");

  2.  addIntHeader与setIntHeader方法

  HttpServletResponse接口定义了两个专门设置整型字段值的方法,这两个方法的定义如下:

  public void addIntHeader(String name, int value);

  public void setIntHeader(String name, int value);

  这两个方法与setHeader和addHeader方法类似。它们在设置整型字段值时避免了将int类型转换为String类型的麻烦。

  3.  addDateHeader与setDateHeader方法

  HttpServletResponse接口定义了两个专门设置日期字段值的方法,这两个方法的定义如下:

  public void addDateHeader(String name, long date);

  public void setDateHeader(String name, long date);

  这两个方法与setHeader和addHeader方法类似。HTTP响应消息头中的日期一般为GMT时间格式。这两个方法在设置日期字段值时省去了将自1970年1月1日0点0分0秒开始计算的一个以毫秒为单位的长整数值转换为GMT时间字符串的麻烦。

  4.  setContentType方法

  setContentType方法用于设置Servlet的响应正文的MIME类型,对于HTTP协议来说,就是设置Content-Type字段的值。如响应正文是png格式的图形数据,就需要使用该方法将响应正文的MIME类型设置成image/png,代码如下:

  response.setContentType("image/png");

  关于更详细的MIME类型消息,可以在<Tomcat安装目录>\conf\web.xml文件中找到。setContentType方法还可指定响应正文所使用的字符集类型,如"text/html; charset=UTF-8",在设置字符集类型时,charset应为小写,否则不起作用。如果在MIME类型中未指定字符集编码类型,并且使用getWriter方法(将在后面的部分介绍)返回的PrintWriter对象输出文本时,Tomcat将使用ISO8859-1字符集编码格式对输出的文本进行编码。因此,如果在Servlet中要向客户端输出中文时,应使用setContentType方法设置响应正文的字符集编码。

  5.  setCharacterEncoding方法

  该方法设置了Content-Type字段的字符集部分,也就是设置"text/html; charset=UTF-8"中的"charset=UTF-8"部分。在使用setCharacterEncoding方法之前,如果Content-Type字段不存在,必须使用setContentType或setHeader方法添加Content-Type字段,否则setCharacterEncoding方法字符集类型不会出现在响应消息头上。setCharacterEncoding方法同时还设置了使用PrintWriter对象向客户端输出的字符的编码格式,这一点和setContextType方法类似。

  6.  setContentLength方法

  setContentLength方法用于设置响应正文的大小(单位是字节)。对于HTTP协议来说,这个方法就是设置Content-Length字段的值。当使用下载工具下载文件时,会发现在每个下载文件的状态栏中都会显示文件大小,其实这个值就是从Content-Length字段中获得。如果下载某些文件时,无法正确显示文件大小,说明HTTP响应消息头中并未设置Content-Length字段的值。一般来说,在Servlet中并不需要使用setContentLength方法设置Content-Length的值,因为Servlet引擎会根据向客户端实际输出的响应正文的大小动态设置Content-Length字段的值。

  7.  containsHeader方法

  containsHeader方法用于检查某个字段名是否在HTTP响应消息头中存在,如果存在,返回true,否则返回false.containsHeader方法的定义如下:

  public boolean containsHeader(String name);

  8.  setLocale方法

  该方法设置了响应消息的本地信息。setLocale方法的定义如下:

  public void setLocale(java.util.Locale loc);

  其中Locale对象包含了语言和国家地区信息。该方法有如下3个作用:

  (1)设置Content-Language字段的值,该字段表示当前页面所使用的语言。如设置成zh-CN,表示当前页面是中文。

  (2)设置Content-Type字段的字符集编码部分。但如果Content-Type字段不存在,则该方法不会自动添加Content-Type字段,也就是说,要想使用setLocale方法设置字符集编码,必须将使用addHeader方法或其他可以添加响应字段的方法添加一个Content-Type字段,才可以使用setLocale方法设置Content-Type字段的字符集编码部分,如下面的代码所示:

  response.addHeader("content-type", "");

  response.setLocale(new java.util.Locale("zh", "CN"));

  response.getWriter()。println("设置Content-Type字段的字符集编码部分");

  由于java.util.Locale类并未包含字符集编码信息,因此,如果要使用setLocale方法设置Content-Type字段值的字符集编码部分,还必须在web.xml文件中使用<local-encoding-mapping-list>元素进行映射,代码如下:

  <locale-encoding-mapping-list>

  <locale-encoding-mapping>

  <locale>zh-CN</locale>

  <encoding>UTF-8</encoding>

  </locale-encoding-mapping>

  </locale-encoding-mapping-list>

  上面的配置代码将zh-CN映射成了UTF-8,因此,使用setLocale方法将Content-Language字段设置成zh-CN后,Content-Type字段值的字符集编码部分就会被设置成"charset=UTF-8".

  (3)设置要输出到客户端的文本的编码格式。

  在SevletResponse和HttpServletResponse接口中有很多方法可以设置Content-Type字段的字符集编码部分和服务端输出文本的编码格式,这些方法包括addHeader、setHeader、setContent-Type、setCharacterEncoding、setLocale.实际上,这些方法具有同等的地位,也就是说,后面调用的方法将覆盖前面调用的方法的设置。但这些方法必须在调用getWriter方法之前调用,否则设置不会生效。

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

用HTTP响应消息头传输中文信息

 

  5.1.3  用HTTP响应消息头传输中文信息

  使用HTTP响应头传递信息是一件非常"酷"的事。但遗憾的是,在传递中文时,会出现乱码问题。其实要解决这个问题也非常简单,只需要对要传输的中文进行编码,然后在接收它们的客户端再对其进行解码即可。

  【实例5-1】  用HTTP响应消息头传输中文信息

  1.  实例说明

  在本程序中通过HTTP响应消息头分别传输英文消息、中文消息和被编码后的中文消息(对中文消息的编码可以采用多种方式,在本例中采用了URL编码的方式,也就是使用java.net.URLEncoder.encode方法对中文消息进行编码),并在客户端使用Socket来访问该Servlet程序,并输出相应的中、英文消息。

  2.  编写ChineseHeader类

  ChineseHeader是一个Servlet类,负责向客户端发送HTTP响应消息,该类的实现代码如下:

  public class ChineseHeader

  {

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

  {

  response.setContentType("text/html;charset=UTF-8");

  PrintWriter out = response.getWriter();

  //  设置英文响应消息头

  response.addHeader("English", "information");

  //  设置中文响应消息头

  response.addHeader("Chinese", "中文头信息");

  //  设置被编码的中文响应消息头

  response.addHeader("NewChinese", java.net.URLEncoder.encode("中文头信息","utf-8"));

  out.println("响应正文");

  }

  }

  上面的代码向客户端输出了3个自定义的HTTP响应消息头字段:English、Chinese和NewChinese.其中English字段值是英文消息、Chinese字段值是未编码的中文消息,而NewChinese字段值是用UTF-8格式编码的中文消息。

  3.  查看HTTP响应消息头

  查看HTTP响应消息头的方法很多,如可以使用telnet或自己编写程序来获得HTTP响应消息头信息。但这些方式都需要编写程序,比较麻烦。因此,可以借助更简单的工具来完成这个工作。在本例中使用了一个叫"影音传送带"的下载工具。读者也可以使用其它的下载工具。

  使用影音传送带下载如下的URL:

  http://localhost:8080/demo/ChineseHeader

  然后查看影音传送带的下载日志,如图5.1所示。

图5.1  查询HTTP响应消息头

  图5.1中的黑框中的内容就是在服务端设置的3个自定义HTTP响应消息头。由此可以看出,English字段的值正常显示了,而Chinese和NewChinese字段的值并没有正常显示。其中Chinese字段的值是乱码,而NewChinese字段的值显示的是URL编码格式。其实这些编码就是"中文头消息"的UTF-8编码,要想获得正确的中文消息,必须要使用java.net.URLDecoder类对其解码。

  从以上结果可以得出一个结论,使用setCharacterEncoding或其他方法设置字符集编码,并不会对HTTP响应消息头进行编码,而只会对响应正文进行编码。

  4.  编写访问ChineseHeader的客户端程序(MyChineseHeader类)

  MyChineseHeader类是一个控制台程序,在该类中通过java.net.Socket类来访问服务端程序,并对被编码的中文消息进行解码。MyChineseHeader类的实现代码如下:

  package chapter5;

  import java.net.*;

  import java.io.*;

  public class MyChineseHeader

  {

  public static void main(String[] args) throws Exception

  {

  Socket socket = new Socket("localhost", 8080);

  OutputStream os = socket.getOutputStream();

  OutputStreamWriter osw = new OutputStreamWriter(os);

  // 向服务端发送HTTP请求消息(只有请求头)

  osw.write("GET /demo/ChineseHeader HTTP/1.1\r\n");

  osw.write("Host:localhost:8080\r\n");

  osw.write("Connection:close\r\n\r\n");

  osw.flush();

  //  从服务端获得HTTP响应消息头

  InputStream is = socket.getInputStream();

  InputStreamReader isr = new InputStreamReader(is, "UTF-8");

  BufferedReader br = new BufferedReader(isr);

  String s = "";

  String responseData = "";

  // 按行读取HTTP响应消息头

  while((s = br.readLine()) != null)

  {

  responseData += s + "\r\n";

  }

  responseData = java.net.URLDecoder.decode(responseData, "UTF-8");

  System.out.println(responseData);// 输出解码后的HTTP响应头

  is.close();

  os.close();

  }

  }

  在编写上面的代码时要注意的是在进行解码时,解码格式必须和服务端一致,也就是说decode方法和encode方法的第二个参数值必须是相同的编码格式,在本例中都是UTF-8。

  5.  获得解码后的HTTP响应头信息

  运行MyChineseHeader程序,在IDE的Console中将会输出如图5.2所示的HTTP响应消息头信息。从图5.2输出的信息可以看出,NewChinese字段的值已经正确显示了。

图5.2  输出正常的HTTP响应头消息

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

禁止浏览器缓存当前Web页面

 

  5.1.4  禁止浏览器缓存当前Web页面

  所谓浏览器缓存,是指当第一次访问网页时,浏览器会将这些网页缓存到本地,当下一次再访问这些被缓存的网页时,浏览器就会直接从本地读取这些网页的内容,而无需再从网络上获取。

  虽然浏览器提供的缓存功能可以有效地提高网页的装载速度,但对于某些需要实时更新的网页,这种缓存机制就会影响网页的正常显示。幸好在HTTP响应消息头中提供了3个字段可以关闭客户端浏览器的缓存功能。下面三条语句分别使用这三个字段来关闭浏览器的缓存:

  response.setDateHeader("Expires", 0);

  response.setHeader("Cache-Control", "no-cache");

  response.setHeader("Pragma", "no-cache");

  虽然上面3个HTTP响应消息头字段都可以关闭浏览器缓存。但并不是所有的浏览器都支持这3个响应消息头字段,因此,最好同时使用上面这3个响应消息头字段来关闭浏览器的缓存。

  【实例5-2】  禁止浏览器缓存当前Web页面

  1.  实例说明

  本程序演示了在未关闭浏览器缓存和关闭浏览器缓存两种情况下,通过form提交请求消息时的表现。

  2.  编写Cache类

  在Cache类中同时使用上述的三个响应消息头字段关闭了浏览器缓存,并向客户端输出一段HTML代码,以测试关闭缓存和未关闭缓存的效果。Cache类的实现代码如下:

  public class Cache extends HttpServlet

  {

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

  {

  response.setContentType("text/html;charset=UTF-8");

  String cache = request.getParameter("cache");

  if (cache != null)

  {

  if (cache.equals("false"))

  {

  //  关闭浏览器缓存

  response.setDateHeader("Expires", 0);

  response.setHeader("Cache-Control", "no-cache");

  response.setHeader("Pragma", "no-cache");

  }

  }

  //  定义HTML代码

  String html = "<form id = 'form', action='test' method='post'>"

  + "姓名:<input type='text' name = 'name'/>"

  + "<input type='submit' value='提交' />" + "</form>";

  PrintWriter out = response.getWriter();

  out.println(html);//  向客户端输出HTML代码

  }

  }

  从上面的代码可以看出,当cache请求参数值为false时关闭浏览器的缓存。

  3.  测试未关闭浏览器缓存的情况

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/Cache?cache=true

  在"姓名"文本框中输入任意字符串,点击"提交"按钮,这时浏览器会显示一个异常(这个异常是由于所提交的test不存在而产生的,我们不用去管它),然后点击浏览器的返回按钮回到刚才输入数据的页面。我们可以看到,刚才输入的字符串仍然存在。这说明在返回时,浏览器并未从服务端重新获得这个页面,而是从本地的缓存里重新加载了当前的页面。

  4.  测试关闭浏览器缓存的情况

  在浏览器地址栏中输入如下的URL来关闭浏览器缓存:

  http://localhost:8080/demo/Cache?cache=false

  按着上一步的方式提交并返回,发现刚才输入的数据没有了。这说明在关闭浏览器缓存后,每次返回时,浏览器总会从服务端重新获得当前页面。因此,当前页面总是保持着初始值。

  5.  程序总结

  在关闭浏览器缓存时,为了尽可能保证在大多数浏览器中都有效,笔者建议同时使用上述三个HTTP响应消息头字段来关闭浏览器缓存。

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

网页定时刷新和定时跳转

 

  5.1.5  网页定时刷新和定时跳转

  有时一个网页需要按着一定的时间间隔刷新,或是在一定时间后跳到其他的网页上。这种功能在定时从服务端获得数据;或是在短时间显示一个公告页,在一段时间后,跳到主页的情况下特别有用。

  虽然实现定时刷新和定时跳转有很多方法,但使用HTTP响应消息头中的Refresh字段无疑是最简单的方法,通过设置这个字段的值,可以使当前网页每隔一定的时间刷新一次,还可以使当前网页在一定时间后跳转到其他的网页。如果只想定时刷新,可以使用下面的代码来实现:

  response.setHeader("Refresh", "3");// 每隔3秒页面刷新一次

  下面的代码实现了3秒后跳转到其他网页的功能:

  response.setHeader("Refresh", "3;URL=http://www.csdn.net");

  时间和URL之间要用分号(;)隔开。其中URL指定了在一定时间间隔要跳转到的其他网页地址。

  【实例5-3】  网页定时刷新和定时跳转

  1.  实例说明

  在本例中使用了url请求参数来指定要跳转到的网页地址,如果不指定url请求参数,则每隔3秒刷新一次网页,并显示当前的服务器时间。读者会看到网页上显示的服务器时间每隔3秒变化一次。

  2.  编写Refresh类

  Refresh类演示了如何使用Refresh字段实现网页定时刷新和定时跳转的功能。Refresh类的实现代码如下:

  public class Refresh extends HttpServlet

  {

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

  {

  response.setContentType("text/html;charset=UTF-8");

  String url = request.getParameter("url");

  if(url == null)

  {

  response.setHeader("Refresh", "3");//  每隔3秒刷新一次网页

  }

  else

  {

  //  在3秒钟后定时跳转

  response.setHeader("Refresh", "0;URL=" + url);

  }

  PrintWriter out = response.getWriter();

  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

  //  输出当前服务器时间

  out.println(dateFormat.format(new java.util.Date()));

  }

  }

  3.  测试定时刷新

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/Refresh

  然后读者就会看到,在浏览器中显示的时间每隔3秒就变化一次。

  4.  测试定时跳转

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/Refresh?url=http://nokiaguy.blogjava.net

  然后读者就会看到,3秒后,当前网页就会跳到www.csdn.net上。其中url参数可以是相对路径(如ChineseHeader),也可以是绝对路径(如http://www.csdn.net)。

  5.  程序总结

  如果将Refresh字段的时间间隔设为0,那么在当前网页装载完后会立即跳转到url所指的网页。除了使用Refresh来跳转网页外,还可以使用HttpServletResponse接口的sendRedirect来重定向网页,代码如下:

  response.sendRedirect("http://www.csdn.net");

  这两种跳转网页的方式可以达到同样的效果,但它们不同的是使用Refresh来跳转网页时,会先将当前网页装载完,才执行跳转动作,而使用sendRedirect方法来重定向网页,会直接转到目标网页上,而在sendRedirect方法之后的内容根本就不会输出到客户端。

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

实现动态文件下载

 

  5.1.6  实现动态文件下载

  在Web服务器上实现文件下载功能很容易。只要将URL指向要下载的文件即可。但是这要有一个前提,就是要下载的文件必须位于在Web服务器中部署的Web目录中。但有时需要在下载文件之前做一些其他的事,如验证用户是否有权限下载该文件。在这种情况下,就必须通过动态下载的方式(也就是通过程序来读取待下载的文件,而不是直接由Web服务器负责下载)来实现。

  下面的例子演示了如何通过Servlet实现动态下载文件的功能。

  【实例5-4】  实现动态下载文件

  1.  实例说明

  在本例中将待下载的文件放到了非Web目录中(在web.xml中设置),使客户端无法直接访问待下载的文件。然后通过一个Servlet进行中转,如果待下载的文件存在,通过FileInputStream对象打开这个文件,并通过ServletOutputStream对象将待下载的文件按字节流的方式输出到客户端,如果待下载的文件扩展名是".jpg",则直接在浏览器中显示该图象。该程序还有一个功能,就是列出在web.xml文件中指定的目录中的所有文件(带链接)。只需要直接点击相应的文件就可下载或显示该文件的内容。

  2.  编写Download类

  该类负责列目录和下载文件,实现代码如下:

  package chapter5;

  import java.io.*;

  import javax.servlet.*;

  import javax.servlet.http.*;

  import java.net.*;

  public class Download extends HttpServlet

  {

  private void download(File file, HttpServletResponse response)

  throws IOException

  {

  if (file.exists())

  {

  // 当扩展名是。jpg时,在浏览器中显示图象,而不是下载这个图象文件

  if ((file.getName()。length() -

  file.getName()。lastIndexOf(".jpg")) == 4)

  {

  response.setContentType("image/jpeg");

  response.addHeader("Content-Disposition",

  "filename=" + URLEncoder.encode(file.getName(), "UTF-8"));

  }

  // 设置要下载的文件的名称、Content-Type和长度

  else

  {

  response.setContentType("application/octet-stream");

  response.addHeader("Content-Disposition",

  "attachment;filename=" +

  URLEncoder.encode(file.getName(),  "UTF-8"));

  }

  response.addHeader("Content-Length",

  String.valueOf(file.length()));

  InputStream is = new FileInputStream(file);

  byte[] buffer = new byte[8192]; // 每次向客户端发送8K字节

  int count = 0;

  ServletOutputStream sos = response.getOutputStream();

  //  向客户端输出下载文件的内容,每次输出8K字节

  while ((count = is.read(buffer)) > 0)

  sos.write(buffer, 0, count);

  is.close();

  sos.close();

  }

  }

  //  输出path初始化参数指定的目录中的文件

  private void listDir(File dir, HttpServletResponse response)

  throws IOException

  {

  response.setContentType("text/html;charset=UTF-8");

  PrintWriter out = response.getWriter();

  //  扫描目录的子目录和文件

  for (File file : dir.listFiles())

  {

  //  如果是文件,输出文件名和其对应的URL

  if (file.isFile())

  {

  out.print("<a href='Download?filename=" +

  URLEncoder.encode(file.getName(), "UTF-8") + "'>");

  out.println(file.getName() + "</a><br/>");

  }

  }

  }

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

  {

  //  读取path初始化参数的值

  String path = this.getServletConfig()。getInitParameter("path");

  String filename = request.getParameter("filename");

  File dir = new File(path);

  if (dir.exists())

  {

  if (filename != null)

  {

  filename = dir.getPath() + File.separator + filename;

  File downloadFile = new File(filename);

  download(downloadFile, response);// 下载文件

  }

  else

  {

  listDir(dir, response); // 列出文件目录

  }

  }

  }

  }

  3.  配置Download类和path参数

  path是Download类的初始化参数,需要在<servlet>元素中配置,代码如下:

  <servlet>

  <servlet-name>Download</servlet-name>

  <servlet-class>chapter5.Download</servlet-class>

  <!--  配置path初始化参数  -->

  <init-param>

  <param-name>path</param-name>

  <param-value>D:\download\</param-value>

  </init-param>

  </servlet>

  <servlet-mapping>

  <servlet-name>Download</servlet-name>

  <url-pattern>/Download</url-pattern>

  </servlet-mapping>

  其中path表示要下载文件所在的目录,读者也可以指定其他存在的目录。

  4.  测试程序

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/servlet/Download

  在浏览器中会列出D:\download目录中的所有文件,如图5.3所示。

图5.3  列出待下载文件所在的目录

  当单击"我的文章。doc"时,就会弹出如图5.4所示的下载对话框。

图5.4  下载对话框

  5.  程序总结

  在编写上面的代码时,应注意如下几点:

  (1)在下载文件时必须设置Content-Type和Content-Disposition字段。其中Content-Type字段的值是application/octet-stream,表示下载的是二进制字节流。而Content-Disposition字段的值有两部分组成,其中attachment表示下载的是附件,也就是说,浏览器会弹出一个下载对话框。而后面的filename部分设置了下载对话框中显示的默认文件名。如果不设置Content-Disposition字段,要下载的文件将直接在浏览器中打开。

  (2)如果要在浏览器中显示某些类型的文件,需要将Content-Type字段值设成相应的MIME类型,如本例中要显示jpg格式的图象,则该字段的值为image/jpeg.但要注意, Content-Disposition字段中不能有attachment,否则浏览器会下载这个jpg文件,而不会显示它。

  (3)如果下载的文件名中包含中文,在设置Content-Disposition中的filename时,应使用java.net.URLEncoder.encode方法将文件名按UTF-8格式编码,否则,在下载对话框中无法正确显示中文名。

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

Http Servlet Request的应用

 

  5.2  Http Servlet Request的应用

  HttpServletRequest接口是Servlet API提供的另一个重要接口。Servlet引擎为每一个用户请求创建一个HttpServletRequest对象。该对象将作为service方法的第二个参数值传给service方法。HttpServletRequest接口是ServletRequest的子接口。在ServletRequest和HttpServletRequest接口中定义子大量的方法,通过这些方法,开发人员不仅可以获得HTTP响应消息头、HTTP响应消息正文、Cookie与Session对象等内容,还可以利用请求域进行数据的传递。

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

获得HTTP请求行信息

 

  5.2.1  获得HTTP请求行信息

  HTTP请求消息的请求行分为三部分:请求方法(GET、POST、HEAD等)、资源路径和HTTP协议版本,如下所示:

  GET /demo/servlet/TestServlet?name=mike&salary=3021 HTTP/1.1

  通过下面的URL可以产生如上所示的请求行消息:

  http://localhost:8080/demo/servlet/TestServlet?name=mike&salary=3021

  HttpServletRequest接口中定义了若干的方法来获取请求行中的各个部分的信息,如下所示:

  1.  getMethod方法

  该方法返回HTTP请求消息的请求方法(如GET、POST、HEAD等),也是请求行的第一部分。

  2.  getRequestURI方法

  该方法返回请求行中的资源名部分,也就是位于URL的端口号和请求参数之间的部分,例如,对于如下的URL:

  http://localhost:8080/demo/servlet/TestServlet?name=mike&salary=3021

  getRequestURI方法返回上面URL中的"/demo/servlet/TestServlet"部分。

  3.  getQueryString方法

  该方法返回请求行中的参数部分,也就是资源路径中问号(?)后面的内容。而且返回的结果不会被解码,也就是说,将保持原样返回。例如:对于如下的URL:

  http://localhost:8080/demo/servlet/TestServlet?name=mike&salary=3021

  getQueryString方法返回上面URL中的"name=mike&salary=3021".如果在资源路径中没有请求参数部分,getQueryString方法返回null.

  4.  getProtocol方法

  该方法返回请求行中的协议名和HTTP版本,即请求行的第3部分,一般是HTTP/1.0或HTTP/1.1.如果在Web应用程序中需要单独对不同的HTTP版本进行处理,可以使用该方法来判断当前请求的HTTP版本。

  5.  getContextPath方法

  该方法返回请求URL中的Web应用程序的路径,也就是说,返回URL中端口号和Web资源路径之间的部分。这个路径以斜杠(/)开头,表示当前Web站点的根目录,路径的结尾不含斜杠(/)。如果请求URL属于Web站点的根目录,则该方法应返回空字符串("")。例如,对于如下的URL:

  http://localhost:8080/demo/servlet/TestServlet?name=mike&salary=3021

  对于上面的URL,"/servlet/TestServlet"是在web.xml中定义的Servlet映射URL(也可以称为Web资源路径),而getContextPath方法则返回端口号(8080)和Web资源路径(/servlet/TestServlet)之间的部分,也就是URL中的"/demo".

  6.  getPathInfo方法

  该方法返回额外的路径部分。额外路径位于Web资源路径和参数之间,以"/"开头。如TestServlet在web.xml中的映射URL是"/TestServlet/*",那么就可以用"/TestServlet/a"、"/TestServlet/b"访问TestServlet,其中"/a"、"/b"就是getPathInfo方法返回的额外路径。如果URL中没有额外路径,getPathInfo方法返回null.

  7.  getPathTranslated方法

  该方法返回URL中额外信息所对应的服务端的本地路径。如"/request/abc.jsp"中的"/abc.jsp"是额外路径信息,则getPathTranslated方法返回"/abc.jsp"所对应的服务端的本地路径。

  8.  getServletPath方法

  该方法返回Servlet在web.xml中定义的<url-pattern>元素的值,也就是Servlet的访问路径。

  9.  getParameterNames方法

  该方法返回一个Enumeration对象,在这个对象中封装了URL的所有的请求参数名。

  10.  getParameter方法

  该方法返回某一个请求参数的值,如获得name请求参数值的代码如下:

  String name = getParameter("name");

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

获得网络连接信息

 

  5.2.2  获得网络连接信息

  由于客户端浏览器和服务端进行交互是建立在TCP连接基础上的,因此,有时在服务端就需要知道客户端的一些网络连接信息,因此,ServletRequest接口定义了若干可以获得网络连接信息的getter方法。通过这些方法,可以获得客户端和服务端的IP、端口以及访问协议等信息。

  假设客户端的IP是192.168.18.10,服务器的IP是192.168.18.254,服务器主机名是webserver.并通过如下的URL来访问Servlet.

  http://localhost:8080/demo/servlet/TestServlet?name=mike&age=52

  使用上面的URL访问Sevlet将产生如下的HTTP请求消息:

  GET /demo/servlet/TestServlet?name=mike&age=52 HTTP/1.1

  Accept: */*

  Accept-Language: zh-cn

  Accept-Encoding: gzip, deflate

  User-Agent: Mozilla/4.0

  Host: localhost:8080

  Connection: Keep-Alive

  下面是ServletRequest接口中定义的用于获得网络连接信息的方法:

  1.  getRemoteAddr方法

  该方法返回客户机用于发送请求的IP地址(192.168.18.10)。

  2.  getRemoteHost方法

  该方法返回发出请求的客户机的主机名。如果Servlet引擎不能解析出客户机的主机名,则返回客户端的IP地址(192.168.18.10)。

  3.  getRemotePort方法

  该方法返回客户机所使用的网络接口的端口号,这个值是由客户机的网络接口随机分配的,如1078,也有可能是其他的值。

  4.  getLocalAddr方法

  该方法返回Web服务器上接收请求的网络接口使用的IP地址(192.168.18.254)。

  5.  getLocalName方法

  该方法返回Web服务器上接收请求的网络接口使用的IP地址所对应的主机名(webserver)。

  6.  getLocalPort方法

  该方法返回Web服务器上接收请求的网络接口的端口号(8080)。

  7.  getServerName方法

  该方法返回HTTP请求消息的Host字段值的主机名部分(localhost)。

  8.  getServerPort方法

  该方法返回HTTP请求消息的Host字段值的端口号部分(8080)。

  9.  getScheme方法

  该方法返回请求的协议名,如http、https等,在本例中是http.

  10.  getRequestURL方法

  该方法返回完整的请求URL(不包括参数部分)。这个方法返回的是StringBuffer类型,而不是String类型。在本例中返回"http://localhost:8080/demo/servlet/TestServlet".要注意该方法和getRequestURI方法的区别。关于getRequestURI方法的介绍详见5.2.1节的内容。

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

获得HTTP请求消息头

 

  5.2.3  获得HTTP请求消息头

  在HttpServletRequest接口中定义了若干读取HTTP请求消息中的头字段值的方法,其中getHeader方法是最常用的方法。通过该方法可以获得指定字段头的值。除了getHeader方法外,在HttpServletRequest接口中还定义了很多其他获得请求头消息的方法,如getIntHeader、getDateHeader、getContentLength等。通过这些方法获得的请求头消息,可以实现更加强大的功能,如可以根据浏览器的语言设置输出相应国家语言的网页内容,或者可以使用Referer字段防止盗链。这些获得HTTP请求头消息的方法如下:

  1.  getHeader方法

  该方法返回指定的HTTP请求消息头字段的值。如获得Host字段值的代码如下:

  String host = getHeader("Host");

  2.  getHeaders方法

  该方法返回一个Enumeration对象,该对象封装了某个指定名称的头字段的所有同名字段的值。

  3.  getHeaderNames方法

  该方法返回一个Enumeration对象,该对象封装了所有的HTTP请求消息头字段的名称。

  4.  getIntHeader方法

  该方法返回一个指定的整型头字段的值。

  5.  getDateHeader方法

  该方法返回一个指定的日期头字段的值。

  6.  getContentType方法

  该方法返回请求消息中请求正文的MIME类型,也就是Content-Type头字段的值。

  7.  getContentLength方法

  该方法返回请求消息中请求正文的长度(以字节为单位),也就是Content-Length字段的值,如果未指定长度,返回-1.

  8.  getCharacterEncoding方法

  该方法返回请求消息正文的字符集编码,通常从Content-Type头字段中提取。

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

客户端身份验证

 

  5.2.4  客户端身份验证

  有时需要对某些网络资源(如Servlet、JSP等)进行访问权限验证,也就是说,有访问权限的用户才能访问该网络资源。进行访问权限验证的方法很多,但通过HTTP响应消息头的WWW-Authenticate字段进行访问权限的验证应该是众多权限验证方法中比较简单的一个。

  通过HTTP响应消息头的WWW-Authenticate字段可以使浏览器出现一个验证对话框,访问者需要在这个对话框中输入用户名和密码,然后经过服务端验证,才可以正常访问网络资源的内容。但要注意,在发送WWW-Authenticate字段的同时,还要使用HttpServletResponse接口的setStatus方法将响应码设为401(HttpServletResponse.SC_UNAUTHORIZED),否则不会出现验证对话框。

  通过WWW-Authenticate字段进行验证有两种方式:BASIC和DIGEST.其中BASIC方式比较简单,密码通过Base64编码格式进行传输,实际上就是通过明文进行传输。而DIGEST可以对传输的用户名、密码等敏感信息进行加密,也更加安全,但是实现起来也更复杂。在本节中将使用BASIC方式进行验证,关于DIGEST的详细信息,感兴趣的读者可以参考RFC2617(http://www.ietf.org/rfc/rfc2617.txt)。

  进行验证的基本过程是首先判断HTTP请求消息头是否有Authorization字段,如果有这个字段,说明用户曾经登录过,可能登录成功,也可能登录失败,只要是输入了用户名和密码,并单击"确定"按钮后,再次在同一个浏览器窗口访问该Servlet,浏览器就会在HTTP请求消息头中加入Authorization字段,格式如下:

  Authorization:Basic  YWRtaW46MTIzNDEx

  其中Basic是验证的类型,后面是被Basic64格式的用户名和密码信息。

  如果HTTP请求消息头中没有Authorization字段,或者不是Basic验证,则在Servlet中要设置WWW-Authenticate响应消息头字段,格式如下:

  WWW-Authenticate:BASIC  realm="/demo"

  其中realm表示当前资源所属的域,可以是任意字符串,一般情况下,同一个Web应用程序要将这个属性值设成同一个值,如可以设成上下文路径(通过getContentPath方法获得)。

  在下面将给出一个实际的例子来演示如何使用BASIC方式进行身份验证。

  【实例5-5】  客户端身份验证

  1.  实例说明

  在每一次访问本例中的程序(Servlet)时,将会弹出一个权限验证对话框,要求输入"用户名"和"密码".用户名和密码输入正确后,就会进入相应的页面。当再次访问当前页面时,就不会弹出权限验证对话框了,而是直接进入当前访问的页面。如果在浏览器的新窗口再次访问该Servlet时,仍然会弹出权限验证对话框,并重复上述的权限验证过程。

  2.  编写AuthenticateServlet类

  AuthenticateServlet类负责效验用户输入的用户名和密码,并且当第一次访问Servlet时设置WWW-Authenticate响应消息头字段,以通知浏览器显示权限验证对话框。AuthenticateServlet类的实现代码如下:

  package chapter5;

  import java.io.*;

  import javax.servlet.*;

  import javax.servlet.http.*;

  public class AuthenticateServlet extends HttpServlet

  {

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

  {

  response.setContentType("text/html; charset=UTF-8");

  PrintWriter out = response.getWriter();

  //  得到Authorization字段的值

  String base64Auth = request.getHeader("Authorization");

  if (base64Auth == null

  || !base64Auth.toUpperCase()。startsWith("BASIC"))

  {

  // 通知浏览器弹出验证对话框

  response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

  response.setHeader("WWW-Authenticate", "BASIC realm=\""

  + request.getContextPath() + "\"");

  out.println("需要进行身份验证!");

  return;

  }

  sun.misc.BASE64Decoder base64Decoder = new sun.misc.BASE64Decoder();

  String auth = new String(base64Decoder.decodeBuffer(base64Auth

  .substring(6)));

  String[] array = auth.split(":");// 将用户名和密码分开

  if(array.length == 2)

  {

  String user = array[0].trim();

  String password = array[1].trim();

  RequestDispatcher rd =

  request.getRequestDispatcher("HeaderInfo");

  rd.include(request, response);// 输出所有的HTTP请求头

  // 进行身份验证

  if(user.equals("admin") && password.equals("1234"))

  out.println("身份验证成功,该Servlet已经进入!");

  else

  out.println("身份验证失败!");

  }

  }

  }

  从上面的代码可以看出,AuthenticateServlet类首先判断了请求消息头字段Authorization是否存在,如果该字段不存在,则设置了响应消息头字段WWW-Authenticate和状态码401,以通知浏览器显示权限验证对话框。如果Authorization字段存在,并且为Basic验证,则从Authorization字段值中取出用户名和密码进行验证,并输出验证结果信息。

  3.  测试

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/AuthenticateServlet

  浏览器会弹出一个权限验证对话框,并在"用户名"和"密码"文本框中分别输入admin和1234,如图5.5所示。

图5.5 权限验证对话框

  单击"确定"按钮,浏览器将显示如图5.6所示的信息。

图5.6  权限验证成功后显示当前访问页面的内容

  4.  程序总结

  要注意的是,在单击图5.5所示的"确定"按钮后,浏览器会再次访问AuthenticateServlet,这时HTTP请求消息头已经包含了authorization字段,如图5.9的黑框中所示。如果单击"取消"按钮,浏览器不会再次访问AuthenticateServlet,同时,AuthenticateServlet会在浏览中输出"需要进行身份验证!"信息。

  在等一次登录成功后,在同一个浏览器窗口再次访问AuthenticateServlet,在HTTP请求消息头中就会包含authorization字段,因此,也就不再需要进行身份验证了。

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

处理Cookie

 

  5.3  处理Cookie

  Cookie是浏览器和Web服务器之间进行信息交换的一种方式。通过这种信息交换方式,浏览器和Web服务器之间可以将要交换的信息放到HTTP请求和响应消息头中,并分别由服务端和客户端读取这些信息。Cookie在实际应用中得到了非常广泛的应用。

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

什么是Cookie

 

  5.3.1  什么是Cookie

  Cookie是一种在客户端保存HTTP状态信息的技术。可以将Cookie想象成商场的会员卡。如果顾客在商场里办了一张会员卡,就意味着该顾客以后的各种消费状态都会通过会员卡保存起来。如该顾客的累计消费金额以及有效期限。当该客户再次到商场消费时,如果顾客出示这张会员卡,商场就会根据这张会员卡进行一些处理,如计算折扣率、累计消费额等。

  Cookie是在浏览器访问Web服务器上的某个资源时,由Web服务器在HTTP消息响应头中附带的一组传送给浏览器的数据。浏览器可以根据这些数据决定是否将它们保存在客户端。实际上,Web服务器通过HTTP响应消息头的Set-Cookie字段将多个Cookie发送到浏览器,每一个Cookie使用一个Set-Cookie字段来设置。当浏览器发现HTTP响应消息头中有Set-Cookie字段时,就会根据Set-Cookie字段的值来决定如何处理这些Cookie,如将Cookie保存在硬盘上(永久Cookie);或是只在当前浏览器窗口中有效,如果当前窗口关闭,Cookie就会被删除(临时Cookie)。

  在浏览器中访问Web服务器的资源时,浏览器会检测本地和当前浏览器窗口中是否有满足当前访问路径的Cookie存在,如果有,则在HTTP请求消息头中通过Cookie字段将Cookie信息发送给Web服务器。也就是说,Cookie信息是通过HTTP响应消息头的Set-Cookie字段和HTTP请求消息头的Cookie字段在浏览器和Web服务器之间进行传递,可以利用Cookie的这一特性来跟踪服务端的对象,如Session对象就是利用Cookie来跟踪的(将在后面详细讲解)。

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

Cookie类

 

  5.3.2  Cookie类

  在Servlet API中,使用java.servlet.http.Cookie类来封装一个Cookie信息,在HttpServletResponse接口中定义了addCookie和getCookies方法可以用来处理Cookie信息。其中addCookie方法用来向浏览器传送Cookie信息,也就是添加Set-Cookie字段。getCookies方法返回一个Cookie数组,这个数组中保存了浏览器发送给Web服务器的所有Cookie信息。

  在Cookie类中定义了生成和提取Cookie信息的方法,这些方法如下:

  1.  构造方法

  Cookie类只有一个构造方法,它的定义如下:

  public Cookie(String name, String value)

  其中name表示Cookie的名称(在name参数中不能包含任何空格字符、逗号(,)、分号(;),并且不能以"$"字符开头),value表示Cookie的值。

  2.  getName方法

  该方法用于返回Cookie的名称。

  3.  setValue和getValue方法

  这两个方法分别用于设置和返回Cookie的值。

  4.  setMaxAge和getMaxAge方法

  这两个方法分别用于设置和返回Cookie在客户机的有效时间(以秒为单位)。如果有效时间为0,则表示当Cookie信息发送到客户端浏览器时立即被删除。如果设置为负数,则表示浏览器并不会把这个Cookie保存在硬盘上,这种Cookie被称为临时Cookie(保存在硬盘上的Cookie也被称为永久Cookie)。它们只存在于当前浏览器的进程中,当浏览器关闭后,Cookie自动失效。对于IE浏览器来说,不同的浏览器窗口不能共享临时Cookie,但按Ctrl+N键或使用JavaScript的windows.open语句打开的窗口由于和它们父窗口属于同一个浏览器进程,因此,它们可以共享临时Cookie.而在FireFox中,所有的进程和标签页都可以共享临时Cookie.

  5.  setPath和getPath方法

  这两个方法分别用于设置和返回当前Cookie的有效Web路径。如果在创建某个Cookie时未设置它的path属性,那么该Cookie只对当前访问的Servlet所在的Web路径及其子路径有效。如果要想使Cookie对整个Web站点中的所有可访问的路径都有效,需要将path属性设置为"/"。

  6.  setDomain和getDomain方法

  这两个方法分别用于设置和返回当前Cookie的有效域。

  7.  setComment和getComment方法

  这两个方法分别用于设置和返回当前Cookie的注释部分。

  8.  setVersion与getVersion方法

  这两个方法分别用于设置和返回当前Cookie的协议版本。

  9.  setSecure和getSecure方法

  这两个方法分别用于设置和返回当前Cookie是否只能使用安全的协议传送。

 
 

  5.3.3  读写Cookie信息与Cookie的中文问题

  在一些Web应用程序中需要在客户端保存一些信息,如用户名等,以使在下次访问同一个Web程序时可以根据这些信息为用户提供方便,或作为其他的用途。这些信息是由一个key-value对组成。每一对这样的信息被称为一个Cookie.如有一些登录程序在下一次访问时,会自动将用户名显示在用户名文本框中,这就是通过Cookie实现的。

  下面的例子演示了如何在Servlet中读、写Cookie信息。

  【实例5-6】  读写Cookie信息(包括英文和中文信息)

  1.  实例说明

  本程序通过WriteCookie和ReadCookie类来分别设置和读取Cookie信息。其中WriteCookie类设置了不同类型的Cookie,包括临时Cookie和永久Cookie.ReadCookie类在不同的情况下读取了这些Cookie信息,读者可以从中了解到不同类型Cookie的区别。

  2.  编写WriteCookie类

  package chapter5;

  import java.io.*;

  import javax.servlet.*;

  import javax.servlet.http.*;

  public class WriteCookie extends HttpServlet

  {

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

  {

  // 临时Cookie

  Cookie tempCookie = new Cookie("temp", "temporary cookie");

  tempCookie.setMaxAge(-1); // 设为临时Cookie,-1是MaxAge的默认值

  response.addCookie(tempCookie);

  // 这个cookie不起作用

  Cookie cookie = new Cookie("cookie", "deleted");

  // Cookie的有效时间设为0,浏览器接收以Cookie后立即被删除

  cookie.setMaxAge(0);

  response.addCookie(cookie);

  //  永久Cookie

  //  由于永久Cookie的值是中文,所以需要对其进行编码

  String chinese = java.net.URLEncoder.encode("将Cookie保存到硬盘上", "UTF-8");

  Cookie persistentCookie = new Cookie("pCookie", chinese);

  persistentCookie.setMaxAge(60 * 60 * 24); // 有效时间为1天

  // 设置有效路径,设为"/"表示这个Cookie在整个站点都是有效的

  persistentCookie.setPath("/");

  response.addCookie(persistentCookie);

  }

  }

  上面的代码分别建立了3种Cookie:临时Cookie、有效时间为0的Cookie和永久Cookie.其中临时Cookie只在当前的浏览器窗口有效。而将Cookie的有效时间设为0,则该Cookie不会起到任何作用,因为这种Cookie一传到浏览器就会被立即删除。浏览器会将永久Cookie保存在本地硬盘上,对于这种Cookie,即使是在新的浏览器窗口,只要在Cookie的有效期内,该Cookie就会永远有效。

  从上面的描述可以看出,Cookie的不同类型是根据有效时间的取值范围确定的,如下所示:

  lCookie有效时间 < 0:临时Cookie,只在当前浏览器窗口以及和该窗口处于同一个进程的窗口中有效。

  lCookie有效时间 = 0:该Cookie会立即被浏览器删除。

  lCookie有效时间 > 0:永久Cookie,在Cookie有效时间内有效。

  3.  编写ReadCookie类

  package chapter5;

  import java.io.*;

  import javax.servlet.*;

  import javax.servlet.http.*;

  public class ReadCookie extends HttpServlet

  {

  // 通过一个Cookie名获得Cookie对象,未找到指定名的Cookie对象,返回null

  private Cookie getCookieValue(Cookie[] cookies, String name)

  {

  if (cookies != null)

  {

  for (Cookie c : cookies)

  {

  if (c.getName()。equals(name))

  return c;

  }

  }

  return null;

  }

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

  {

  response.setContentType("text/html; charset=UTF-8");

  PrintWriter out = response.getWriter();

  // 获得临时Cookie ,getCookies方法获得一个保存了请求消息头中所有Cookie的数组

  Cookie tempCookie = getCookieValue(request.getCookies(), "temp");

  if (tempCookie != null)

  out.println("临时Cookie值:" + tempCookie.getValue() + "<br/>");

  else

  out.println("临时Cookie值:null</br>");

  // 这个Cookie永远不可能获得,因为它的MaxAge为0

  Cookie cookie = getCookieValue(request.getCookies(), "cookie");

  if (cookie != null)

  out.println("cookie:" + cookie.getValue() + "<br/>");

  else

  out.println("cookie:null</br>");

  // 获得永久Cookie

  Cookie persistentCookie = getCookieValue(request.getCookies(), "pCookie");

  if (persistentCookie != null)

  //  对永久Cookie中保存的中文信息进行解码

  out.println("persistentCookie:" +

  java.net.URLDecoder.decode(persistentCookie.getValue(), "UTF-8"));

  else

  out.println("persistentCookie:null!");

  }

  }

  在上面的代码中分别读取了在WriteCookie类中设置的3个Cookie.由于HttpServletRequest接口中未提供通过Cookie名返回特定Cookie对象的方法,因此,在ReadCookie类中增加了一个getCookieValue方法,用于返回指定Cookie名的Cookie对象。

  4.  在同一个浏览器窗口中测试

  在浏览器地址栏中依次输入如下两个URL:

  http://localhost:8080/demo/WriteCookie

  http://localhost:8080/demo/ReadCookie

  浏览器中显示的结果如图5.7所示。

图5.7  在同一个浏览器窗口读取Cookie值

  实际上,在访问WriteCookie时,addCookie方法向HTTP响应消息头添加了如下的3个Set-Cookie字段:

  Set-Cookie: temp="temporary cookie"

  Set-Cookie: cookie=deleted; Expires=Thu, 01-Jan-1970 00:00:10 GMT

  Set-Cookie:pCookie=%E5%B0%86Cookie%E4%BF%9D%E5%AD%98%E5%88%B0%E7%A1%AC%E7%9B%98%E4%B8%8A; Expires=Tue, 24-Jun-2008 14:43:23 GMT; Path=/

  5.  在不同的浏览器窗口中测试

  打开一个新的浏览器窗口,在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/ ReadCookie

  浏览器中显示的结果如图5.8所示。

图5.8  在不同的浏览器窗口读取Cookie值

  从上面的测试结果可以看出,在新的浏览器窗口中,并不存在这个临时Cookie,因此,临时Cookie的值为null.

  6.  程序总结

  在WriterCookie类中使用了setPath方法设置的Cookie有效路径。假设使用setPath设置的路径为"/path",如果通过localhost访问Web资源。要想该Cookie有效,访问Web资源的URL的前半部分必须是"http://localhost:8080/path".如下面的URL所指的Servlet可以访问这个Cookie:

  http://localhost:8080/path/servlet/MyServlet

  实际上,这个路径就相当于上下文路径,如果只想让某个Cookie在当前Web应用程序中有效,可以使用HttpServletRequest接口的getContextPath方法返回值来设置这个Cookie的有效路径。

  在使用Cookie时,还有一点需要注意,由于Cookie只支持ISO-8859-1编码格式的字符,因此,不能直接向Cookie中写入中文,如果要让Cookie支持中文,需要将中文字符编码。如Base64编码、URL格式的UTF-8编码。在本例中采用的是URL格式的UTF-8编码。

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

处理Session

 

  5.4  处理Session

  Session是服务端的对象,用于保存当前会话中用户的数据。浏览器可以通过Cookie机制来跟踪Session对象,如果浏览器将Cookie功能关闭,或不支持Cookie,则可以采用重写URL的方式来跟踪Session对象。

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

什么是Session

 

  5.4.1  什么是Session

  虽然可以使用Cookie或URL请求参数在不同请求中传递信息,但如果传递的信息较多的话,将极大地降低网络传输效率和消耗网络带宽,同时也会增大在服务端程序的处理难度。就算这些都可以承受,使用Cookie或URL请求参数传递的信息量也是非常有限的。为此,在服务端的开发方案中提供了一种将用户会话中产生的大量信息保存在服务端的技术,这就是Session技术。

  如果将Cookie比喻成商场里的会员卡,在会员卡里记录着顾客以前的消费累计金额和有效期限,那么就可以将Session比喻成银行卡,在银行卡中只保存了用户的帐号,而一些敏感信息以及很多的历史信息则保存在银行的服务器中,如密码、存款和取款记录、利率等。当储户在发生银行业务时,如取钱时,只需要提供银行卡(号),银行的系统就会通过银行卡中的帐号找到该用户的银行卡信息,在通过密码验证后,就可以进行正常的操作了,如提款、查询信息等。

  从上面的描述可以知道,服务端Session保存了某个客户的一些信息(银行卡信息),但该客户必须提供一个Session标识(银行帐号)才可以锁定这个Session对象。在Web应用程序中,该Session标识实际上就是一个临时Cookie,对于Java Web程序来说,该临时Cookie的key是JSESSIONID,value是一个唯一标识Session的字符串,也被称为Session ID.正是由于Session ID在Web服务器和浏览器之间不断地传送,浏览器才能找到服务端以前为某个用户创建的Session对象。如果在新的浏览器窗口再次访问服务端程序,由于临时Cookie丢失,因此,在这个新的浏览器窗口也就无法再使用这个Session对象了。从表面上看好象是Session对象被删除,但实际上只是由于Session ID丢失从而导致Session对象的丢失(这就相当于银行卡丢失,从而导致和该银行卡相关的信息无法取到,但这些信息并未丢失,只要再次提供银行卡或正确的银行帐号,就可以找到这些信息)。

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

Http Session接口中的方法

 

  5.4.2  Http Session接口中的方法

  一个Session就是一个HttpSession对象。实际上,HttpSession是一个接口,在Servlet引擎中实现了这个接口。在HttpSession接口中定义了若干的方法来操作HttpSession对象,这些方法如下:

  1.  getId方法

  getId方法用于返回当前HttpSession对象的SessionID值。要注意的是,SessionID是由系统自动生成的,该值可以唯一标识Web服务器中的Session对象。因此,在HttpSession接口中并未定义setId方法来设置这个SessionID值。

  2.  getCreationTime方法

  getCreationTime方法用于返回当前的HttpSession对象的创建时间,返回的时间是一个自1970年1月1日的0点0分0秒开始计算的毫秒数。

  3.  getLastAccessedTime方法

  getLastAccessedTime方法用于返回当前HttpSession对象的上一次被访问的时间,返回的时间格式是一个自1970年1月1日的0点0分0秒开始计算的毫秒数。

  4.  setMaxInactiveInterval和getMaxInactiveInterval方法

  这两个方法分别用来设置和返回当前HttpSession对象的可空闲的最长时间(单位:秒),这个时间也就是当前会话的有效时间。当某个HttpSession对象在超过这个最长时间后仍然没有被访问,该HttpSession对象就会失效,整个会话过程就会结束。如果有效时间被设置成负数,则表示会话永远不会过期。

  5.  isNew方法

  isNew方法用来判断当前的HttpSession对象是否是新创建的,如果是新创建的,则返回true,否则返回false. 在以下两种情况下,isNew返回true.

  (1)在请求消息中不包含SessionID,这时调用getSession方法返回的HttpSession对象一定是新创建的。

  (2)在请求消息中包含SessionID,但这个SessionID在服务端没有找到与其匹配的HttpSession对象。发生这种情况的原因可能是HttpSession对象失效或客户端发送了错误的SessionID.

  6.  invalidate方法

  invalidate方法用于强制当前的HttpSession对象失效,这样Web服务器可以立即释放该HttpSession对象。虽然会话在有效时间后会自动释放,但为了减少服务器的HttpSession对象的数量,节省服务端的资源开销,建议在不需要某个HttpSession对象时显式地调用invalidate方法,以尽快释放HttpSession对象。

  7.  getServletContext方法

  getServletContext方法用于返回当前HttpSession对象所属的Web应用程序的ServletContext对象。这个方法和GenericServlet接口的getServletContext方法返回的是同一个ServletContext对象。

  8.  setAttribute方法

  setAttribute方法用于将key-value对保存在Session域中,该方法和HttpRequestServlet接口中的setAttribute方法类似。如果value为null,则从Session域中删除该key-value对。

  9.  getAttribute方法

  getAttribute方法用于返回Session域中指定key的value值。如果Session域中不存在指定的key,则返回null.

  10.  remoteAttribute方法

  remoteAttribute方法用于根据key删除Session域中某一个key-value对。该方法和使用setAttribute方法时value为null的效果相同。在如下两种情况,系统会自动删除Session域中的对象:

  (1)当调用invalidate方法使当前HttpSession对象失效后,系统将自动删除保存在当前Session域中的所有对象。

  (2)使用setAttribute方法添加一个key-value对,如果key已经存在,并且value和已经存在的key所对应的value不同时,系统会先删除原来的key-value对,再添加新的key-value对。

  11.  getAttributeNames方法

  getAttributeNames方法用于返回一个Enumeration对象,该对象包含了当前Session域中的所有key值。可以通过扫描该Enumeration对象来获得当前Session域中的所以key值,并通过getAttribute方法来获得它们的value值。

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

Http Request Session接口中的Session方法

 

  5.4.3  Http Request Session接口中的Session方法

  在Http Request Session接口中也定义了若干和Session有关的方法,这些方法如下:

  1.  get Session方法

  get Session方法用于根据当前的请求返回Http Session对象,该方法有两种重载形式,它们的定义如下:

  public Http Session get Session();

  public Http Session get Session(boolean create);

  调用第一种重载形式时,如果在请求消息中包含SessionID,就根据这个SessionID返回一个HttpSession对象,如果在请求信息中不包含SessionID,就创建一个新的HttpSession对象,并返回它。在调用第二种重载方法时,如果create参数为true时,则等同于第一种重载形式。如果create为false时,当请求信息中不包含SessionID时,并不创建一个新的HttpSession对象,而是直接返回null.

  2.  is Requested SessionId Valid方法

  当请求消息中包含的Session ID所对应的Http Session对象已经超过了有效时间,也就是说Http Session对象无效,is Requested SessionIdValid方法返回false.否则返回true(当请求消息中不包含SessionID时,is Requested SessionId Valid返回false)。

  3.  is Requested SessionId From Cookie方法

  isRequested SessionIdFrom Cookie方法用于判断Session ID是否通过HTTP请求消息中的Cookie头字段传递过来的,如果该方法返回true,则表示SessionID是通过Coolie头字段发送到服务端的。

  4.  isRequested SessionId FromURL方法

  is Requested SessionId From URL方法用于判断SessionID是否通过HTTP请求消息的URL请求参数传递过来的。在使用这个方法时要注意,还有一个isRequested SessionId FromUrl方法和这个方法的功能完全一样,只是最后的URL变成了Url.这个方法已经被加了@deprecated标记,也就是并不建议使用。因此,建议使用isRequested SessionId From URL方法来实现这个功能。

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

通过重写URL跟踪Session

 

  5.4.5  通过重写URL跟踪Session

  如果用户把浏览器的Cookie功能关闭,或者浏览器不支持Cookie功能,那么SessionId就不能通过Cookie向服务端发送了。Servlet规范为了弥补这个不足,允许通过URL请求参数来发送SessionId.这样当浏览器的Cookie功能关闭时,在浏览器中仍然可以通过由URL请求参数发送的SessionId在服务端找到相应的HttpSession对象。

  在下面的例子演示了如何通过URL的请求参数来发送SessionId,读者可以从该例子中学到如何通过重写URL的方式来跟踪Session对象。

  【实例5-7】  通过重写URL跟踪Session

  1.  实例说明

  本例使用HttpResponseServlet接口的encodeURL方法重写了URL,发在浏览器未关闭Cookie和关闭Cookie的情况下测试本例中的Servlet.当关闭浏览器的Cookie功能时,encodeURL方法会在URL后面加一个jsessionid参数,该参数的值就是SessionId.

  2.  编写RewriteURL类

  public class RewriteURL extends HttpServlet

  {

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

  {

  HttpSession session = request.getSession();

  response.setContentType("text/html; charset=UTF-8");

  RequestDispatcher rd = request.getRequestDispatcher("HeaderInfo");

  //  包含HeaderInfo的内容

  rd.include(request, response);

  PrintWriter out = response.getWriter();

  //  重写URL

  out.println("<br/><br/><a href='" +

  response.encodeURL("RewriteURL?param=value") + "'>RewriteURL</a>");

  }

  }

  在上面的代码中包含了HeaderInfo,以显示请求消息头的内容。重写URL可以使用HttpResponseServlet接口中的encodeURL方法,在本例中,使用encodeURL方法将访问RewriteURL的URL进行了重写。

  3.  测试未关闭Cookie功能的情况

  假设本机的IP是192.168.18.212,在浏览器地址栏中输入如下的URL:

  http://192.168.18.212:8080/demo/RewriteURL

  在第一次访问上面的URL时,encodeURL方法在重写URL时会加入jsessionid参数,这是因为当第一次访问这个URL时,HTTP请求消息头的Cookie字段中并没有叫JSESSIONID的Cookie,所以encodeURL会在URL中加入jsessionid参数,如图5.9所示。

图5.9  第一次访问RewriteURL,在URL后添加了jsessionid参数

  当在同一个浏览器窗口再次访问上面的URL时, jsessionid参数就不会在URL中出现了,这是由于浏览器已经在HTTP请求消息头的Cookie字段中加入了叫JSESSIONID的Cookie,就不需要在URL中传递SessionId了,如图5.10所示。

图5.10  再次访问RewriteURL时,重写URL时不再添加jsessionid参数

  从这一点可以看出,encodeURL方法将根据HTTP请求消息头中是否有叫JSESSIONID的Cookie来决定是否在被重写的URL中加入jsessionid参数。

  4.  测试关闭Cookie功能的情况

  在IE中单击"工具"|"Internet选项"命令,打开"Internet选项"对话框,选中"隐私"页面 ,单击"高级"按钮,打开"高级隐私策略对话框"对话框,并按着如图5.11所示来设置。

图5.11  关闭Cookie功能

  在浏览器地址栏中输入如下的URL:

  http://192.168.18.212:8080/demo/RewriteURL

  无论访问多少次上面的URL,在URL上都会加上一个jsessionid参数,也就是说,效果都会和5.9一样。

  5.  程序总结

  如果要使用URL参数来传递SessionId,不要选中图5.20所示的"总是允许会话cookie"复选框。如果选中,虽然Cookie被禁止,但却可以保存临时Cookie,由于JSESSIONID也是临时Cookie,所以在这种情况下,JSESSIONID仍然可以在客户端和服务端之间传递。

  还要提一点的是jsessionid并不是请求参数,这个参数和RewriteURL使用了分号分隔(;)分隔,它是请求URL的一部分,可以使用HttpServletRequest的getRequestURI方法获得。如果URL含有请求参数,格式类似下面的形式:

  http://192.168.18.212:8080/demo/RewriteURL;jsessionid=AB130896860CD37902CDEDEB63A372B5?param=value

  如果使用localhost访问RewriteURL,就算关闭了Cookie功能,仍然可以使用临时Cookie,因此,用localhost来访问RewriteURL时,无论Cookie功能是否被关闭,都可以使用Cookie来传递SessionId,如下面的URL所示:

  http://localhost:8080/demo/RewriteURL

 
 
 
 

第 5 章:Servlet高级技术作者:李宁    来源:希赛网    2014年03月07日

 

小结

 

  5.5  小结

  本章介绍了Servlet的高级功能。在Servlet API中有两个非常重要的接口:HttpServletResponse和HttpServletRequest,这两个接口分别作为service方法的两个参数的类型。在这两个方法中定义了操作HTTP响应消息和HTTP请求消息的各种方法。通过这些方法,可以实现更复杂的Web应用程序,如动态下载文件、客户端身份验证等。

  除了上述的两个接口,Cookie和Session也是Web应用程序中经常使用的两种技术。其中Cookie是保存在客户端的信息。浏览器在每次访问Web资源时,都会检测在本机是否有对该Web资源有效的Cookie,如果存在这种Cookie,就会通过HTTP响应消息头的Cookie字段将这些Cookie信息发送到服务端。在服务端,也可以通过HTTP响应消息头的Set-Cookie字段向浏览器发送Cookie信息,并通知浏览器如何处理这些Cookie信息。

  Session是服务端的对象(HttpSession对象)。为了将浏览器和服务端的HttpSession对象进行关联,客户端就需要一个SessionId值,这个SessionId值就是服务端唯一表示HttpSession对象的key值。客户端可以通过Cookie字段或URL的jsessionid参数进行传递。如果客户端发送的SessionId值在服务端存在和其对应的HttpSession对象,那么服务端就会取出这个HttpSession对象,并做进一步处理。如果不存在这个HttpSession对象,服务端要么创建一个新的HttpSession对象,要么什么都不做(getSession方法返回null),继续处理其他的服务端逻辑。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

认识JSP

 

  第6章 JSP基础

  在很多动态网页中,绝大部分的内容是固定不变的,只有少量的动态内容由服务端生成。在这种情况下虽然可以使用Servlet来向客户端输出静态的和动态的内容,但这样就会使Servlet的控制逻辑和输出的网页代码混合在一起,非常不利于代码的编写和维护。而且大多数时候,需要使用网页设计工具对网页静态部分进行可视化的设计,而将网页的静态部分混在Servlet的代码中就无法使用网页设计工具进行页面设计了。

  为了更好地实现网页的静态部分和动态部分的组成,Sun公司在1999年诞生了JSP(Java Server Pages)技术。JSP实际上就是一种动态的网页。在JSP页面中即可以有客户端代码,如HTML代码,JavaScript等,也可以有动态的代码,如Java代码、EL、JSTL等。系统在运行JSP程序时,会自动将JSP页面中的静态和动态的部分分离。而在设计JSP页面时就象在设计HTML页面一样,也可以使用可视化的网页设计器来完成页面的布局等工作。JSP的出现也大大简化了用Servlet生成页面的工作, 从而使Servlet只专注于生成不需要复杂界面的工作,或是向客户端提供不包含界面元素的数据。总之一句话,如果需要复杂界面的Web程序,就使用JSP,否则,应该使用Servlet来实现它。

  6.1  认识JSP

  在第3章已经给出了一个简单的JSP程序。但该程序是在IDE中编写的。虽然在IDE中开发JSP程序非常方便,但这并不利于充分地了解JSP的发布过程、运行原理和其他的一些细节。因此,在本节将脱离IDE,使用手工的方式编写一些简单的JSP程序,并揭示Tomcat如何来运行这些JSP程序,以及由JSP页面生成的Servlet类的结构。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

初次接触JSP

 

  6.1.1  初次接触JSP

  在第3章已经介绍使用IDE开发JSP程序的过程。从其中的JSP页面可以看出,JSP页面是由静态和动态两部分组成。静态部分主要是HTML、CSS、JavaScript等客户端脚本。而动态部分主要是在服务端运行的程序,如使用<% … %>或<%=…%>包含的Java代码,以及使用${…}包含的EL表达式等。由于JSP在首次运行时被翻译成Servlet(将在6.1.3节详细介绍),因此,整个JSP页面翻译时都被转换成相应的Java代码,并插入到由JSP生成的Servlet中。

  JSP页面中所有的静态部分使用out.write方法直接发送给客户端,而动态部分根据具体的内容进行相应的转换,如在<%…%>中的Java代码被直接插入到Servlet中,而<%=…%>中的Java代码使用out.println方法输出。

  虽然JSP在运行时被翻译成Servlet,但在访问JSP时和访问静态的HTML页面类似,也就是说,可以直接在浏览器中访问。jsp页面,Web服务器会根据所访问的JSP页面去自动调用由该JSP页面生成的Servlet。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

编写简单的JSP程序

 

  6.1.2  编写简单的JSP程序

  手工编写一个JSP程序要比编写一个Servlet容易得多,只需要建立一个空的目录,然后在目录中建立JSP文件即可。

  在<Tomcat安装目录>\webapps目录中建立一个myjsp目录,并在该目录中建立一个simple.jsp文件(文件要以UTF-8格式保存),simple.jsp的主要功能是使用Java代码显示服务器的当前时间,并输出name请求参数的值。simple.jsp的代码如下:

  <%@ page language="java" contentType="text/html; charset=UTF-8"

  pageEncoding="UTF-8"%>

  <!--  引用Java类  -->

  <%@page import="java.text.SimpleDateFormat, java.util.Date"%><html>

  <head>

  <title>简单的JSP程序</title>

  </head>

  <body>

  当前日期和时间:

  <%

  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

  //  输出服务器的当前时间

  out.println(dateFormat.format(new Date()));

  %>

  <p/>

  <!--  输出name请求参数值  -->

  name请求参数值:<%= request.getParameter("name") %>

  </body>

  </html>

  simple.jsp页面中的Java代码分别被写在了<%…%>和<%=…%>中,使用这两种格式编写的Java代码在JSP被翻译成Servlet时采用了不同的处理方法。

  运行<Tomcat安装目录>\bin\ startup.bat命令,启动Tomcat,并在浏览器地址栏中输入如下的URL:

  http://localhost:8080/myjsp/simple.jsp?name=bill

  浏览器显示的信息如图6.1所示。

图6.1  显示服务器当前时间和name请求参数值

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

改变JSP的访问路径

 

  6.1.3  改变JSP的访问路径

  虽然web.xml文件在JSP页面运行的过程中并不是必须的,但仍然可以在web.xml文件中配置JSP,以改变JSP页面的访问路径。

  配置JSP的方法和配置Servlet的方法类似,只是将<servlet-class>元素替换成了<jsp-file>元素,以便指定JSP文件相对于Web应用程序的目录。如下面的配置将simple.jsp的访问路径配置成了"/jsp/simple.html":

  <servlet>

  <servlet-name>simple</servlet-name>

  <jsp-file>/simple.jsp</jsp-file>

  </servlet>

  <servlet-mapping>

  <servlet-name>simple</servlet-name>

  <url-pattern>/jsp/simple.html</url-pattern>

  </servlet-mapping>

  在web.xml文件中输入上面的配置代码后,重启Tomcat,在浏览器地址栏中输入如下的URL:

  http://localhost:8080/myjsp/jsp/simple.html

  浏览器将会显示如图6.1所示的信息。由此可以,上面的URL和输入如下的URL的效果相同:

  http://localhost:8080/myjsp/simple.jsp

  在设置JSP访问路径时要注意,<jsp-file>元素的值必须以"/"开头,表示当前Web应用程序的根目录。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

JSP基本语法

 

  6.2  JSP基本语法

  在JSP页面中除了可以包含客户端代码(HTML、JavaScript等)外,还可以包含很多服务端的代码,如JSP表达式、Java代码、JSP声明、JSP指令等,通过这些在服务端执行的代码,可以完成各种各样复杂的功能。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

JSP表达式

 

  6.2.1  JSP表达式

  实际上,JSP表达式也是Java代码,只是这些Java代码被放到了<%= … %>中。JSP编译器在翻译JSP表达式时,直接将<%= … %>中的内容作为Java变量或表达式使用println方法输出到客户端。也就是说,将<%= … %>中的内容翻译成println方法的参数值,而不是直接插入到由翻译JSP生成的Servlet类中。看下面的JSP表达式:

  <%= (3+4) * 5 %>

  JSP编译器会将上面的JSP表达式翻译成如下的Java代码:

  out.println((3+4) * 5);

  要注意的是,<%= … %>中的Java代码必须是println方法的参数的合法值,也就是说,如果将JSP表达式中的Java代码作为println方法的参数值,Java编译器会编译出错,那么该JSP表达式就是错误的。如不能在<%= … %>中的Java代码后加分号(;)。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

在JSP中嵌入Java代码

 

  6.2.2  在JSP中嵌入Java代码

  除了通过JSP表达式在JSP页面中嵌入Java代码外,还可以通过<% … %>在JSP页面中直接嵌入Java代码。所有写在<% … %>之间的内容JSP编译器都会将其认为是Java代码,并直接将其插入由JSP页面生成的Servlet类的_jspService方法中,如下面的JSP代码:

  <%

  int n = (3 + 4) * 5;

  %>

  上面的代码在JSP页面中嵌入了一行非常简单的Java代码,这行Java代码会被直接放到_jspService方法(相当于Servlet中的service方法)的相应位置。因此,<% … %>之间的内容必须是合法的Java代码,例如,每条Java语句后面必须加分号(;),否则,访问该JSP页时会抛出异常。

  在JSP页面中嵌入Java代码时有如下两点需要注意:

  可以在JSP页面的任何位置使用<% … %>插入Java代码。<% … %>可以有任意多个。

  每一个<% … %>中的代码可以不完整,但是该<% … %>中的内容和JSP页面中的一个或多个<% … %>中的内容组合起来必须完整。

  下面的代码将一条Java语句分拆到多个<% … %>中,并在<% … %>之间包含静态的内容:

  <!--  javacode.jsp  -->

  <%@ page language="java" pageEncoding="UTF-8"%>

  <html>

  <head>

  <title>在JSP中嵌入多段不完整的Java代码,但整个JSP页面的Java代码必须完整</title>

  </head>

  <body>

  <!--  第一段Java代码  -->

  <%

  java.util.Random rand = new java.util.Random();

  int gradeList[] = new int[5];

  for(int i = 0; i < 5; i++)

  gradeList[i] = rand.nextInt(100);

  for(int i = 0; i < 5; i++)

  {

  out.println(gradeList[i]);

  out.println("&nbsp;&nbsp;");

  if(gradeList[i] >= 90)

  {

  %>

  优秀

  <!--  第二段Java代码  -->

  <%

  }

  else if(gradeList[i] >= 80 && gradeList[i] < 90)

  {

  %>

  良好

  <!--  第三段Java代码  -->

  <%

  }

  else if(gradeList[i] >= 60 && gradeList[i] < 80)

  {

  %>

  及格

  <!--  第四段Java代码  -->

  <%

  }

  else

  {

  %>

  不及格

  <!--  第五段Java代码  -->

  <%

  }

  out.println("<br>");

  }

  %>

  </body>

  </html>

  在上面的代码中通过5对<%…%>,将嵌入的Java代码分成了5部分,这5部分中的Java代码的每一部分都不完整,但如果将它们合起来就是完成的Java代码。在这5对<%…%>之间是JSP页面中的静态部分,这部分在JSP页面被翻译成Servlet类时使用write方法直接输出到客户端。

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/chapter6/javacode.jsp

  浏览器显示的信息如图6.2所示。

图6.2  多对<%…%>中的内容组合成完整的Java代码

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

JSP声明

 

  6.2.3  JSP声明

  虽然使用<%…%>可以将任何Java代码直接插入到_jspService方法中,但是如果想在_jspService方法外插入Java代码,<%…%>却无能为力。要想达到这个目的,就必须要使用JSP声明。JSP声明中的Java代码被封装在<%!…%>中。所有在<%!…%>中的Java代码都会被作为Servlet类的全局部分插入到_jspService方法外。如下面的JSP代码所示:

  <!--  declare.jsp  -->

  <!-- JSP声明  -->

  <%!

  static

  {

  System.out.println("正在装载Servlet!");

  }

  private int globalValue = 0;

  public void jspInit()

  {

  System.out.println("正在初始化JSP!");

  }

  public void jspDestroy()

  {

  System.out.println("正在销毁JSP!");

  }

  %>

  <!--  在_jspService方法中插入Java代码  -->

  <%

  int localValue = 0;

  %>

  globalValue:<%= ++globalValue %><br>

  localValue:<%= ++localValue %>

  在上面的JSP代码中使用JSP声明做了如下3件事:

  在Servlet类中插入了一个静态块(static {…})。

  定义了一个全局的int类型变量globalValue,并将该变量初始化为0.

  覆盖了由JSP生成的Servlet类中的jspInit和jspDestroy方法。这两个方法分别在该Servlet对象初始化和销毁时被调用。

  declare.jsp页面将被JSP引擎翻译成如下的Servlet类源代码:

  … …

  public final class declare_jsp extends org.apache.jasper.runtime.HttpJspBase

  implements org.apache.jasper.runtime.JspSourceDependent

  {

  static

  {

  System.out.println("正在装载Servlet!");

  }

  private int globalValue = 0;

  public void jspInit()

  {

  System.out.println("正在初始化JSP!");

  }

  public void jspDestroy()

  {

  System.out.println("正在销毁JSP!");

  }

  … …

  public void _jspService(HttpServletRequest request, HttpServletResponse

  response)  throws java.io.IOException, ServletException

  {

  … …

  int localValue = 0;

  out.write("\r\n");

  out.write("globalValue:");

  out.print(++globalValue);

  out.write("<br>\r\n");

  out.write("localValue:");

  out.print(++localValue);

  … …

  }

  }

  从上面的Servlet源代码可以看出,所有<%!…%>中的内容都被作为declare_jsp类的全局部分插入到declare_jsp类中。

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/chapter6/declare.jsp

  当首次访问上面的URL时,Tomcat控制台会输出如下的信息:

  正在装载Servlet!

  正在初始化JSP!

  浏览器将显示如下的信息:

  globalValue:1

  localValue:1

  在多次访问上面的URL后,由于globalValue是declare_jsp的全局变量,因此,在declare_jsp对象未被销毁之前,globalValue变量的值在每刷新一次页面时就会增1,而localValue是在_jspService方法定义的局部变量,因此,localValue变量始终是1.

  修改declare.jsp页面中的内容(只要加几个空格或回车即可,目的就是该变declare.jsp页面的修改时间),然后再次访问上面的URL,Tomcat控制台会输出如下的信息:

  正在销毁JSP!

  正在装载Servlet!

  正在初始化JSP!

  从上面的输出信息可以看出,当修改JSP页面后,再次访问该JSP页面,JSP引擎会重新将该JSP页面翻译成Servlet,而Servlet引擎会先销毁以前由该JSP页面生成的Servlet的对象实例,然后再重新装载该Servlet。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

JSP中的注释

 

  6.2.4  JSP中的注释

  在JSP代码中有3种注释:JSP注释、Java注释和HTML注释。

  1.  JSP注释

  这种注释的格式如下:

  <%-- JSP注释 --%>

  JSP引擎在处理JSP代码时,会忽略JSP注释。也就是说,JSP注释既不会出现在由JSP生成的Servlet类中,也不会被作为静态内容输出到客户端。JSP注释的作用只是为了使JSP代码更容易理解。

  2.Java注释

  Java注释就是Java源代码的注释。该注释可以在<% … %>或<%=…%>中的Java代码中使用。JSP引擎在翻译JSP页面时会将Java注释直接插入到由JSP生成的Servlet类中。下面是Java注释的例子代码:

  <%= (4+5) /*  Java注释  */ %>

  <%

  /*  Java注释1  */

  //  Java注释2

  %>

  3.HTML注释

  HTML注释的格式如下:

  <!--  HTML注释 -->

  JSP引擎在处理这类注释时,将它们和其他的JSP静态内容一起使用write方法输出到客户端。也就是说,HTML注释将被当成JSP代码中的静态内容处理。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

JSP指令

 

  6.3  JSP指令

  在JSP规范中还定义了另外一种JSP元素:JSP指令(directive)。JSP指令也会被JSP引擎转换为相应的Java代码。但这些Java代码并不直接产生任何可见输出,而是告诉JSP引擎如何处理JSP页面,或是如何生成Servlet类。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

JSP指令简介

 

  6.3.1  JSP指令简介

  JSP指令的语法格式如下:

  <%@ 指令 属性名 = "值" %>

  在JSP2.0规范中提供了3个指令:page、include和taglib(这个指令将在后面的章节详细介绍)。每种指令都定义了若干属性。根据具体的需求,每个指令可以选择只使用一个属性,也可以选择多个属性的组合。如下面2条page指令分别设置了contentType和pageEncoding属性:

  <%@ page  contentType="text/html"  %>

  <%@ page  pageEncoding="UTF-8"  %>

  上面的两条指令也可以写成一条page指令,如下面的代码所示:

  <%@ page  contentType="text/html"  pageEncoding="UTF-8"  %>

  在使用JSP指令时应注意。JSP指令和属性名都是大小写敏感的。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

page指令

 

  6.3.2  page指令

  page指令用于设置JSP页面的各种属性。大多数的JSP页面中都包含page指令。虽然page指令可以出现在JSP页面的任何位置,但最好将page指令放到JSP页面的起始位置。page指令的完整语法格式如下:

  <%@ page

  [ language="java" ] [ extends="package.class" ]

  [ import="{package.class | package.*} , … " ]

  [ session="true|false" ]

  [ buffer="none| 8kb|sizekb" ] [ autoFlush="true|false" ]

  [ isThreadSafe="true|false" ] [ info="text" ]

  [ errorPage="relativeURL" ] [ isErrorPage="true| false" ]

  [ contentType="{mimeType [ ; charset=characterSet ] | text/html ; charset=ISO-8859-1}" ]

  [ pageEncoding="{characterSet | ISO-8859-1}" ]

  [ isELIgnored="true | false" ]

  %>

  上面定义中的每对方括号"[]"分别表示page指令的一个属性。属性值中用坚杠(|)分隔的不同部分为该属性可以设置的值,如session属性可以设置为true或false.属性值中黑体部分为该属性的默认值。在这些属性中,import属性是唯一允许出现多次的属性。下面是对page指令的所有属性的详细描述。

  1.  language属性

  language属性用来设置JSP页面所使用的开发语言(也就是在<% … %>和<%= … %>中所使用的语言)。由于目前JSP只支持Java,因此,language属性的值只能为java,而且这个值也是language属性的默认值。因此,可以不指定该属性。

  2.  extends属性

  extends属性设置了由JSP生成的Servlet类的父类。一般不需要设置这个属性。如果在某些特殊情况下非要设置这个属性,应该注意设置后可能会对JSP造成的影响。

  3.  import属性

  import属性指定在JSP页面被翻译成Servlet源代码后要导入的包或类。也就是翻译成Java中的import语句。在page指令中可以有多个import属性,如下面的page指令所示:

  <%@ page  import="java.util.*" import = "java.text.SimpleDateFormat" %>

  上面的page指令将被翻译成如下的Java代码:

  import java.util.*;

  import java.text.SimpleDateFormat;

  在page指令中不仅可以有多个import属性,还可以在一个import属性中使用逗号(,)分割不同的包或类。如上面的page指令也可以写成如下的形式:

  <%@ page  import="java.util.*, java.text.SimpleDateFormat"s %>

  4.  session属性

  session属性用于指定在JSP页面中是否可以使用内置session对象。session属性的默认值为true.也就是说,JSP在默认的情况下会自动创建HttpSession对象。

  5.  buffer属性

  buffer属性用于设置JSP中out对象的缓冲区大小,默认值是8kb.如果将buffer设为none,out对象则不使用缓冲区。还可以通过这个属性来自定义out对象的缓冲区的大小。但单位必须是kb,也就是说,buffer属性的值的最后两个字母必须是kb,而且必须是非负整数。如10kb,120kb等。将buffer属性的值设为0kb的效果和设为none是一样的。

  6.  autoFlush属性

  autoFlush属性用于设置当out对象的缓冲区已满时如何处理缓冲区中的内容,如果autoFlush属性的值为true,则当out对象的缓冲区已满时直接将缓冲区中的内容刷新到客户端。如果autoFlush属性的值为false,则对于已满的缓冲区,系统会抛出缓冲区溢出的异常。该属性其默认值为true.如果buffer属性的值为none或0kb,autoFlush属性的值不能为false.因为将buffer属性的值设为none或0kb,表明未使用缓冲区,也就相当于缓冲区永远是满的。这时将autoFlush属性值设为false,JSP引擎会在编译JSP页面时会产生一个内部编译错误。

  7.  isThreadSafe属性

  isThreadSafe属性用于设置JSP页面是否是线程安全的,该属性的默认值是true.当isThreadSafe属性值为true时,说明当前的JSP页面在设计时已经考虑到了线程安全(如全局共享的资源已经同步),并不需要Servlet引擎再来考虑这些问题了。当isThreadSafe属性为false时,由JSP页面翻译成的Servlet类会实现SingleThreadModel接口,也就是说,线程完全将由Servlet引擎来负责。

  8.  info属性

  info属性用于定义一个描述当前JSP页面的字符串信息。在翻译JSP页面时,info属性值被翻译成getServletInfo方法的返回值。看下面的JSP代码:

  <%@ page info ="输出info属性的值" contentType="text/html" pageEncoding="UTF-8"%>

  上面的JSP代码中的info属性将被翻译成getServletInfo方法的返回值,代码如下:

  … …

  public final class info_jsp extends org.apache.jasper.runtime.HttpJspBase

  implements org.apache.jasper.runtime.JspSourceDependent

  {

  //  info属性值实际上是getServletInfo方法的返回值

  public String getServletInfo()

  {

  return "输出info属性的值";

  }

  … …

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

  {

  … …

  }

  … …

  }

  在JSP页面中可以通过如下的代码来输出info属性的值:

  info属性的值:<%= getServletInfo() %>

  9.  errorPage属性

  errorPage属性用于指定处理当前JSP页面抛出的异常的页面。如果JSP页面抛出了未被捕获的异常,就会自动跳转到errorPage属性所指的页面。errorPage属性的值必须是相对路径。如果以"/"开头,表示相对于当前Web应用程序的根目录,否则,表示相对于当前JSP页面所在的目录。要注意的是,errorPage属性的值可以是JSP页面,也可以是静态的页面(如html、图象文件等)。

  10.isErrorPage属性

  isErrorPage属性指定当前JSP页面是否可用于处理其他JSP页面未捕获的异常。该属性的默认值为false.errorPage属性所指的异常处理JSP页面必须将isErrorPage属性设为true.否则,无法在异常处理页中使用exception对象。关于JSP页面异常的处理将在6.3.3节详细讲解。

  11.  contentType属性

  contentType属性用于设置响应消息头的Content-Type字段,该字段设置了响应正文的MIME类型和JSP页面中文本内容的字符集编码。contentType属性的默认MIME类型是text/html,默认字符集是ISO-8859-1.对于简体中文来说,可以将contentType属性设为如下两个值中的任何一个:

  <%@ page errorPage="error.jsp" contentType="text/html;  charset=GBK"%>

  <%@ page errorPage="error.jsp" contentType="text/html;  charset=UTF-8"%>

  12.  pageEncoding属性

  pageEncoding属性用于指定JSP页面中文本内容的字符集编码格式。如果指定了pageEncoding属性,contentType属性中的charset就不再表示JSP页面的字符集编码了。如果contentType属性中未指定字符集编码格式(也就是没有charset),pageEncoding属性同时还具有设置Content-Type字段中的字符集编码的作用(相当于设置了contentType属性的charset)。

  13.  isELIgnored属性

  isELIgnored属性用于设置JSP页面是否支持EL(表达式语言,Expression Language)。如果Web应用程序是遵循Servlet2.3或更低版本,isELIgnored属性的默认值为true(表示JSP页面在默认情况下不支持EL),如果遵循Servlet2.4或更高版本,isELIgnored属性的默认值为false(表示JSP页面在默认情况下支持EL)。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

JSP页面中的异常处理

 

  6.3.3  JSP页面中的异常处理

  JSP页面可以通过page指令的errorPage和isErrorPage属性进行异常处理。errorPage属性要用在抛出异常的JSP页面,该属性指定了处理异常的页面(一般是JSP页面)。generator_error.jsp页面是一个抛出异常的JSP页面,代码如下:

  <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" errorPage="deal_error.jsp"%>

  <%

  out.println("发生错误之前");

  //抛出java.lang.ClassNotFoundException异常

  Class.forName("NoExist");

  out.println("发生错误之后");

  %>

  在上面的代码中使用forName方法动态装载了一个不存在的NoExist类,因此会抛出ClassNotFoundException异常,如果不使用errorPage属性,异常信息将直接在访问generate_error.jsp页面时在浏览器中显示。但如果使用了errorPage属性,就可以在另外一个处理异常的JSP页面由开发人员决定如何处理抛出的异常。

  处理异常的页面为deal_error.jsp,代码如下:

  <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"

  isErrorPage="true"%>

  <%

  out.println("<font color='#FF0000'>异常信息</font><hr>");

  exception.printStackTrace(new java.io.PrintWriter(out));

  %>

  在deal_error.jsp页面中使用了page指令的isErrorPage属性。如果将该属性设为true,则JSP引擎在翻译deal_error.jsp页面时会建立exception对象,因此,在deal_error.jsp页面中可以直接使用exception对象。

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/chapter6/generate_error.jsp

  浏览器显示的信息如图6.3所示。

图6.3  显示抛出的异常信息

  除了通过errorPage属性指定处理异常的JSP页面外,还可以在web.xml文件中配置处理异常的JSP页面。由于_jspService方法只能抛出java.io.IOException和javax.servlet.ServletException异常,而且在抛出java.lang.RuntimeException异常时不需要在方法定义中显式地声明,因此,在web.xml文件中只能配置处理如下三种异常类及其子类的JSP页面:

  java.io.IOException

  javax.servlet.ServletException

  java.lang.RuntimeException

  除了上述3种异常,其他的异常将在_jspService内部进行处理。

  下面将在generate_error.jsp页面中编写如下的代码:

  <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

  <%

  if("servlet".equals(request.getParameter("error")))

  {

  throw new ServletException("Servlet异常");

  }

  else if("io".equals(request.getParameter("error")))

  {

  throw new java.io.IOException("IO异常");

  }

  else

  {

  int i = 1 / 0;

  }

  %>

  从上面的代码可以看出,在page指令中并未指定errorPage属性,这是因为如果指定errorPage属性,系统将会优先考虑errorPage属性的设置,也就是说,系统会使用errorPage属性所指的异常处理页面,而不会考虑在web.xml文件中配置的异常处理页面。因此,要想使用web.xml文件来配置异常处理页面,就不能在抛出异常的页面中的page指令中指定errorPage属性。

  新建一个处理异常的deal_error1.jsp页面,代码如下:

  <%@page import="java.io.PrintStream"%>

  <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isErrorPage="true"%>

  <%

  out.println("<font color='#FF0000'>异常信息(deal_error1.jsp)</font><hr>");

  out.println(exception.getMessage());

  exception.printStackTrace(new java.io.PrintWriter(out));

  %>

  为了处理上述三种异常,需要在web.xml文件中添加如下的配置代码:

  <error-page>

  <exception-type>javax.servlet.ServletException</exception-type>

  <location>/chapter6/deal_error1.jsp</location>

  </error-page>

  <error-page>

  <exception-type>java.io.IOException</exception-type>

  <location>/chapter6/deal_error1.jsp</location>

  </error-page>

  <error-page>

  <exception-type>java.lang.RuntimeException</exception-type>

  <location>/chapter6/deal_error1.jsp</location>

  </error-page>

  从上面的配置代码可以看出,使用了3个<error_page>元素分别用来配置上述3类异常的处理页面。在<error_page>元素中有两个子元素:<exception-type>和<location>,其中<exception-type>元素用来指定异常类名,<location>元素用来指定异常处理页的路径,必须以"/"开头,表示相对当前Web应用程序的根目录。

  读者可以在浏览器地址栏中输入如下三个URL来测试上述3种异常的处理情况:

  http://localhost:8080/demo/chapter6/generate_error.jsp?error=servlet

  http://localhost:8080/demo/chapter6/generate_error.jsp?error=io

  http://localhost:8080/demo/chapter6/generate_error.jsp

  处理Servlet异常时的输出结果如图6.4所示。

图6.4  处理Servlet异常

  <error-page>元素除了可以使用<exception-type>元素指定异常类外,还可以使用<error-code>元素指定HTTP响应状态码,如下面的配置代码所示:

  <error-page>

  <error-code>404</error-code>

  <location>/images/error.jpg</location>

  </error-page>

  上面的配置代码使用<error-code>元素设置了HTTP响应状态码404,这就意味着访问所有在服务端不存在的Web资源,从页产生404状态码的请求,都会交由<location>元素所指定的异常处理页面来处理。在该例中指定了一个图像文件(error.jpg),读者也可以在<location>元素中指定其他的Web资源,如HTML页面、JSP页面等。

  在使用web.xml文件配置异常处理页面时要注意,<error_code>和<exception-type>元素只能同时在一个<error-page>元素中出现一个。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

include指令

 

  6.3.4  include指令

  include指令用于将其他文件的内容合并到当前的JSP程序中。这种合并是静态的,也就是说,将其他文件的内容合并到由当前JSP页面生成的Servlet类中。include指令的语法格式如下:

  <%@ include file="relativeURL" %>

  include指令只有一个file属性。这个属性的值是一个相对路径,如果以"/"开头,则相对于Web应用程序的根目录,否则,相对于当前JSP页面所在的目录。在使用include指令时应注意以下几点:

  1.  被合并的文件可以是任何扩展名。但该文件的内容必须符合JSP页面的规范。因为JSP引擎会按着处理JSP页面的方式处理被引入的文件。

  2.  include指令是静态引入文件的,也就是说,被引入文件内容将成为由JSP所生成的Servlet类的一部分。

  3.  由于JSP引擎将合并被引入的文件与当前JSP页面中的指令,因此,除了page指令的import和pageEncoding属性外,其他的属性不能在当前的JSP页面和被引入的JSP页面中有不同的值。否则JSP引擎在翻译JSP页面时会抛出JasperException异常。

  4.  合并文件的过程是在JSP引擎翻译成Servlet的过程中进行的,因此,如果当前JSP页面和被引入的页面需要采用不同的字符集编码,必须在各自的页面单独设置。也就是说,当前页面设置的字符集编码并不代表被引入页面的字符集编码。

  5.  Tomcat会自动检测被引入页面是否被修改。如果被引入页面被修改,在访问当前页面时,JSP引擎会重新翻译当前页面。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

JSP的9个内置对象

 

  6.4  JSP的9个内置对象

  在JSP页面中为了访问系统的资源,提供了9个内置对象(也可以称为JSP隐含对象或JSP隐式对象),如用于访问用户请求消息的request对象,访问响应消息的response对象,向客户端输出信息的out对象等。这些对象和Servlet中相应的对象的使用方法类似。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

out对象

 

  6.4.1  out对象

  out对象用来向客户端输出信息。如下面的代码所示:

  <!--  jspout.jsp  -->

  <%@ page language="java" pageEncoding="UTF-8"  %>

  <%

  out.println("使用out对象输出<br>");

  java.io.PrintWriter myOut = response.getWriter();

  myOut.println("使用PrintWriter对象输出<br>");

  %>

  在上面的代码中,首先使用了out对象向客户端输出信息,然后调用了response对象的getWriter方法获得一个PrintWriter对象,并通过该对象的println方法向客户端输出信息。

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/chapter6/jspout.jsp

  浏览器显示的信息如图6.5所示。

图6.5  使用out和PrintWriter对象输出的信息

  从图6.5所示的信息可以看出,使用PrintWriter对象输出的信息显示在了使用out对象输出的信息的前面。这是因为out对象实际上通过pageContext对象的getOut方法获得的JspWriter对象,通过JspWriter对象输出的信息首先会被写入out对象的缓冲区,在满足如下两个条件中的一个时,系统会将out对象缓冲区中的内容写入Servlet引擎提供的缓冲区:

  整个JSP页面结束时。

  当前out对象缓冲区已满时。

  将out对象缓冲区中的内容写到Servlet引擎提供的缓冲区后,再通过PrintWriter对象将这些内容输出到客户端。也就是说,不管是JSP,还是Servlet,最终都是依靠PrintWriter对象向客户端输出信息的。

  从上面的程序可以看出,虽然一开始就使用了out对象输出信息,但这些信息都被写入out对象的缓冲区,而使用PrintWriter对象输出的内容则直接被写入了Servlet引擎提供的缓冲区,当整个页面结束时,系统会将out对象缓冲区中的内容写入Servlet引擎提供的缓冲区。因此,从写入Servlet引擎提供的缓冲区的顺序看,使用PrintWriter对象输出的信息要比使用out对象输出的内容更早地被写入Servlet引擎提供的缓冲区,这也就是为什么输出信息的顺序会和out及PrintWriter对象在JSP页面中的调用顺序正好相反的原因。

  如果想让输出顺序和JSP页面中的调用顺序保持一致,可以通过禁止out对象缓冲区的方法来解决,如下面的代码所示:

  <!--  jspout.jsp  -->

  <%@ page language="java" pageEncoding="UTF-8" buffer="none" %>

  <%

  out.println("使用out对象输出<br>");

  java.io.PrintWriter myOut = response.getWriter();

  myOut.println("使用PrintWriter对象输出<br>");

  %>

  上面的代码在page指令中加了一个buffer属性,并将该属性的值设为"none",也就是禁止out对象的缓冲区。这时再次访问jspout.jsp页面,就会看到信息的输出顺序改变了。

  由于buffer属性的默认值是8k,因此,当使用out对象输出的信息总量超过8k时,就算JSP页面未结束,也会将信息(out对象缓冲区中的8k的内容)写入Servlet引擎提供的缓冲区,并清空out对象的缓冲区。下面的JSP页面将buffer属性值设为1k(该值是buffer属性可设置的最小值),来模拟out缓冲区溢出的过程。

  <!--  jspbuffer.jsp  -->

  <%@ page language="java" pageEncoding="UTF-8" buffer="1kb" %>

  <%

  for(int i = 0; i < 1024; i++)

  out.println("x");

  java.io.PrintWriter myOut = response.getWriter();

  myOut.println("使用PrintWriter对象输出信息");

  %>

  下面的代码循环产生了1024个"x"字符,并通过out对象输出的客户端,在最后使用PrintWriter对象输出了一条信息。

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/chapter6/jspbuffer.jsp

  浏览器显示的信息如图6.6所示。

图6.6  out对象缓冲区溢出

  从图6.6所示的输出信息可以看出,使用PrintWriter对象输出的信息被夹在了1024个x字符中间。在该信息前面的x字符是out对象缓冲区未满时写入的。虽然用程序产生了1024个x,但由于JSP的静态部分(如页面开头的注释部分)也占用了一定的out对象缓冲区空间,因此,out对象缓冲区空间容纳的x字符数要小于1024,因此,会出现1024个x未被完全写入out对象的缓冲区,该缓冲区就溢出了的现象。

  当out对象缓冲区被第一次写满时,就会将该缓冲区的内容一次性地写入Servlet引擎的缓冲区,然后清空out对象缓冲区,并会再次写入剩余的x.因此,在使用PrintWriter对象输出信息之前,已经有1024个字节的信息被写入到了Servlet引擎的缓冲区。所以会出现图6.6所示的输出结果。

  由于JSP向客户端输出信息时使用了JspWriter对象(out对象),并且在out对象缓冲区被写入Servlet引擎的缓冲区后,Servlet引擎会使用PrintWriter输出缓冲区中的内容,因此,如果JSP页面中包含有静态内容,则无法使用ServletOutputStream对象来输出信息,否则会造成冲突,如下面的代码所示:

  <!--  jspstream.jsp  -->

  <%@ page language="java" pageEncoding="UTF-8" %>

  <%

  ServletOutputStream sos = response.getOutputStream();

  sos.println(new String("使用ServletOutputStream输出信息".getBytes("UTF-8"), "ISO-8859-1"));

  %>

  在上面的代码中使用了response.getOutputStream方法获得了一个ServletOutputStream对象,并通过该对象的println方法向客户端输出信息。

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/chapter6/jspstream.jsp

  浏览器将显示如图6.7所示的异常信息。

图6.7  使用ServletOutputStream对象输出信息时抛出的异常

  产生图6.7所示的异常的原因是由于jspstream.jsp页面包含了静态部分(注释、\r\n等),而这些注释部分最终要通过PrintWriter输出到客户端,但在jspstream.jsp页面中又使用了ServletOutputStream对象,在前面讲过,不能同时使用ServletOutputStream和PrintWriter对象向客户端输出信息。因此,才会抛出上面的异常。

  如果将jspstream.jsp页面中所有的静态部分都删除,那么JSP引擎不会向out对象缓冲区写入任何内容,也不会使用PrintWriter对象向客户端输出信息。因此,这时Servlet引擎实际上只使用了ServletOutputStream对象,所以可以正常向客户端输出信息。

  除了直接在JSP页面中使用ServletOutputStream对象可能会抛出异常外,使用forward和include方法转发和包含页面时也可能会抛出和图6.7相同的异常信息,如下面的代码所示:

  <!-- jspforward.jsp -->

  <%@ page language="java" pageEncoding="UTF-8"  %>

  <%

  RequestDispatcher rd = request.getRequestDispatcher("/test.html");

  rd.forward(request, response);

  //  使用include方法和使用forward都会带来同样的问题

  //  rd.include(request, response);

  %>

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/chapter6/jspforward.jsp

  浏览器将会显示如图6.7所示的异常信息。

  抛出异常的原因是由于Servlet引擎通过默认的Servlet来处理html、jpg等Web资源。默认Servlet会首先检查是否调用了getWriter方法获得PrintWriter对象,如果系统还未获得PrintWriter对象,则默认的Servlet会使用ServletOutputStream对象来处理这些Web资源。而在jspforward.jsp页面中并未显式地调用getWriter方法来获得PrintWriter对象,而且out对象缓冲区也未满,因此,也不可能通过将out对象缓冲区的内容写入Servlet引擎缓冲区的方式来调用getWriter方法获得PrintWriter对象。所以这时使用forward方法来转发test.html页面,实际上是使用ServletOutputStream对象来处理的。

  当jspforward.jsp页面结束时,会因为将out对象缓冲区的内容写入Servlet引擎的缓冲区而调用getWriter方法。因此,实际上jspforward.jsp页面相当于先调用了getOutputStream方法,再调用了getWriter方法,因此,就会造成冲突,从而抛出异常。

  读者可以通过如下3种方法来解决这个问题,从而避免抛出异常:

  清空jspforward.jsp页面中的所有静态部分,包括\r\n.这样系统就不会向out对象的缓冲区写入任何内容了。

  如果在<%…%>前面有静态内容的话(在一般情况下<%…%>前都会有一些静态内容),可以使用page指令的buffer属性将out对象缓冲区关闭,也就是将buffer属性设为"none".这样只要在<%…%>前面有静态内容,就可以直接写到Servlet引擎的缓冲区中,也就相当于调用了getWriter方法。

  在<%…%>中的开始部分加上response.getWriter方法的调用。这样再调用forward或include方法,默认Servlet就会检测到已经调用了getWriter方法,因此,就会使用PrintWriter来处理完成forward或include方法的工作。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

request对象

 

  6.4.2  request对象

  JSP页面中的request对象和Servlet中的request对象的使用方法完全一样,该对象主要用来获得客户端的一些信息,如请求参数、HTTP请求消息头等。request对象还可以将对象通过setAttribute方法保存在请求域中,并使用getAttribute方法取得保存在请求域中的对象。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

response对象

 

  6.4.3  response对象

  JSP中的response对象和Servlet中的response对象完全一样。response对象除了可以使用getWriter和getOutputStream方法获得PrintWriter和ServletOutputStream对象(在JSP页面中尽量不要使用ServletOutputStream对象向客户端输出数据,否则可能会抛出异常),并利用这两个对象向客户端输出数据外。主要就是用来修改HTTP响应消息头的内容。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

page对象

 

  6.4.4  page对象

  page对象表示由JSP页面生成的Servlet类的对象实例本身。page对象实际上是Object类型的对象。但可以将page对象转换成相应的Servlet类型的对象。在下面的代码中输出了page对象的类型信息,并通过反射技术输出了由JSP生成的Servlet类中的所有public方法名。

  <!--  page.jsp  -->

  <%@ page language="java" pageEncoding="UTF-8" %>

  <%

  out.println(page.getClass());

  out.println("<hr>");

  java.lang.reflect.Method[] methods = page.getClass()。getMethods();

  //  通过反射技术列出由JSP生成的Servlet中的所有public方法

  for(java.lang.reflect.Method method: methods)

  {

  out.println("{" + method.getName() + "}");

  }

  %>

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/chapter6/page.jsp

  浏览器显示的信息如图6.8所示。

图6.8  输出page对象中的所有public方法名

  从图6.8所示的输出信息可以看出,在page对象中有一些我们很熟悉的方法,如_jspService、init方法等。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

session对象

 

  6.4.5  session对象

  session对象用实际上是HttpSession对象实例。用来操作服务端的Session对象。JSP中的Session对象和Servlet中的Session对象基本一样,但有一点不同。就是在默认情况下,每一个JSP页面都会建立一个HttpSession对象。而在Servlet中只有通过调用HttpServletRequest接口的getSession方法时才会建立一个HttpSession对象(当SessionId没有对应的HttpSession对象时创建新的HttpSession对象)。要想关闭JSP中自动建立HttpSession对象的功能,需要将page指令的session属性值设为false.关于session对象的详细用法,请读者参阅5.4节所讲的内容。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

application对象

 

  6.4.6  application对象

  application对象实际上就是ServletContext对象。该对象除了可以获得一些系统的信息外,也可以将对象保存在自己的域中。到现在为止,已经讲过了三个对象(request、session和application)可以在自己的域中保存对象信息。在6.4.9节还会讲到一个pageContext对象,也拥有自己的域。

  在上述四个对象中,application域的应用范围最大,保存在application域的信息可以被当前Web应用程序中的所在Servlet和JSP页面访问。而保存在session域中的信息只能被属于同一个会话的Servlet和JSP页面访问。而request域只能被属性同一个请求的Servlet和JSP页面访问,如当前页面和在该页面中通过forward或include方法转发或包含的页面之间就属性同一个request.应该范围最小的是pageContext域,该域只能在当前JSP页面中访问。关于application对象的详细用法,请读者参阅4.5节所讲的内容。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

config对象

 

  6.4.7  config对象

  config对象实际上就是ServletConfig对象。该对象主要用来读取Servlet的配置信息,如初始化参数等信息。关于config对象的详细用法,请读者参阅4.4节所讲的内容。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

exception对象

 

  6.2.8  exception对象

  必须在当前JSP页面中将page指令的isErrorPage属性设为true,在当前JSP页面才可以使用exception对象。该对象可以获得JSP页面抛出的异常信息。关于该对象的详细用法请读者参阅6.3.3节所讲的内容。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

page Context对象

 

  6.2.9  pageContext对象

  pageContext对象是javax.servlet.jsp.PageContext类的对象实例,该类是javax.servlet.jsp.JspContext的子类。pageContext对象封装了当前JSP页面的各种信息,通过pageContext对象的getter方法可以获得JSP页面的其他8个内置对象,这些getter方法如下:

  getException:该方法返回exception对象。

  getOut:该方法返回out对象。

  getPage:该方法返回page对象。

  getRequest:该方法返回request对象。

  getResponse:该方法返回response对象。

  getServletConfig:该方法返回config对象。

  getServletContext:该方法返回application对象。

  getSession:该方法返回session对象。

  如果在JSP页面中要使用某个普通的类,在该类中要使用JSP的内置对象,为了方便起见,可以将pageContext对象作为参数传入该类的对象实例,这样在该类中就可以使用JSP页面中所有9个内置对象了。

  在前面讲过,request和application对象都可以通过forward和include方法转发和包含Web资源。实际上,在pageContext对象中也提供了forward和include方法来简化转发和包含Web资源的编码工作。

  在pageContext对象中有一个forward方法和两个include方法,这3个方法的定义如下:

  abstract public void forward(String relativeUrlPath)

  throws ServletException, IOException;

  abstract public void include(String relativeUrlPath)

  throws ServletException, IOException;

  abstract public void include(String relativeUrlPath, boolean flush)

  throws ServletException, IOException;

  其中flush参数为true,表示在调用include方法之前,将out对象的缓冲区中的内容刷新到Servlet引擎提供的缓冲区中。pageContext对象中的forward和include方法与前面讲的相应方法类似,只是forward方法在处理out对象缓冲区上有一些区别,看如下的代码:

  <!--  pageContext.jsp  -->

  <%@ page language="java" pageEncoding="UTF-8" %>

  <%

  pageContext.forward("/test.html");

  %>

  如果将pageContext.jsp页面中<%…%>后面的静态部分都删除,则可以正常访问该页面,但如果在<%…%>后面还有静态部分,则在访问pageContext.jsp页面时会抛出如图6.8所示的异常。这是由于pageContext对象的forward对象在转发Web资源之前,会先清空out对象的缓冲区,因此,在<%…%>之前写入out对象缓冲区的内容将作废,这时如果<%…%>后面没有静态部分,则系统就不会调用getWriter方法获得PrintWriter对象,因此,也就不会抛出异常了。

  如果在<%…%>后面也加上JSP页面的静态部分,则仍然会抛出图6.8所示的异常。这是因为虽然在调用forward方法之前清空了out对象的缓冲区,但在调用forward方法之后,仍然会继续将静态内容写入out对象的缓冲区。当JSP页面结束时,还会调用getWriter方法来获得PrintWriter对象。因此,就会抛出异常。

  pageContext对象拥有自己的域,也就是说,可以通过setAttribute、getAttribute、removeAttribute方法设置、获得和删除域信息外。还有如下两个方法可以访问pageContext、request、session和application四个域:

  findAttribute:该方法可以依次从pageContext、request、session和application四个域中获得指定的属性值。如果前一个域中没有要找的属性,则继续在下一个域中寻找。如果在这四个域中都没有要找的属性,则该方法返回null.

  getAttributeNamesInScope:该方法返回某个域中的所有属性名,这些属性名将被放在一个Enumeration对象中返回。该方法有一个参数,可以通过PageContext的常量设置。如要获得request域中的所有属性的名称,可以使用PageContext.REQUEST作为该方法的参数值。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

JSP标签

 

  6.5  JSP标签

  JSP还提供了一种标准动作(Standard Action),也被称为JSP标签。利用这些JSP标签,可以完成很多通用的功能,如创建Bean对象实例、转发和包含其他的页面、向客户端输出信息等。JSP标签采用了XML元素的语法格式,也就是说每一个JSP标签在JSP页面中都是以XML元素的形式出现的,所以的JSP标签都以jsp作为前缀,如<jsp:include>、<jsp:forward>、<jsp:userBean>等。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

jsp:include标签

 

  6.5.1  <jsp:include>标签

  <jsp:include>标签用于把另外一个Web资源引入当前JSP页面的输出内容之中。该标签的语法格式如下:

  <jsp:include page="relativeURL | <%=expression%> | EL"  flush="true|false"/>

  其中page属性用于指定被引入的Web资源的相对路径,该属性可以用普通字符串指定相对路径,也可以使用JSP表达式或EL(表达式语言)来指定相对路径(EL将在下一章详细讲解)。flush属性表示在引入Web资源时,是否先将out对象缓冲区中的内容刷新到Servlet引擎提供的缓冲区。如果flush属性为true,表示在引入Web资源时,先刷新out对象的缓冲区。flush属性的默认值是false.

  在使用<jsp:include>标签时应注意如下几点:

  引入资源的方式:<jsp:include>标签和6.3.4节讲的include指令在引入资源时有很大的差别。它们之间最大的差别就是include指令是静态引入的,也就是在JSP引擎翻译JSP页面时就将当前JSP页面和被引入的页面合并了,最终生成的Servlet就已经是这两个JSP页面的合体的。而<jsp:include>标签是动态引入Web资源。也就是说,在JSP每次运行时都会引入page属性指定的Web资源。

  引入资源的路径:如果单从引用文件的目录结构来看,<jsp:include>标签的page属性和include指令的file属性指定的路径是一样的。如将test.jsp文件放在"WEB-INF"目录中,通过<jsp:include>标签的page属性可设为page="/WEB-INF/test.jsp",include指令的file属性也可设为file="/WEB-INF/test.jsp",但page和file不同的是page属性可以设置在web.xml文件中配置的路径,而file属性的值只能是在目录结构中存在的文件。如将"/WEB-INF/test.jsp"映射成"/jsp/test.jsp",这个新的路径并不存在,只是个虚拟的映射路径。在page属性中该值是有效的,而将file属性设成该值,JSP引擎会提示该路径不存在。

  引入资源的内容:<jsp:include>标签引入的资源可以是任何内容,而include指令引入的资源必须符合JSP语法规范,即使引入的资源文件的扩展名不是。jsp,该文件的内容也必须符合JSP的语法规范。这是由于include指令在引入任何资源文件时,都会将该文件作为JSP页面进行翻译。如果有一个test.html文件,该文件的内容是<% abcd %>,很明显,该文件的内容不符合JSP语法规范(abcd并未定义,也不是表达式,在翻译成Java代码时会编译出错)。如果这个文件被<jsp:include>标签引用,会直接输出<% abcd %>,但被include指令引用,则会抛出异常。当然,如果将test.html改名为test.jsp,不管是<jsp:include>标签,还是include指令,都会抛出异常。这是由于<jsp:include>标签是根据引入文件的扩展名来决定如何处理该文件的,如果扩展名是。jsp,也会按着JSP页面来处理,所以会抛出异常。

  <jsp:include>标签和RequestDispatcher.include方法类似,在被引入的页面中修改响应状态码和响应消息头的语句将被忽略。

  <jsp:include>标签无论在任何情况下,都会使用PrintWriter对象来输出信息。这一点和include方法有很大的差别。对于include方法来说,系统会根据include方法前面的代码是否使用了PrintWriter或ServletOutputStream对象来决定使用哪一个对象来输出信息。而<jsp:include>标签通过某些机制使得ServletOutputStream永远不可用,因此,该标签只能使用PrintWriter对象来输出信息。从这一点可以看出,只要在JSP页面中不使用ServletOutputStream对象来输出信息,<jsp:include>标签是绝对不会由于同时调用了PrintWriter和ServletOutputStream对象来抛出异常的。所以也可以有一个推论,就是在使用<jsp:include>标签的JSP页面中使用ServletOutputStream对象,不管任何情况,都会抛出异常。关于<jsp:include>标签为什么会有这样的特性,将在本节的后面部分详细讲解。

  效率:include指令的效率是最高的,但include指令不如<jsp:include>标签灵活。如include指令的file属性不能使用EL和JSP表达式。

  <jsp:include>标签在引入资源文件时可以传递请求参数,但由于include指令是静态引用资源文件的,因此,include指令在引用资源文件时不能传递请求。

  <jsp:include>标签的page属性必须是相对路径,如果以"/"开头,表示相对于当前Web应用程序的根目录(不是站点根目录),否则,相对于当前页面。

  【实例6-1】  <jsp:include>标签演示

  1.  编写dynamicincluding.jsp页面

  该页面使用<jsp:include>指令引入一个included.jsp页面,dynamicincluding.jsp页面的代码如下:

  <%@ page language="java" pageEncoding="UTF-8" %>

  使用out对象输出信息<br>

  <jsp:include page="included.jsp" flush="false"/>

  <br>

  <%

  response.getWriter()。println("使用PrintWriter输出信息<br>");

  %>

  在上面的代码中,<jsp:include>指令前面有一行静态的内容,这部分内容将通过out对象输出到客户端,在<jsp:include>指令后面通过PrintWriter对象输出了一条信息。如果<jsp:include>标签的flush指令为false,则在引入included.jsp页面时不刷新out对象的缓冲区,因此,使用PrintWriter对象输出的信息将会在最前面显示。

  2.  编写included.jsp页面

  该页面只是一个普通的JSP页面,代码如下:

  <%@ page language="java" import = "java.util.*"  pageEncoding="UTF-8"%>

  included.jsp中的内容<br>

  3.  测试<jsp:include>标签引入资源的效果

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/chapter6/dynamicincluding.jsp

  浏览器显示的信息如图6.9所示。

图6.9  使用<jsp:include>标签引入资源文件

  从图6.9所示的信息可以看出,使用PrintWriter对象输出的信息显示在了页面的开始部分。如果在dynamicincluding.jsp页面的任何位置调用了response.getOutputStream方法,则一定会抛出异常。读者可以自己做这个实验。

  4.  在引入资源文件时刷新out对象的缓冲区

  将dynamicincluding.jsp页面中<jsp:include>标签的flush属性设为true,代码如下:

  <%@ page language="java" pageEncoding="UTF-8" %>

  使用out对象输出信息<br>

  <jsp:include page="included.jsp" flush="true"/>

  <br>

  <%

  response.getWriter()。println("使用PrintWriter输出信息<br>");

  %>

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/chapter6/dynamicincluding.jsp

  浏览器显示的信息如图6.10所示。

图6.10  引入资源文件时刷新out对象的缓冲区

  从图6.10所示的输出信息可以看出,由于在引入included.jsp页面时已经将out对象的缓冲区刷新,所以在此之前被写入out缓冲区的内容将会首先输出的客户端,因此,<jsp:include>标签前面的静态内容会显示在最前面。

  5.  引用web.xml文件中配置的资源文件

  如果将dynamicincluding.jsp和included.jsp页面在web.xml中重新配置一下它们的访问路径,使用<jsp:include>标签仍然可以使用这些新的路径来引用included.jsp页面。配置代码如下:

  <!--  配置dynamicincluding.jsp  -->

  <servlet>

  <servlet-name>dynamicincluding</servlet-name>

  <jsp-file>/chapter6/dynamicincluding.jsp</jsp-file>

  </servlet>

  <servlet-mapping>

  <servlet-name>dynamicincluding</servlet-name>

  <url-pattern>/abcd/including.jsp</url-pattern>

  </servlet-mapping>

  <!--  配置included.jsp  -->

  <servlet>

  <servlet-name>included</servlet-name>

  <jsp-file>/chapter6/included.jsp</jsp-file>

  </servlet>

  <servlet-mapping>

  <servlet-name>included</servlet-name>

  <url-pattern>/myjsp/included.jsp</url-pattern>

  </servlet-mapping>

  如果按着上面的配置代码引用included.jsp,则dynamicincluding.jsp页面的代码如下:

  <%@ page language="java" pageEncoding="UTF-8" %>

  使用out对象输出信息<br>

  <jsp:include page="/myjsp/included.jsp" flush="true"/>

  <br>

  <%

  response.getWriter()。println("使用PrintWriter输出信息<br>");

  %>

  在浏览器地址栏输入如下的URL:

  http://localhost:8080/demo/abcd/including.jsp

  浏览器输出的信息和图6.10所示的输出内容完全相同。

  6.  为什么<jsp:include>标签一定会使用PrintWriter对象输出信息

  如果读者查询由dynamicincluding.jsp页面生成的Servlet源代码,就会发现<jsp:include>标签被翻译成了下面的Java代码:

  org.apache.jasper.runtime.JspRuntimeLibrary.include(request, response,

  "/test.html", out, true);

  其中include方法的最后一个参数就是flush属性的值。继续查看JspRuntimeLibrary.include方法的源代码(该源代码可以在Tomcat的源代码中找到)。include方法的相关代码如下:

  public static void include(ServletRequest request,

  ServletResponse response,

  String relativePath,

  JspWriter out,

  boolean flush)

  throws IOException, ServletException {

  … …

  RequestDispatcher rd = request.getRequestDispatcher(resourcePath);

  rd.include(request,

  new ServletResponseWrapperInclude(response, out));

  }

  从上面的代码可以看出,实际上,<jsp:include>标签最终调用的是RequestDispatcher接口的include方法。从这一点还看不出<jsp:include>标签使用的一定是PrintWriter对象。然而,"玄机"就在ServletResponseWrapperInclude类中,这个类实现了HttpServletResponse接口,因此,该类可以转换成HttpServletResponse对象。

  在Tomcat源代码中找到ServletResponseWrapperInclude.java,该类的相关代码如下:

  package org.apache.jasper.runtime;

  … …

  public class ServletResponseWrapperInclude extends HttpServletResponseWrapper

  {

  private PrintWriter printWriter;

  private JspWriter jspWriter;

  public ServletResponseWrapperInclude(ServletResponse response,

  JspWriter jspWriter)

  {

  super((HttpServletResponse)response);

  this.printWriter = new PrintWriter(jspWriter);

  this.jspWriter = jspWriter;

  }

  public PrintWriter getWriter() throws IOException

  {

  return printWriter;

  }

  //  抛出异常,使getOutputStream方法永远不可用

  public ServletOutputStream getOutputStream() throws IOException

  {

  throw new IllegalStateException();

  }

  … …

  }

  从上面的代码中可以看出,在getOutputStream方法中抛出一个异常,这说明getOutputStream方法是永远不可用的。但光在getOutputStream方法中抛出异常并不足以说明<jsp:include>标签一定使用了PrintWriter对象输出信息(还有一种可能,就是最后会抛出一个异常)。

  决定<jsp:include>标签使用哪个对象输出信息的最后一道"关卡"就是处理默认请求的DefaultSevlet类,该类也可以在Tomcat源代码中找到(DefaultServlet.java)。该类是通过try…catch语句来选择使用哪个对象输出信息的。下面是DefaultServlet类选择PrintWriter或ServletOutputStream对象的主要逻辑:

  ServletOutputStream ostream = null;

  PrintWriter writer = null;

  try

  {

  ostream  = response.getOutputStream();

  }

  catch(Exception(IllegalStateException e)

  {

  writer = response.getWriter();

  }

  从上面的代码可以看出,首先在try{…}块中尝试获得ServletOutputStream对象,在这时response对象实际上是ServletResponseWrapperInclude对象实例,而ServletResponseWrapperInclude类中的getOutputStream方法只有一条抛出异常的语句,而且抛出的异常正好是IllegalStateException,刚好被catch{…}捕获,因此,使用ServletResponseWrapperInclude对象实例作为include方法的第二个参数时,一定使用的是PrintWriter对象输出的信息(因为getOutputStream方法总是抛出IllegalStateException异常)。所以笔者建议在JSP中引用资源文件时,应尽量使用<jsp:include>标签。

  下面的JSP代码将抛出一下异常:

  <%@ page language="java" pageEncoding="UTF-8" %>

  abcdefg

  <%

  request.getRequestDispatcher("/test.html")。include(request, response);

  %>

  由于上面代码中的include方法使用了ServletOutputStream对象,因此,在访问上面的JSP页面时将抛出一个异常。根据上面的描述,可以采用ServletResponseWrapperInclude对象来包装response,如使用下面的代码将不会抛出异常:

  <%@ page language="java" pageEncoding="UTF-8" %>

  abcdefg

  <%

  //  下面的语句一定使用PrintWriter对象来输出信息

  request.getRequestDispatcher("/test.html")。 include(request,  new

  org.apache.jasper.runtime.ServletResponseWrapperInclude(response, out));

  %>

  要注意的是,在使用ServletResponseWrapperInclude类时,需要在demo工程中引用jasper.jar文件,该文件可以在<Tomcat安装目录>\lib目录中找到。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

jsp:forward标签

 

  6.5.2  <jsp:forward>标签

  <jsp:forward>标签用于转发Web资源。<jsp:forward>标签的语法格式如下:

  <jsp:forward page="relativeURL | <%=expression%> | EL " />

  <jsp:forward>标签和pageContext.forward方法的功能完全一样,看如下的代码:

  <!--  forward.jsp  -->

  <%@ page language="java" pageEncoding="UTF-8"%>

  <jsp:forward page="/test.html"/>

  上面的代码通过<jsp:forward>标签转入"/test.html",查询由forward.jsp页面翻译成的Servlet源文件,其中和<jsp:forward>标签相关的代码如下:

  out.write('\r');

  out.write('\n');

  if (true) {

  _jspx_page_context.forward("/test.html");

  return;

  }

  out.write('\r');

  out.write('\n');

  从上面的代码可以看出,<jsp:forward>标签实际上被翻译成了调用pageContext对象的forward方法。因此,<jsp:forward>标签和pageContext.forward方法是完全一样的。但它们有一点不同,虽然<jsp:forward>标签和pageContext.forward方法等效,但是由<jsp:forward>标签翻译成的Java代码在调用完forward方法后,直接通过return语句退出了_jspService方法,也就是说,使用<jsp:forward>标签转发Web资源,不管在<jsp:forward>标签后面有没有静态的内容,都不会被写入out对象的缓冲区,自然也就不会使用PrintWriter对象将信息输出的客户端了。从这一点可以看出,在<jsp:forward>标签后面的内容是不会造成由于同时使用PrintWriter和ServletOutputStream对象而抛出异常的结果的。

  从上面的描述可能看出,在JSP页面中使用<jsp:forward>标签转发Web资源将大大降低抛出异常的可能性。但<jsp:forward>标签至少在如下3种情况下仍然会抛出异常:

  page指令的buffer属性值为none.

  在调用<jsp:forward>标签之前,out对象缓冲区中的内容的大小由于已经超过了缓冲区的大小,从而被刷新了。

  显示调用out.flush方法刷新out对象缓冲区。

  上面的3种情况之所以会抛出异常,是由<jsp:forward>标签的一个特性决定的,由于调用<jsp:forward>标签时,out对象缓冲区会被清空,而在调用clear方法清空缓冲区时,不能在此之前调用flush来刷新缓冲区,否则会抛出IOException异常。因此,在调用<jsp:forward>标签之前,不能通过任何方式刷新out对象的缓冲区。

  对于上述情况的第一种,如果将buffer属性设为none,那么只要有一个字节的数据被写入out对象的缓冲区,该缓冲区都会被刷新。而对于第二种和第三种情况则毫无疑问会刷新缓冲区。但第三种情况则仍然会在浏览器中显示out对象缓冲区中的内容,而抛出的异常将在Tomcat控制台中显示(这种情况将会抛出java.io.IOException:异常)。前两种情况则既会在浏览器中显示异常,也会在Tomcat控制台中显示异常,而且抛出的异常是java.lang.IllegalStateException。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

jsp:param标签

 

  6.5.3  <jsp:param>标签

  当使用<jsp:include>和<jsp:forward>标签引入或转发的Web资源需要请求参数时,可以通过<jsp:param>标签进行传递。<jsp:param>标签的语法格式如下:

  <jsp:param name="parameterName" value="parameterValue | <%= expression %> | EL"/>

  下面的代码通过<jsp:param>标签向<jsp:include>标签引用的included.jsp标签传递一个name请求参数:

  <%@ page language="java" pageEncoding="UTF-8"%>

  <jsp:include page="included.jsp">

  <jsp:param name="name" value="bill" />

  </jsp:include>

  可以在included.jsp页面中使用${param.name}来获得name请求参数的值。

  需要注意的是,如果使用<jsp:param>标签传递中文请求参数时,在默认情况下,将会输出"??".如下面的代码所示:

  <%@ page language="java" pageEncoding="UTF-8"%>

  <jsp:include page="included.jsp">

  <jsp:param name="name" value="比尔" />

  </jsp:include>

  发生这种情况的原因也很简单,就是<jsp:param>标签在被翻译成的Java代码中的参数名和参数值时按着URL的编码格式进行编码了,所使用的字符集编码是通过request.getCharacterEncoding方法获得的,在默认情况下,通过该方法获得的字符集编码是ISO-8859-1,该字符集编码不支持中文字符,因此会输出"?".

  如果要解决这个问题,可以使用setCharacterEncoding方法将字符集编码设成UTF-8.如下面的代码所示:

  <%@ page language="java" pageEncoding="UTF-8"%>

  <%

  request.setCharacterEncoding("UTF-8");

  %>

  <jsp:include page="included.jsp">

  <jsp:param name="name" value="比尔" />

  </jsp:include>

  <jsp:param>标签在<jsp:forward>标签中的使用方法和<jsp:include>标签完全一样。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

jsp:useBean标签

 

  6.5.4  <jsp:useBean>标签

  <jsp:useBean>标签用于在指定的范围(pageContext、request、session和application)中查找一个指定名称的Java对象,如果在指定的范围存在该对象,则<jsp:userBean>标签直接返回该对象的引用,否则创建一个新的对象,并将这个新对象存储在指定的范围。

  <jsp:useBean>标签的id属性用来指定对象名,class属性用来指定要查找或创建的对象所对应的类名。scope属性用来指定搜索范围。该属性可以接受如下四个值:

  page:表示<s:useBean>标签将从PageContext对象中搜索指定的对象,或将新创建的对象存储在PageContext对象中。page是scope属性的默认值。

  request:表示<s:useBean>标签将从ServletRequest对象中搜索指定的对象,或将新创建的对象存储在ServletRequest对象中。

  session:表示<s:useBean>标签将从HttpSession对象中搜索指定的对象,或将新创建的对象存储在HttpSession对象中。

  application:表示<s:useBean>标签将从ServletContext对象中搜索指定的对象,或将新创建的对象存储在ServletContext对象中。

  下面的代码使用Java代码将一个java.util.Date对象保存在request对象中,并通过<s:useBean>标签来读取该对象,最后输出该对象。

  <!--  usebean.jsp  -->

  <%@ page language="java" pageEncoding="UTF-8"%>

  <%

  java.util.Calendar calendar = java.util.Calendar.getInstance();

  calendar.set(2001, 2,1);

  request.setAttribute("myDate", calendar.getTime());

  %>

  <jsp:useBean id="myDate" scope="request" class="java.util.Date"/>

  <%

  out.println(myDate);

  %>

  访问usebean.jsp页面,在浏览器中将输出如下的信息:

  Thu Mar 01 13:19:14 CST 2001

  在上面的输出信息中,时间是当天的时间,而日期是使用Calendar.set方法设置的日期。从而可以断定,<jsp:useBean>返回的对象实例是保存在request对象中的对象。如果读者将保存在request对象中的myDate对象改成其他的名,<jsp:useBean>标签就会由于未找到相应的对象,而创建一个新的java.util.Date对象,从而输出当天的日期和时间。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

jsp.setProperty标签

 

  6.5.5  <jsp.setProperty>标签

  <jsp:setProperty>标签用于设置JavaBean对象的属性。实际上,该标签是通过调用JavaBean的setter方法设置属性值的。<jsp:setProperty>标签的语法格式如下:

  <jsp:setProperty name="beanInstanceName" prop_expr  />

  prop_expr ::=

  property="*" |

  property="propertyName"|

  property="propertyName" param="parameterName"|

  property="propertyName" value="propertyValue"

  propertyValue ::= string | <%= expression %> | EL

  下面是<jsp:setProperty>标签中各个属性的含义:

  name(必选):该属性用于指定JavaBean对象实例名,该属性值应与<jsp:useBean>标签的id属性值相同。

  property(必选):该属性用于指定JavaBean对象实例的属性名。如果该属性值为 "*",则为JavaBean对象的所有属性赋值

  value(可选):该属性用于指定JavaBean对象实例的属性值。value属性可以是普通字符串,也可以是JSP表达式或EL.<jsp:setProperty>标签为将value属性指定的值类型换成JavaBean对象属性的值类型。如果类型无法转换,将抛出异常。如果不指定该属性。则<jsp:setProperty>标签会寻找和property属性值匹配的请求参数,如果找到,会以该请求参数值作为相应的JavaBean对象属性值。如果指定value属性,则property属性值不能为"*".

  param(可选):该属性指定将哪一个请求参数赋给指定的属性。如果请求消息中没有param属性所指的请求参数,则<jsp:setProperty>标签什么都不会做,仍然会保留JavaBean对象原来的属性值。value和param属性不能同时使用,它们在同一个<jsp:setProperty>标签中只能出现一个。如果指定param属性,则property属性值不能为"*".

  下面是一个JavaBean的代码:

  package chapter6;

  public class MyBean

  {

  private String name;

  private int age;

  //  省略了属性的getter和setter方法

  … …

  }

  下面的代码演示了各种使用<jsp:setProperty>标签的方式:

  1.  使用value属性设置JavaBean对象的指定属性值

  <jsp:useBean id="myBean" class="chapter6.MyBean"/>

  <jsp:setProperty property="name" name="myBean" value = "比尔"/>

  <jsp:getProperty property="name" name="myBean"/>

  在浏览器地址栏中输入如下的URL来测试上面的代码:

  http://localhost:8080/demo/chapter6/setproperty.jsp

  2.  使用请求参数设置JavaBean对象的指定属性值

  <jsp:useBean id="myBean" class="chapter6.MyBean"/>

  <jsp:setProperty property="name" name="myBean" />

  <jsp:getProperty property="name" name="myBean"/>

  在浏览器地址栏中输入如下的URL来测试上面的代码:

  http://localhost:8080/demo/chapter6/setproperty.jsp?name=bill

  在访问上面的URL后,name请求参数的值将被赋给MyBean对象的name属性。

  3.  使用请求参数设置JavaBean对象中的所有属性值

  <jsp:useBean id="myBean" class="chapter6.MyBean"/>

  <jsp:setProperty property="*" name="myBean"/>

  <jsp:getProperty property="name" name="myBean"/>

  <jsp:getProperty property="age" name="myBean"/>

  在浏览器地址栏中输入如下的URL来测试上面的代码:

  http://localhost:8080/demo/chapter6/setproperty.jsp?name=bill&age=22

  在访问上面的URL后,name和age请求参数的值分别将被赋给MyBean对象的name和age属性。

  4.  使用param指定为JavaBean对象指定属性赋值的请求参数

  <jsp:useBean id="myBean" class="chapter6.MyBean"/>

  <jsp:setProperty property="name" name="myBean" param="myname"/>

  <jsp:getProperty property="name" name="myBean"/>

  在浏览器地址栏中输入如下的URL来测试上面的代码:

  http://localhost:8080/demo/chapter6/setproperty.jsp?myname=Mike

  在访问上面的URL后,myname请求参数的值将被赋给MyBean对象的name属性。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

jsp:getProperty标签

 

  6.5.6  <jsp:getProperty>标签

  在上一节已经使用了<jsp:getProperty>标签,该标签用于输出JavaBean对象中的指定属性值。<jsp:getProperty>标签的语法格式如下:

  <jsp:getProperty name="beanInstanceName"  property="propertyName" />

  其中name属性值应与<jsp:useBean>标签的id属性值相同。property属性表示JavaBean对象的属性名。关于<jsp:getProperty>标签的使用方法可以参阅上一节的例子。

 
 
 
 

第 6 章:JSP基础作者:李宁    来源:希赛网    2014年03月07日

 

小结

 

  6.6  小结

  本章介绍了JSP的一些基础知识。JSP和其他的脚本语言(如PHP、ASP等)一样,也有一套自己的语法。比较常用的JSP语法有JSP表达式、Java代码(<%…%>)、JSP声明、JSP注释、JSP指令等。为了使JSP拥有对系统更多的控制权限,JSP规范为其增加了9个内置对象,如request、response、session等。除此之外,JSP还提供了一些JSP标签,如<jsp:include>、<jsp:forward>等,通过这些JSP标签,可以完成很多通用的功能。

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月07日

 

表达式语言(EL)EL概述

 

  第7章  表达式语言(EL)

  EL是Expression Language(表达式语言)的英文缩写。EL最初是在JSTL(JSP Standard Tag Library)1.0中定义的。有了EL,使得网页设计人员无需精通更复杂的编程语言(如Java)就可以访问和操作应用程序数据。为了使EL更加成功,Sun公司从JSTL1.1开始将EL从JSTL中剥离出来,使其成为JSP2.0规范的单独的一部分,并为EL增加了很多新的功能。

  7.1  EL概述

  EL表达式是一种被设计用来满足表现层需求的语言,基本语法格式为"${表达式}".当JSP引擎在翻译JSP页面的过程中遇到"${表达式}"这样的字符串时,JSP引擎就会将"${…}"中的内容提取出来作为EL表达式来处理。"${表达式}"中的表达式必须符合EL的语法,该语法具有如下特点:

  1.  在EL表达式中可以直接引用Java变量,并且可以通过嵌套属性的方式访问Java对象中的属性,如下面的代码如下:

  <jsp:useBean id="date" class="java.util.Date"/>

  <!--  访问date变量  -->

  ${date}

  <jsp:useBean id="myBean" class="chapter6.MyBean"/>

  <jsp:setProperty property="age" name = "myBean" value="20" />

  <!--  通过嵌套属性方式访问myBean对象的age属性  -->

  ${myBean.age}

  2.  在EL表达式中可以执行基本的关系运算、逻辑运算、算术运算、条件运算,并且可以使用empty操作符。下面的EL表达式输出的结果为15.0:

  ${(4+5) * 20 / 12}

  3.  在EL表达式中可以使用自定义函数来完成一些更复杂的工作。EL表达式的自定义函数由Java语言编写。实际上,一个自定义函数就是一个Java类的静态方法。如下面的EL表达式调用了一个自定义函数:

  ${fun:invoke("abcd")}

  其中fun是invoke所在类的别名,invoke是自定义函数名, abcd是传递给自定义函数的参数。

  4.  在EL表达式中提供了一系列的内置对象,如pageContext、requestScope等,通过这些内置对象,EL表达式可以访问JSP页面中的各种信息。如通过requestScope对象可以请求域中的属性信息。如果不使用EL表达式,要获得这些信息必须在JSP页面中编写复杂的Java代码。

  5.  EL表达式的语法非常宽松,尽量提供默认值和类型转换,以使得尽可能少地输出错误信息。

  由于"${"是EL表达式的开始标记,因此,JSP引擎不会直接输出这个字符串。要想将"${"直接输出到客户端,需要对"$"字符使用反斜杠"\"对"$"字符进行转义。如要输出"An expression is ${(4 + 5) * 20}",可以使用如下的代码:

  An expression is \${(4+5)*20}

  如果"${"作为"${…}"内部的表达式,如可以使用如下的代码来输出"${":

  ${"${"}(4+5)*20}

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月07日

 

EL的基本应用

 

  7.2  EL的基本应用

  在JSP2.0和JSP2.1中,EL表达式可以用于JSP页面和任何可以接收动态值的JSP标签的属性中。这些标签包括JSP标签、JSTL标签和自定义标签等。

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月07日

 

在JSP页面中使用EL

 

  7.2.1  在JSP页面中使用EL

  EL表达式最简单的使用方法就是将其直接放到JSP页面中。JSP引擎在遇到"${…}"时,会将里面的内容作为EL表达式来处理。并且将EL表达式的执行结果作为JSP页面的静态部分在表达式所在的位置输出。如在JSP页面中有如下的内容:

  1 + 3 = ${1 + 3}

  JSP引擎在翻译上面的代码时,会将如下的内容输出到客户端:

  1 + 3 = 4

  如果要客户端输出HTML或XML格式的内容,由于这些文档的内容包含了一些特殊字符,因此,最好不要使用EL表达式来输出这些具有特殊格式的内容,而要使用JSTL标签<c:out…/>标签(<c:out…/>标签是一个JSTL标签,将在第9章详细讲解)输出,这是由于<c:out…/>标签在默认情况也可以对HTML或XML格式的内容中的特殊字符进行转换,以使这些特殊字符可以正常在浏览器中显示。

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月07日

 

在标签属性中使用EL表达式

 

  7.2.2  在标签属性中使用EL表达式

  EL表达式可以使用在任何接收动态内容的标签属性中。在这些属性中既可以只包含一个的EL表达式,也可以包含多个EL表达式和静态文本。

  标签属性中只包含一个EL表达式的语法如下:

  <prefix:tag value = "${表达式}" />

  下面是标签属性包含一个单独EL表达式的示例代码:

  <jsp:setProperty property="age" name = "myBean" value="${requestScope.abc}" />

  <c:out value="${myBean.name}" />

  标签属性中包含多个EL表达式和静态文本的语法如下:

  <prefix:tag value="The first is ${value1}, the second is ${value2}" />

  JSP引擎在翻译标签属性时,会将其中的EL表达式的执行结果作为属性的静态内容插入到表达式所在的位置。如果EL表达式执行的结果不是字符串类型,系统将会对其进行类型转换,如下面的代码所示:

  <c:out value="I'm a ${value1}. I like ${value2}" />

  value属性中的两个EL表达式在被执行完后,会将它们的执行结果分别插入到表达式所在的位置,然后再进行输出。

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月07日

 

使用isELIgnored属性禁止EL表达式

 

  7.2.3  使用isELIgnored属性禁止EL表达式

  JSP在2.0以前不支持EL表达式,因此,在这些老版本的JSP页面中,如果包含了"${…}"格式的信息,将会被当作普通的字符串来处理。如果这些老版本的JSP页面被移植到支持新版JSP标准(2.0及以上版本)的JSP引擎上,系统就会将"${…}"格式的信息当成EL表达式来处理。这就可能会使同一个JSP页面中不同版本的JSP引擎中运行结果不一致。

  为了使JSP引擎向下兼容,在page指令中提供了一个isELIgnored属性,通过将该属性设为true,可以将高版本的JSP引擎的EL表达式功能关闭。也就是说,当isELIgnored属性为true时,支持JSP2.0及以上版本的JSP引擎会将"${…}"当成是普通字符串处理。

  看下面的JSP代码:

  <!--  elignored.jsp  -->

  <%@ page isELIgnored="true" pageEncoding="UTF-8"%>

  <jsp:useBean id="date" class="java.util.Date"/>

  当前的日期是:${date}

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/chapter7/elignored.jsp

  浏览器显示的效果如图7.1所示。

图7.1  使用isELignored属性禁止EL

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月07日

 

在web.xml中禁止EL表达式

 

  7.2.4  在web.xml中禁止EL表达式

  虽然可以通过page指令的isELIgnored属性禁止在JSP页面中使用EL表达式,但是对每个JSP页面都设置isELIgnored属性就变得非常麻烦,因此,也可以在web.xml文件中禁止在所有或部分JSP页面中使用EL表达式语言。如果要在当前应用程序所有的JSP页面中禁止使用EL表达式,可以使用如下的配置代码:

  <web-app …>

  … …

  <jsp-config>

  <jsp-property-group>

  <url-pattern>*.jsp</url-pattern>

  <el-ignored>true</el-ignored>

  </jsp-property-group>

  </jsp-config>

  </web-app>

  如果只想禁止在部分的JSP页面中使用EL表达式,可以使用如下的配置代码:

  <web-app …>

  … …

  <jsp-config>

  <jsp-property-group>

  <url-pattern>/chapter7/*</url-pattern>

  <el-ignored>true</el-ignored>

  </jsp-property-group>

  </jsp-config>

  </web-app>

  上面的配置代码禁止在chapter7目录及其子目录中所有的JSP页面中使用EL表达式。

  JSP页面的设计者也可以通过isELIgnored属性来覆盖web.xml中的配置。虽然在web.xml文件中禁止在JSP页面中使用EL表达式,但可以通过将isELIgnored属性值设为false的方式单独打开某个JSP页面的EL表达式功能。也就是说,如果既在web.xml文件配置了JSP页面是否支持EL表达式,也在JSP页面中使用page指令的isELIgnored属性设置了JSP页面是否支持EL表达式,那么以JSP页面中的isELIgnored属性的设置为准。

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月07日

 

在web.xml中禁止Java代码

 

  7.2.5  在web.xml中禁止Java代码

  在JSP页面中使用EL表达式可以完成一些基本的功能,并且会使JSP页面变得更加整洁。但在某些时候,开发人员总爱在JSP页面中编写一些Java代码。虽然Java代码功能强大,但在JSP页面中加入大量的Java代码会使用页面更加混乱。因此,良好的编程习惯是在JSP页面中只使用EL表达式或标签。为了更有效地规范这个习惯,在web.xml中提供了一个<scripting-invalid>元素可以关闭JSP页面对Java代码的支持,如果将<scripting-invalid>元素值设为true,则在JSP页面中加入<%…%>或<%=…%>后,JSP页面就会抛出异常。

  禁止在JSP页面中使用Java代码的完整配置代码如下:

  <web-app …>

  … …

  <jsp-config>

  <jsp-property-group>

  <url-pattern>*.jsp</url-pattern>

  <scripting-invalid>true</scripting-invalid>

  </jsp-property-group>

  </jsp-config>

  </web-app>

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月07日

 

EL的内置对象

 

  7.3  EL的内置对象

  在EL表达式语言中定义了11个内置对象(也可以称为EL隐含对象或EL隐式对象)。通过这些内置对象可以很方便地读取请求参数、请求域、Cookie、HTTP请求消息头、Web应用程序的初始化参数等信息。

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月07日

 

内置对象与域对象

 

  7.3.1  内置对象与域对象

  在处理EL表达式的标识符时,会先判断标识符是否为EL的内置对象,如果为EL的内置对象,则按内置对象来处理,如果不是EL内置对象,则会将表达式中的表示符当成域对象来处理。相当于pageContext.findAttribute方法返回域属性中的相应对象。如果标识符在域中未找到相应的对象,则什么都不会输出,也就是说返回结果为null.

  表7.1列出了所有的EL内置对象及其作用。

表7.1  EL内置对象及其作用

  表7.1所示的11个EL内置对象中只有pageContext对象和JSP中的pageContext完全对应,其他10个内置对象都是Map对象。通过这10个对象只能访问相应的key-value对,并不能操作这些内置对象所对应的JSP内置对象的方法、属性。

  如果EL表达式中的标识符和内置对象重名,系统会将该标识符当作EL内置对象处理。如下面的代码所示:

  <!--  elobject.jsp  -->

  <%@ page language="java" contentType="text/html; charset=UTF-8"

  pageEncoding="UTF-8"%>

  <%

  request.setAttribute("requestScope", "myRequest");

  request.setAttribute("pageContext1", "pageContext1");

  session.setAttribute("pageContext", "pageContext");

  %>

  <!--  下面两个EL表达式中的表示符被当成EL内置对象处理  -->

  ${requestScope}<br>

  ${pageContext}<br>

  <!--  下面的EL表达式中的表示域被当成域属性处理  -->

  ${pageContext1}

  上面的JSP页面的运行结果如图7.2所示。

图7.2  按内置对象处理EL表达式中的标识符

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月07日

 

获得域属性集合的内置对象

 

  7.3.2  获得域属性集合的内置对象

  pageScope、requestScope、sessionScope和applicationScope四个EL内置对象分别对应page、request、session和application四个域的属性集合。这四个EL内置对象可以使用如下两种方法访问域属性集合中的对象:

  1.  获得特定域属性集合中的对象:这种方法需要指定要获得哪个域的属性。如下面的代码将获得request域中的name属性:

  ${requestScope.name}

  上面的代码相当于如下的Java代码:

  <%

  out.println(request.getAttribute("name"));

  %>

  2.  按顺序搜索每个域中的属性:这种方法不需要指定域,只需要指定域中的属性。系统会依次从page、request、session和application四个域中搜索该属性。直到发现该属性为止。也就是说,如果在request域中找到该属性,则不会再继续搜索下一个域。如下面的代码输出了name属性的值:

  ${name}

  上面的代码相当于如下的Java代码:

  <%

  out.println(pageContext.findAttribute("name"));

  %>

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月07日

 

pageContext内置对象

 

  7.3.3  pageContext内置对象

  EL表达式中的pageContext对象相当于JSP内置对象中的pageContext.在EL表达式中可以通过pageContext对象访问其他的JSP内置对象。这也正是EL表达式语言要引入pageContext对象的原因。下面的代码演示了如何用pageContext对象来访问out、page以及ServletConfig:

  <!--  pagecontext.jsp  -->

  <%@ page language="java" contentType="text/html; charset=UTF-8"

  pageEncoding="UTF-8"%>

  out对象缓冲区大小:${pageContext.out.bufferSize}<br>

  由当前JSP页面生成的Servlet类名:<br>${pageContext.page.class}<br>

  配置默认Servlet的名称:${pageContext.servletConfig.servletName}

  上面的JSP页面的运行结果如图7.3所示。

图7.3  使用pageContext对象获得JSP的其他内置对象

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月07日

 

获得请求参数集合的内置对象

 

  7.3.4  获得请求参数集合的内置对象

  EL表达式中的param和paramValues对象都可以获得请求参数集合,它们的区别是param对象返回的Map对象的value是String类型,而paramValues对象返回的Map对象的value是String[]类型。因此,paramValues对象可以用于获得可能有重名的请求参数集合。而param对象用于获得没有重名的请求参数集合。如要获得请求参数name的值,可以使用如下的代码:

  ${param.name}

  ${paramValues.name[0]}

  如果使用paramValues对象返回Map对象时,由于value是一个String数组,即使没有重名的请求参数,value的类型仍然为只有一个元素的String数组,因此,必须使用name[0]来输出name请求参数的值。

  如果不为param和paramValues对象指定请求参数,则输出所有的请求参数,代码如下:

  <!--  param.jsp  -->

  ${param}<hr>

  ${paramValues}

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/chapter7/param.jsp?name=bill&age=22

  浏览器显示的效果如图7.4所示。

图7.4  使用param和paramValues对象输出所有的请求参数

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月07日

 

获得HTTP请求头消息集合的内置对象

 

  7.3.5  获得HTTP请求头消息集合的内置对象

  EL表达式中的header和headerValues对象都可以获得HTTP请求消息头字段集合,它们的区别是header对象返回的Map对象的value是String类型,而headerValues对象返回的Map对象的value是String[]类型。因此,headerValues对象可以用于获得可能有重名的请求消息头字段集合。而header对象用于获得没有重名的请求消息头字段集合。如要获得HTTP请求消息头的cookie字段,可以使用如下的代码:

  ${header.cookie}

  ${headerValues.cookie[0]}

  如果使用headerValues对象返回Map对象时,由于value是一个String数组,即使没有重名的请求消息头字段,value的类型仍然为只有一个元素的String数组,因此,必须使用cookie[0]来输出cookie字段的值。

  不为header或headerValues指定请求消息头字段,则输出所有的请求消息头字段的值,代码如下:

  ${header}<hr>

  ${headerValues}

  在运行上面的JSP代码时,输出的结果如图7.5所示。

图7.5  使用header和headerValues对象输出所有的HTTP请求消息头字段

  从图7.5所示的输出信息可以看出,在输出由headerValues对象返回的请求消息头字段集合时,只输出了字段值的String[]数组地址。而由header对象返回的请求消息头字段集合时,同时输出的字段名和字段值。

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月07日

 

cookie内置对象

 

  7.3.6  cookie内置对象

  EL表达式中的cookie对象表示所有Cookie信息的集合。实际上,cookie对象返回的Map对象的value是Cookie类型。使用cookie对象的好处是可以直接通过Cookie名来获得Cookie值。而如果通过HTTPServletRequest.getCookies方法获得指定的Cookie,必须得扫描该方法返回的Cookie对象数组才能获得指定的Cookie对象。如果多个Cookie共用一个名称,Cookie对象数组中第一个与其对应的Cookie对象。

  <!--  cookie.jsp  -->

  <%@ page language="java" contentType="text/html; charset=UTF-8"

  pageEncoding="UTF-8"%>

  <%

  Cookie cookie = new Cookie("product", "bike");

  response.addCookie(cookie);

  %>

  ${cookie.product}<hr>

  ${cookie.product.name} = ${cookie.product.value}

  由于cookie对象是从HTTP请求消息头的cookie字段中提取Cookie信息的。而在第一次执行上面的JSP代码后,由于HTTP请求消息头中并没有cookie字段,因此,第一次执行上面的JSP代码并不会输出Cookie名和Cookie值,当再次执行上面的JSP代码后,就会输出如图7.6所示的信息。

图7.6  使用cookie对象输出Cookie名和Cookie值

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月07日

 

initParam内置对象

 

  7.3.7  initParam内置对象

  EL表达式中的initParam对象可以获得Web应用程序中的初始化参数值。相当于调用ServletContext.getInitParameter方法返回的初始化参数值。Web应用程序的初始化参数可以在server.xml或web.xml文件中配置。

  在server.xml文件中指定初始化参数,可以使用如下的配置代码:

  <Context docBase="demo" path="/demo" reloadable="true"

  source="org.eclipse.jst.jee.server:demo">

  <Parameter name = "myParam" value = "newValue " override="true" />

  </Context>

  在web.xml文件中指定初始化参数,可以使用如下的配置代码:

  <web-app  … >

  <context-param>

  <param-name>companyName</param-name>

  <param-value>Sun公司</param-value>

  </context-param>

  … …

  </web-app>

  可以使用如下的EL表达式来输出myParam和companyName参数的值:

  <!--  initparam.jsp  -->

  <%@ page language="java" contentType="text/html; charset=UTF-8"

  pageEncoding="UTF-8"%>

  ${initParam.myParam}<br>

  ${initParam.companyName}

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月10日

 

EL的基本语法

 

  7.4  EL的基本语法

  EL表达式语言和基本的高级语言(如Java、C#)一样,也有自己的语法和规则。EL表达式语言包含了基本的语言元元,如标识符、保留字、常量、变量等。利用这些语言元素,可以编写出具有基本功能的程序。

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月10日

 

EL中的标识符

 

  7.4.1  EL中的标识符

  EL表达式中的变量和自定义函数名被称为标识符。与Java中的标识符的规则相同。在EL表达式中的标识符可以由任何大小写的字母、数字或下划线组成。但标识符不能以数字开头,也不能是EL中的保留字(将在下一节介绍EL表达式中的保留字)、EL内置对象名以及一些特殊的字符,如单引号(')、双引号(")、减号(-)和斜杠(/)等。例如,name、product32、new_bike都是合法的标识符,而12product、param、abc/xyz是不合法的标识符。

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月10日

 

EL中的保留字

 

  7.4.2  EL中的保留字

  EL表达式语言定义了如下的保留字,这些保留字不能被用做标识符。

  and      eq      gt      true      instanceof

  or       ne      le      false      empty

  not      lt       ge      null      div    mod

  要注意的是,上面的很多关键字现在还没有在EL表达式语言中明确使用,但在EL的未来版本中可以加入这些未用到的保留字,因此,应该尽量避免将这些保留字作为标识符使用。

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月10日

 

EL中的常量

 

  7.4.3  EL中的常量

  EL中的常量又称字面量(Literal)。常是是不可改变的数据。在EL中有以下几种类型的常量:

  1.  布尔(Boolean)类型常量

  布尔常量只有两个值:true和false.该常量可用在条件判断中,也可以在EL表达式中直接输出,如${true}将输出true.

  2.  整数(Integer)类型常量

  整型常量和Java的十进制的整型常量(被声明为final的变量)的取值范围相同。也就是说,整型常量的取值范围在Long.MIN_VALUE和Long.MAX_VALUE之间。

  3.  浮点(Floating point)类型常量

  浮点类型常量的Java的双精度浮点类型常量的取值范围相同,取值范围在Double.MIN_VALUE和Double.MAX_VALUE之间。

  4.  字符串(String)类型常量

  字符串常量是由单引号或双引号括起来的一连串字符。由于字符串常量需要使用单引号或双引号括起来,所以如果字符串中包含单引号或双引号,就需要使用反斜杠(\)进行转义,如果字符串中包含有反斜杠,也需要使用反斜杠来进行转义,例如,"\\"表示字符串中的反斜杠。

  如果字符串是被双引号括起来的,则单引号不需要转换,但单引号要成对出现,如${"a'b'c"},如果单引号个数为奇数,则会抛出如图7.7所示。

  如果对单引号使用反斜杠,则会抛出如图7.8所示的异常。

图7.7  奇数个单引号抛出异常 

 

图7.8  对单引号使用转义符抛出的异常

  综上所述,如果在由双引号括起来的字符串中,单引号必须成对出现,而且不能对单引号使用转义符。但可以对双引号使用转义符,例如${"a\"b"}可以输出"a"b".

  对于由单引号括起来的字符串正好和双引号括起来的字符串相反,也就是说,双引号必须成对出现,而且不能对双引号使用转义符。但可以对单引号使转义符,例如${'a\'b'}可以输出"a'b".如果违反这个规则,将抛出如图7.7或图7.8所示的异常。

  5.  Null常量

  Null常量用于判断某个对象是否为空,该常量只有一个值,用null表示。例如,${param==null}输出的值为false(由于param是EL的内置对象,因此,param对象不可能为空)。

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月10日

 

EL中的变量

 

  7.4.4  EL中的变量

  在EL表达式中可以直接使用变量来引用EL内置对象或域对象。例如,${name},EL引擎会先判断"name"是否为EL内置对象的标识符,如果不是,则调用PageContext.findAttribute方法依次在page、request、session和application四个域中查找名为"name"的域对象,如果找到该对象,则输出它的值,否则输出空串(实际上是返回了null,但EL会使用空串代替null进行输出)。

  从上面的描述可以看出,EL变量并不是预先对某个对象的引用,而只是对EL表达式的引用。在EL引擎翻译该变量表达式时,会根据该变量标识符是否为EL内置对象的标识来决定是按着EL内置对象处理,还是域对象来处理。

 
 
 
 

第 7 章:表达式语言(EL)作者:李宁    来源:希赛网    2014年03月10日

 

EL中的枚举类型

 

  7.4.5  EL中的枚举类型

  枚举类型是Java SE5新增加的特性。使用enum关键字来定义枚举类型,如下面的代码所示:

  enum MyEnum{ABC, XYZ}

  如果在Java代码中使用枚举类型,可将枚举类型中的值当成常量来处理,也可以使用字符串来为枚举类型变量赋值,便必须使用EnumvalueOf方法将字符串转换成枚举类型。下面的代码演示了Java代码操作枚举类型变量的过程:

  <%!

  enum Seasons{SPRING, SUMMER, AUTUMN, WINTER}

  %>

  <%

  Seasons season = Seasons.SPRING;

  out.println(season);//  输出SPRING

  //  使用字符串为枚举类型变量赋值

  season=Enum.valueOf(Seasons.class, "AUTUMN");

  out.println(season);//  输出AUTUMN

  %>

  如果直接枚举类型变量,则会将变量值当成字符串输出。

  在EL表达式中也可以直接输出枚举类型变量,也可以对枚举类型变量进行逻辑判断。但要将枚举类型中的值当成字符串来处理,也就是要将枚举类型的值用单引号或双引号括起来。下面的代码演示了如何在EL表达式中来使用枚举类型变量:

  <!--  enum.jsp  -->

  <%@ page language="java" pageEncoding="UTF-8"%>

  <%!

  enum Seasons{SPRING, SUMMER, AUTUMN, WINTER}

  %>

  <%

  Seasons season = Seasons.SPRING;

  request.setAttribute("season", season);

  %>

  <!--  输出SPRING  -->

  \${season}:${season}<br>

  <!--  输出true  -->

  \${season == "SPRING" }:${season == "SPRING" }<br>

  <!--  输出false  -->

  \${season == "SPRING" }:${season == 'AUTUMN' }<br>

  <!--  如果请求参数为SPRING,输出true,否则输出false  -->

  \${season == "SPRING" }:${season == param.season}

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/chapter7/enum.jsp?season=AUTUMN

  浏览器输出的信息如图7.9所示。

图7.9  在EL表达式中使用枚举类型变量

  在EL表达式中用字符串来代替枚举类型值进行逻辑判断时,必须要考虑开字符串的大小写。也就是说,season请求参数的值必须是Seasons枚举类型中的四个值,而且大小写要一致,否则enum.jsp页面中最后一个EL表达式将抛出异常。

  注意:由于目前很多开发JSP的IDE(如MyEclipse等)还不支持在EL表达式中对枚举类型的变量进行逻辑判断,例如,${session="SPRING"},因此,在这些IDE中编写JSP页面时,如果在EL表达式使用枚举类型的变量进行逻辑判断,IDE可能会提示语法错误,不过这并不影响JSP的运行。读者在使用IDE开发JSP页面时应注意这一点。

 

 第8章  Java Web国际化

  随着Internet的普及,很多Web应用程序可能要被很多国家或地区的用户访问,为了适应不同国家或地区的用户的习惯,Web应用程序必须支持国际化功能。实现国际化功能最直接的方法就是为每一个国家或地区的用户单独设计页面,但这样做工作量会很大,也不易维护和升级。为了解决这个问题,现在普遍的做法是将需要国际化的资源信息保存在资源文件中,并根据本地信息来读取相应资源文件中的国际化信息。

  8.1  Web程序国际化的原理

  国际化程序需要通过Locale对象确定具体的本地信息。在Web程序中,可以通过HttpServletRequest类的getLocale方法获得客户端浏览器支持的首选本地信息(Locale对象)。创建Locale对象需要指定语言和国家,在Web程序中这些信息一般是由HTTP请求消息头的Accept-Language字段指定这些信息。

  查看浏览器发给服务端的Accept-Language字段值的方法有很多。在这里笔者推荐使用HTTP监视软件(如HTTP Analyzer)来截获HTTP请求消息头。读者可以在浏览器中访问任何一个本地或Internet上的网址,如http://nokiaguy.blogjava.net,HTTP Analyzer截获的HTTP请求消息头如图8.1所示。

图8.1  HTTP请求消息头

  从图8.1所示的HTTP请求消息头可以看出,Accept-Language字段的内容如下:

  Accept-Language:zh-cn,en-us;q=0.5

  浏览器支持的所有本地信息都包含在Accept-Language字段中,如果有多个本地信息,中间用逗号(,)分隔。HttpServletRequest类的getLocale方法会根据这些信息返回相应的Locale对象。实际上,Accept-Language字段的信息和浏览器的设置有关,在IE浏览器中通过单击"工具"|"Internet选项"菜单项打开"Internet选项"对话框,单击"语言"按钮打开"语言首选项"对话框。Accept-Language字段的值就是在"语言首选项"对话框中设置的值。在笔者的机器上的"语言首选项"对话框如图8.2所示。

图8.2  "语言首选项"对话框

  如果使用图8.2所示的设置,在访问服务端资源时,IE发送的HTTP请求消息头中的Accept-Language字段值就会和图8.1所示的Accept-Language字段值相同。

  HttpServletRequest类除了getLocale方法外,还有一个getLocales方法用来获得客户端支持的所有本地信息。下面的程序列出了客户端浏览器的首选本地信息和支持的所有本地信息:

  package chapter8.servlet;

  import java.io.IOException;

  import java.io.PrintWriter;

  import java.util.Locale;

  import java.util.Enumeration;

  import javax.servlet.ServletException;

  import javax.servlet.http.HttpServlet;

  import javax.servlet.http.HttpServletRequest;

  import javax.servlet.http.HttpServletResponse;

  public class ListClientLocale extends HttpServlet

  {

  protected void service(HttpServletRequest request,

  HttpServletResponse response) throws ServletException, IOException

  {

  response.setContentType("text/html;charset=UTF-8");

  PrintWriter out = response.getWriter();

  Locale locale = request.getLocale();

  out.println("首选的语言和国家<p/>");

  out.println("语言:" + locale.getLanguage() + "<br>");

  out.println("国家:" + locale.getCountry() + "<hr>");

  out.println("客户端浏览器支持的所有本地信息列表,按优先级的高级排序<p/>");

  Enumeration<Locale> allLocale = request.getLocales();

  while(allLocale.hasMoreElements())

  {

  Locale loc = allLocale.nextElement();

  out.println(loc.getLanguage() + "-" + loc.getCountry() + "<br>");

  }

  }

  }

  在浏览器地址栏中输入如下的URL:

  http://localhost:8080/demo/chapter8/ListClientLocale

  浏览器显示的输出结果如图8.3所示。

  如果将8.2所示的两个本地信息调换,再次访问上面的URL,将会得到如图8.4所示的输出结果。

图8.3  显示客户端的首选本地信息和支持的所有本地信息

 

图8.4  显示首选本地信息和支持的所有信息

  从图8.4所示的输出结果可以看出,首选的本地信息变成了英文(美国),而浏览器支持的所有本地信息的顺序也变化了。

  【实例8-1】  编写国际化的Web程序

  本实例演示了如何在Web程序中根据客户端支持的本地信息显示不同语言的信息。在本例中通过改变IE的默认语言来模拟中文和英语的用户。

  1.  建立中文资源文件

  在WEB-INF\classes\resources目录中建立一个I18nResource_zh_CN.properties文件,该文件的内容如下:

  i18n.welcome=欢迎访问国际化web程序

  i18n.datetime = 今天是{0, date, long}, 现在的时间是 {0, time, long}.

  i18n.message = 我买了{0, number}本英语书, 共花费 {1, number, currency}.现在是学习英语的时间。请不要打扰我!

  2.  建立英文资源文件

  在WEB-INF\classes\resources目录中建立一个I18nResource_en_US.properties文件,该文件的内容如下:

  i18n.welcome=welcome to the internationalization web program!

  i18n.datetime = Today is {0, date,long}, time is {0, time,long}.

  i18n.message = I bought {0, number} English books, and spent {1, number,currency}. Now is the time to learn English. Please Don't bother me!

  3.  编写I18nServlet类

  I18nServlet是一个Servlet类,负责根据客户端浏览器的默认语言将相应语言的国际化信息输出的客户端。I18nServlet类的实现代码如下:

  package chapter8.servlet;

  import java.io.IOException;

  import java.io.PrintWriter;

  import java.util.Locale;

  import java.util.ResourceBundle;

  import java.text.MessageFormat;

  import java.util.Date;

  import javax.servlet.ServletException;

  import javax.servlet.http.HttpServlet;

  import javax.servlet.http.HttpServletRequest;

  import javax.servlet.http.HttpServletResponse;

  public class I18nServlet extends HttpServlet

  {

  protected void service(HttpServletRequest request,

  HttpServletResponse response) throws ServletException, IOException

  {

  response.setContentType("text/html;charset=UTF-8");

  PrintWriter out = response.getWriter();

  Locale locale = request.getLocale();

  //  装载相应语言的资源文件

  ResourceBundle rb =

  ResourceBundle.getBundle("resources.I18nResource", locale);

  //  读取资源文件的内容

  String webcome = rb.getString("i18n.welcome");

  String datetime = rb.getString("i18n.datetime");

  String message = rb.getString("i18n.message");

  //  输出webcome

  out.println(webcome + "<p/><hr>");

  MessageFormat mf = new MessageFormat(datetime, locale);

  //  输出datetime,并指定相应的占位符的参数值

  out.println(mf.format(new Object[]{new Date()})+ "<br>");

  mf.applyPattern(message);

  //  输出message,并指定相应的占位符的参数值

  out.println(mf.format(new Object[]{5, 332}));

  }

  }

  4.  测试

  在IE地址栏中输入如下的URL:

  http://localhost:8080/demo/chapter8/I18nServlet

  如果IE的默认本地信息是"中文(中国)",则在浏览器中显示的信息如图8.5所示。

图8.5  中文(中国)本地环境下的显示效果

  从图8.5所示的显示效果可以看出,I18nResource向客户端输出了中文信息,而且日期、时间和货币信息都符合中文习惯。由此可以断定,I18nResource类读取的是I18nResource_zh_CN.properties文件中的国际化信息。

  将IE的默认本地环境改为"英语(美国)".刷新图8.5所示的页面,输出的信息如图8.6所示。

图8.6  英语(英文)本地环境下的显示效果

  从图8.6所示的显示效果可以看出,当IE的默认本地信息变成"英语(美国)"时,I18nResource类就会读取I18nResource_en_US.properties文件,而且日期、时间和货币的信息都变成了英语的习惯。

  5.  程序总结

  在Web程序中进行国际化,不仅要在获得ResourceBundle对象时指定Locale对象,而且要在创建MessageFormat对象时也指定Locale对象。否则就会出现文本信息根据指定的本地信息显示,但日期、时间和货币等信息却按着服务端的默认本地信息来显示。读者在国际化Web程序时要注意这一点。

 

 

 

posted @ 2016-07-04 15:52  mengxiangtong22  阅读(445)  评论(0编辑  收藏  举报