java web 開發快速寶典 ------電子書

 

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

 

1.2.1  JDk 簡介php

  JDK是Sun公司在1995年推出的一套能夠跨操做系統平臺編譯和運行Java程序的開發包。JDK包括JRE(Java的運行環境)、Java的編譯環境、Java工具集和Java類庫。根據JDK的使用領域,還能夠分爲Java SE、Java EE和Java ME三套開發包。css

其中Java SE主要用於桌面程序、服務類程序的開發;html

Java EE用於企業應用程序的開發(如Web、EJB等);java

Java ME用於編寫在移動設備、便攜式設備上運行的程序。mysql

這三套開發包都使用相同的Java編譯環境和運行環境(也就是說,均可以在操做系統上使用相同的JDK進行開發),它們的區別是所帶的Java類庫不一樣。如Java EE包含了一些在企業應用中所須要的類庫,如Servlet API、JSP API等。git

  在2006年,Sun已經逐步將JDK開源了。 目前JDK的最高版本爲JDK1.7.這個版本能夠從以下的地址下載:web

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

  從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.五、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、product3二、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程序時要注意這一點。

 
相關文章
相關標籤/搜索