1. 程式人生 > >圖解JSP與Servlet的關係

圖解JSP與Servlet的關係

Servlet是Java提供的用於開發Web伺服器應用程式的一個元件,執行在伺服器端,由Servlet容器所管理,用於生成動態的內容。Servlet是平臺獨立的Java類,編寫一個Servlet,實際上就是按照Servlet規範編寫一個Java類。

如圖所示,Java提供一系列介面類(所謂介面類就是類中所有方法只提供方法宣告,不提供任何的方法實現,這些類的實現就留給後繼者去做。):Servlet、ServletConfig、Serializable,然後通過多重繼承產生一個最通用的Servlet實現類(圖中Gerneric Servlet類),接下來,通過一個多重繼承與實現,產生一個新的實現類HttpServlet,使用者在開發Servlet程式時只需繼承這個類,從而產生一個自己的類(圖中Hello_Servlet類),然後根據實際開發功能與資訊處理需要,去實現該類中的相關方法即可。這就是前面提到的按照Servlet規範編寫一個Java類,從而編寫一個Servlet。

至於JSP(JavaServlet Page)從圖中可以看出,實際上它也是從Servlet繼承而來。只不過它在Servlet當中又新增/修改了一些方法,作了新的封裝。具體到Tomcat Web應用伺服器中,它通過一個多重繼承,分別從Java的HttpJspPage和HttpServlet兩個類那裡繼承和實現一些方法,然後封裝一個叫做HttpJspBase的類從而實現了一個通用化的JSP類,使用者在開發自己的JSP時,只需要從HttpJspBase繼承一個自己的類(如圖中Hello_jsp類),然後根據需要去實現相應的方法即可。

因此這也是為什麼JSP的程式碼中總是閃現Servlet程式碼框架影子的原因,其實它們只是為實現同樣的功能而進行了不同封裝的元件而已,血脈裡留著的是一樣的血。

“既生瑜何生亮?”呵呵,因為JSP確實比Servlet要更勝一籌,所謂“青出於藍勝於藍”,既然Sun公司要在Servlet基礎上推出JSP技術,那肯定是因為JSP有它更高明的地方。

使用Servlet產生動態網頁,需要在程式碼中列印輸出很多HTML的標籤,此外,在Servlet中,我們不得不將靜態現實的內容和動態產生內容的程式碼混合在一起。使用Servlet開發動態網頁,程式設計師和網頁編輯人員將無法一起工作,因為網頁編輯人員不瞭解Java語言,無法修改Servlet程式碼,而Java程式設計師可能也不是很瞭解網頁編輯人員的意圖,以至於無法修改和實現網頁功能。為了解決這些問題,Sun公司就推出了JSP技術。

JSP是Servlet的擴充套件,在沒有JSP之前,就已經出現了Servlet技術。Servlet是利用輸出流動態生成HTML頁面,包括每一個HTML標籤和每個在HTML頁面中出現的內容。

JSP通過在標準的HTML頁面中插入Java程式碼,其靜態的部分無須Java程式控制,只有那些需要從資料庫讀取並根據程式動態生成資訊時,才使用Java指令碼控制。

事實上,JSP是Servlet的一種特殊形式,每個JSP頁面就是一個Servlet例項——JSP頁面由系統編譯成Servlet,Servlet再負責響應使用者請求。JSP其實也是Servlet的一種簡化,使用JSP時,其實還是使用Servlet,因為Web應用中的每個JSP頁面都會由Servlet容器生成對應的Servlet。對於Tomcat而言,JSP頁面生成的Servlet放在work路徑對應的Web應用下。

以apache-tomcat-7.0.37\webapps\myapp\index.jsp為例,

<html>
<body>
<center> 
Now time is: <%=new java.util.Date()%> 
</center>
</body>
</html>

當啟動Tomcat之後,可以在Tomcat的apache-tomcat-7.0.37\work\Catalina\localhost\myapp\org\apache\jsp目錄下找到如下檔案:indexd.java和index.class。這兩個檔案都是Tomcat生成的,Tomcat根據JSP頁面生成對應Servlet的Java檔案及class檔案。

index.java

//JSP頁面經過Tomcat編譯後預設的包
package org.apache.jsp;

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

//繼承HttpJspBase類,該類其實是個Servlet的子類
public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent {

  private static final javax.servlet.jsp.JspFactory _jspxFactory =
          javax.servlet.jsp.JspFactory.getDefaultFactory();

  private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants;

  private javax.el.ExpressionFactory _el_expressionfactory;
  private org.apache.tomcat.InstanceManager _jsp_instancemanager;

  public java.util.Map<java.lang.String,java.lang.Long> getDependants() {
    return _jspx_dependants;
  }

  public void _jspInit() {
    _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
    _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig());
  }

  public void _jspDestroy() {
  }

  //用於響應使用者的方法
  public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
        throws java.io.IOException, javax.servlet.ServletException {

    final javax.servlet.jsp.PageContext pageContext;
    javax.servlet.http.HttpSession session = null;
    final javax.servlet.ServletContext application;
    final javax.servlet.ServletConfig config;
	//獲得頁面輸出流
    javax.servlet.jsp.JspWriter out = null;
    final java.lang.Object page = this;
    javax.servlet.jsp.JspWriter _jspx_out = null;
    javax.servlet.jsp.PageContext _jspx_page_context = null;


	//開始生成響應
    try {
	  //設定輸出的頁面格式
      response.setContentType("text/html");
      pageContext = _jspxFactory.getPageContext(this, request, response,
      			null, true, 8192, true);
      _jspx_page_context = pageContext;
      application = pageContext.getServletContext();
      config = pageContext.getServletConfig();
      session = pageContext.getSession();
	  //頁面輸出流
      out = pageContext.getOut();
      _jspx_out = out;

	  //輸出流,開始輸出頁面文件
      out.write("<html>\r\n");
      out.write("<body>\r\n");
      out.write("<center> \r\n");
      out.write("Now time is: ");
      out.print(new java.util.Date());
      out.write(" \r\n");
      out.write("</center>\r\n");
      out.write("</body>\r\n");
      out.write("</html>");
    } catch (java.lang.Throwable t) {
      if (!(t instanceof javax.servlet.jsp.SkipPageException)){
        out = _jspx_out;
        if (out != null && out.getBufferSize() != 0)
          try { out.clearBuffer(); } catch (java.io.IOException e) {}
        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
        else throw new ServletException(t);
      }
    } finally {
      _jspxFactory.releasePageContext(_jspx_page_context);
    }
  }
}
JSP頁面中內建了幾個物件,如pageContext、application、config、page、session、out等_jspService()方法,這幾個內建物件就是在這裡定義的。

根據上面的JSP頁面工作原理圖,可以得到如下結論: JSP檔案必須在JSP伺服器內執行。JSP檔案必須生成Servlet才能執行。每個JSP頁面的第一個訪問者速度很慢,因為必須等待JSP編譯成Servlet。JSP頁面的訪問者無須安裝任何客戶端,甚至不需要可以執行Java的執行環境,因為JSP頁面輸送到客戶端的是標準HTML頁面。index.jsp頁面中的每個字元都由index.java檔案的輸出流生成.
servlet是在web伺服器上的java程式,它提供服務,由它來傳遞給你html的格式。Servlet API為Servlet提供了統一的程式設計介面
Servlet必須部署在Servlet容器,才能響應客戶端的請求  對外提供服務。要對外統一介面,由容器來呼叫。
jsp側重顯示;servlet側重控制邏輯。
MVC模式:Jsp + Servlet + JavaBean。M-JavaBean V-Jsp C-Servlet
小應用程式(Applet)是指採用Java建立的基於HTML的程式。瀏覽器將其暫時下載到使用者的硬碟上,並在Web頁開啟時在本地執行。們可以直接嵌入到網頁或者其他特定的容器中,並能夠產生特殊的效果。

所有基於Java的伺服器端程式設計都是構建在Servlet之上的。在J2EE中Servlet已經是一個標準的元件。

目前,Servlet引擎一般是第三方的外掛,它通過一定的方法連線到Web伺服器,Servlet引擎把它識別為Servlet請求的那些HTTP請求截獲下來處理,而其他的HTTP請求由Web伺服器按照通常的方式來處理,Servlet引擎會裝載合適的Servlet到記憶體中,如果Servlet還沒有執行的話,會分配一個可以使用的執行緒來處理請求,再把Servlet的輸出返回到發出請求的Web客戶機。

Java Servlet和Java Applet正好是相對應的兩種程式型別,Applet執行在客戶端,在瀏覽器內執行,而Servlet在伺服器內部執行,通過客戶端提交的請求啟動執行。

同樣的Servlet完全可以在Apache,IIS等不同Web伺服器上執行,不管底層的作業系統是Windows,Solaris,Mac,Linux。

Java Servlet有著十分廣泛的應用。使用Servlet還可以實現大量的伺服器端的管理維護功能,以及各種特殊的任務,比如,併發處理多個請求,轉送請求,代理等。

典型的Servlet執行環境有JSWDK,Tomcat,Resin等,。它們都自帶一個簡單的HTTP Server,只需簡單配置即可投入使用,你也可以把它們繫結到常用的Web伺服器上,如Apache,IIS等,提供小規模的Web服務。還有一些商業的大中型的支援Servlet和JSP的Web伺服器,如JRun,Web Sphere,Web Logic等等,配置比較複雜,並不適合初學者。但是功能較為強大,有條件的讀者可以一試。

      import java.io.*;
   import java.util.*;
   import javax.servlet.http.*;
   import javax.servlet.*;
   //匯入必要的包
   public class HelloServlet extends HttpServlet { 
     //所有Servlet必須從HttpServlet派生
     public void doGet (HttpServletRequest req, HttpServletResponse res) 
     throws ServletException, IOException 
     //doGet()是這個Servlet的核心,真正處理請求的地方
   {
   res.setContentType("text/html"); 
     //設定相應的型別為text/html
   PrintWriter pw = res.getWriter(); 
     //從HttpServletResponse得到輸出流   
     pw.println("<!DOCTYPE HTML PUBLIC ""-//W3C//DTD HTML 4.0 Transitional//EN"">");
   pw.println("<head>");
   pw.println("<meta http-equiv=""Content-Type"" content=""text/html; charset=ISO-8859-1"">");
   pw.println("<!-- The Servlet expression tags interpolate script variables into the HTML -->");
   pw.println("<title>Hello, world!</title>");
   pw.println("</head>");
   pw.println("<body bgcolor=#cc99dd>");
   pw.println("<h1>Hello, world!</h1>");
   pw.println("</body>");
   //上面的語句都是向客戶端列印HTML文字
   pw.close(); 
   //關閉HttpServletResponse,使Web伺服器知道相應結束
  }
  public HelloServlet() {} //建構函式,可以不要
 }
Servlet與表單互動的方法
Servlet使用HttpServlet類中的方法與表單進行互動。在HttpServlet類中有幾個未完全實現的方法,你可以自己定義方法的內容,但是必須正確使用方法名稱以使HTTP Server把客戶請求正確的對映到相應的函式上。
  doHeader 用於處理HEADER請求
  doGet 用於處理GET請求,也可以自動的支援HEADER請求
  doPost 用於處理POST請求
  doPut 用於處理PUT請求
  doDelete 用於處理DELETE請求

在使用這些方法時必須帶兩個引數。第一個包含來自客戶端的資料HttpServletRequest。第二個引數包含客戶端的相應HttpServletResponse。

一個HttpServletRequest物件提供請求HTTP頭部資料,也允許獲取客戶端的資料。怎樣獲取這些資料取決於HTTP請求方法。不管何種HTTP方式,都可以用getParameterValues方法返回特定名稱的引數值。HttpServletRequest,HttpServletResponse介面分別繼承於ServletRequest和ServletResponse介面,getParameterValues和getWriter方法都是其祖先介面中的方法。

對於HTTP GET請求的方式,getQueryString方法將會返回一個可以用來解剖分析的引數值。
  對於用HTTP POST,PUT和DELETE請求的方式,HttpServletRequest有兩種方法可以選擇:如果是文字資料,你能通過getReader的方法得到BufferedReader獲取資料;如果是二進位制資料,可以通過getInputStream方法得到ServletInputStream獲取資料。
  為了相應客戶端,一個HttpServletResponse物件提供返回資料給使用者的兩個方法:一種是用getWriter方法得到一個PrintWriter,用於返回文字資料;另一種方法是用getOutputStream方法得到ServletOutputStream,用於返回二進位制資料。在使用Writer或OutputStream之前應先設定頭部(HttpServletResponse中有相應的方法),然後用Writer或OutputStream將相應的主體部分發給使用者。完成後要關閉Writer或OutputStream以便讓伺服器知道相應已經結束。

在進行HTTP網路傳輸的時候,統一採用的編碼方式是ISO-8859-1

字元編碼轉換常用的方法是
  String native_encoded = "中文字串"; //本地編碼的字串
  Byte[] byte_array = native_encoded.getBytes(); //得到本地編碼的位元組陣列
  String net_encoded = new String(native_encoded, "ISO-8859-1"); //生成ISO-8859-1編碼的字串

例:out.println(new String(new String("<td>你的姓名:</td>").getBytes(),"ISO-8859-1"));

用Servlet控制會話

會話狀態的維持是開發Web應用所必須面對的問題,有多種方法可以來解決這個問題,如使用Cookies,或直接把狀態資訊加到URL中等,還有Servlet本身提供了一個HttpSession介面來支援會話狀態的維持 。

Session的發明是為了填補HTTP協議的侷限。

從伺服器這端來看,每一個請求都是獨立的,因此HTTP協議被認為是無狀態協議,當用戶在多個主頁間切換時,伺服器無法知道他的身份。Session的出現就是為了彌補這個侷限。利用Session,您就可以當一個使用者在多個主頁間切換的時候也能儲存他的資訊。這樣很多以前根本無法去做的事情就變得簡單多了。
    在訪問者從到達某個特定的主頁到離開為止的那段時間,每個訪問者都會單獨獲得一個Session

Java Servlet定義了一個HttpSession介面,實現的Session的功能,在Servlet中使用Session的過程如下:
  (1) 使用HttpServletRequest的getSession方法得到當前存在的session,如果當前沒有定義session,則建立一個新的session,還可以使用方法getSession(true)
  (2) 寫session變數。可以使用方法HttpSession.setAttribute(name,value)來向Session中儲存一個資訊。
  (3) 讀Session變數。可以使用方法HttpSession.getAttribute(name)來讀取Session中的一個變數值,如果name是一個沒有定義的變數,那麼返回的是null。需要注意的是,從getAttribute讀出的變數型別是Object,必須使用強制型別轉換,比如:String uid = (String) session.getAttribute("uid");
  (4) 關閉session,當時用完session後,可以使用session.invalidate()方法關閉session。但是這並不是嚴格要求的。因為,Servlet引擎在一段時間之後,自動關閉seesion。
      HttpSession session = request.getSession(true); //引數true是指在沒有session時建立一個新的
   Date created = new Date(session.getCreationTime()); //得到session物件建立的時間
   out.println("ID " + session.getId()+"<br>"); //得到該session的id,並列印
   out.println("Created: " + created+"<br>");//列印session建立時間
   session.setAttribute("UID","12345678"); //在session中新增變數UID=12345678
   session.setAttribute("Name","Tom"); //在session中新增變數Name=Tom  10.2.4 Servlet的生命週期

跟客戶端的Applet相似,Servlet也遵循嚴格的生命週期。在每個Servlet例項的生命中有三種類型的事件,這三種事件分別對應於由Servlet引擎所喚醒的三個方法:
  1.init()。當Servlet第一次被裝載時,Servlet引擎呼叫這個Servlet的init()方法,只調用一次。系統保證,在init方法成功完成以前,是不會呼叫Servlet去處理任何請求的。
  2.service()。這是Servlet最重要的方法,是真正處理請求的地方。對於每個請求,Servlet引擎將呼叫Servlet的service方法,並把Servlet請求物件和Servlet響應物件作為引數傳遞給它。
  3.destroy()。這是相對於init的可選方法,當Servlet即將被解除安裝時由Servlet引擎來呼叫,這個方法用來清除並釋放在init方法中所分配的資源。 
Servlet的生命週期可以被歸納為以下幾步:
  (1) 裝載Servlet,這一項操作一般是動態執行的。然而,Servlet通常會提供一個管理的選項,用於在Servlet啟動時強制裝載和初始化特定的Servlet
  (2) Server建立一個Servlet例項
  (3) Server呼叫Servlet的init方法
  (4) 一個客戶端請求到達Server
  (5) Server建立一個請求物件
  (6) Server建立一個響應物件
  (7) Server啟用Servlet的service方法,傳遞請求和響應物件作為引數
  (8) service方法獲得關於請求物件的資訊,處理請求,訪問其他資源,獲得需要的資訊
  (9) service方法使用響應物件的方法。將響應傳回Server,最終到達客戶端。Service方法可能啟用其他方法以處理請求。如doGet,doPost或其他程式設計師自己開發的方法
  (10) 對於更多的客戶端請求,Server建立新的請求和響應物件,仍然啟用此servlet的service方法,將這兩個物件作為引數傳遞給它,如此重複以上的迴圈,但無需再次呼叫init方法,Servlet一般只初始化一次
  (11) 當Server不再需要Servlet時,比如當Server要關閉時,Server呼叫Servlet的destroy

JSP一般的執行方式為:當伺服器啟動後,當Web瀏覽器端傳送過來一個頁面請求時,Web伺服器先判斷是否是JSP頁面請求。如果該頁面只是一般的HTML/XML頁面請求,則直接將HTML/XML頁面程式碼傳給Web瀏覽器端。如果請求的頁面是JSP頁面,則由JSP引擎檢查該JSP頁面,如果該頁面是第一次被請求、或不是第一次被請求但已被修改,則JSP引擎將此JSP頁面程式碼轉換成Servlet程式碼,然後JSP引擎呼叫伺服器端的Java編譯器javac.exe對Servlet程式碼進行編譯,把它變成位元組碼(.class)檔案,然後再呼叫JAVA虛擬機器執行該位元組碼檔案,然後將執行結果傳給Web瀏覽器端。如果該JSP頁面不是第一次被請求,且沒有被修改過,則直接由JSP引擎呼叫JAVA虛擬機器執行已編譯過的位元組碼.class檔案,然後將結果傳送Web瀏覽器端。

採用JSP來表現頁面,採用Servlet來完成大量的處理,Servlet扮演一個控制者的角色,並負責響應客戶請求。Servlet建立JSP需要的Bean和物件,根據使用者的行為,決定將哪個JSP頁面傳送給使用者。特別要注意的是,JSP頁面中沒有任何商業處理邏輯,它只是簡單的檢索Servlet先前建立的Beans或者物件,再將動態內容插入預定義的模板。