JSP的運行內幕_JSP編程

來源:互聯網
上載者:User

    經常有朋友問起,JSP和Servlet之間有什麼區別,兩者之間又有什麼聯絡?其實Servlet技術的出現時間很早,是當時為了Java的伺服器端應用而開發的。大家都知道Applet是應用小程式,Servlet就是伺服器端小程式了。但在Microsoft公司的ASP技術出現後,使用Servlet進行響應輸出時一行行的輸出語句就顯得非常笨拙,對於複雜布局或者顯示頁面更是如此。JSP就是為了滿足這種需求在Servlet技術之上開發的。可見,JSP和Servlet之間有著內在的血緣關係,在學習JSP時,如果能夠抓住這種聯絡,就能更深刻地理解JSP的運行機理,達到事半功倍的效果。

本文將通過對一個JSP運行過程的剖析,深入JSP啟動並執行內幕,並從全新的視角闡述一些JSP中的技術要點。

HelloWorld.jsp

我們以Tomcat 4.1.17伺服器為例,來看看最簡單的HelloWorld.jsp是怎麼啟動並執行。

代碼清單1:HelloWorld.jsp

HelloWorld.jsp
<%
 String message = "Hello World!";
%>
<%=message%>
 


  這個檔案非常簡單,僅僅定義了一個String的變數,並且輸出。把這個檔案放到Tomcat的webapps\ROOT\目錄下,啟動Tomcat,在瀏覽器中訪問http://localhost:8080/HelloWorld.jsp,瀏覽器中的輸出為“HelloWorld!”

  讓我們來看看Tomcat都做了什麼。轉到Tomcat的\work\Standalone\localhost\_目錄下,可以找到如下的HelloWorld_jsp.java,這個檔案就是Tomcat解析HelloWorld.jsp時產生的源檔案:

  代碼清單2:HelloWorld_jsp.java

package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;

public class HelloWorld_jsp extends HttpJspBase {
 ......
public void _jspService(HttpServletRequest request,
HttpServletResponse response)throws java.io.IOException, ServletException
 {
  JspFactory _jspxFactory = null;
  javax.servlet.jsp.PageContext pageContext = null;
  HttpSession session = null;
  ServletContext application = null;
  ServletConfig config = null;
  JspWriter out = null;
  Object page = this;
  JspWriter _jspx_out = null;

  try {
   _jspxFactory = JspFactory.getDefaultFactory();
   response.setContentType("text/html;charset=ISO-8859-1");
   pageContext = _jspxFactory.getPageContext(this, request, response,null, true, 8192, true);
   application = pageContext.getServletContext();
   config = pageContext.getServletConfig();
   session = pageContext.getSession();
   out = pageContext.getOut();
   _jspx_out = out;

   String message = "Hello World!";
   out.print(message);
  } catch (Throwable t) {
   out = _jspx_out;
   if (out != null && out.getBufferSize() != 0)
    out.clearBuffer();
   if (pageContext != null) pageContext.handlePageException(t);
  } finally {
  if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);
  }
 }
}
 


  從上面可以看出,HelloWorld.jsp在運行時首先解析成一個Java類HelloWorld_jsp.java,該類繼承於org.apache.jasper.runtime.HttpJspBase基類,HttpJspBase實現了HttpServlet介面。可見,JSP在運行前首先將編譯為一個Servlet,這就是理解JSP技術的關鍵。

  我們還知道JSP頁面中內建了幾個對象,如pageContext、application、config、page、session、out等,你可能會奇怪,為什麼在JSP中的代碼片斷中可以直接使用這些內建對象。觀察_jspService()方法,實際上這幾個內建對象就是在這裡定義的。在對JSP檔案中的代碼片斷進行解析之前,先對這幾個內建對象進行初始化。

  首先,調用JspFactory的getDefaultFactory()方法擷取容器實現(本文中指Tomcat 4.1.17)的一個JspFactory對象的引用。JspFactory是javax.servlet.jsp包中定義的一個抽象類別,其中定義了兩個靜態方法set/getDefaultFactory()。set方法由JSP容器(Tomcat)執行個體化該頁面Servlet(即HelloWorld_jsp類)的時候置入,所以可以直接調用JspFactory.getDefaultFactory()方法得到這個JSP工廠的實作類別。Tomcat是調用org.apache.jasper.runtime.JspFactoryImpl類。

  然後,調用這個JspFactoryImpl的getPageContext()方法,填充一個PageContext返回,並賦給內建變數pageConext。其它內建對象都經由該pageContext得到。具體過程見上面的代碼,這裡不再贅述。該頁面Servlet的環境設定完畢,開始對頁面進行解析。HelloWorld.jsp頁面僅僅定義了一個String變數,然後直接輸出。解析後的代碼如下:

  代碼清單3:JSP頁面解析後的代碼片斷

String message = "Hello World!";
out.print(message);
 


  定製標籤的解析過程

  在一個中大型的Web應用中,通常使用JSP定製標籤來封裝頁面顯示邏輯。剖析容器對定製標籤的解析過程,對我們深入理解定製標籤的運行機理非常有協助。下面我們以Struts1.1b中附帶的struts-example應用的首頁運行為例加以說明。

  包含定製標籤的index.jsp

  Struts1.1b的下載地址是http://jakarta.apache.org/struts/index.html。將下載的包解壓,在webapps目錄下可以找到struts-example.war。將該War包拷貝到Tomcat的webapps目錄下,Tomcat會自動安裝此應用程式套件。在瀏覽器中通過http://localhost:8080/struts-example訪問struts-example應用,將顯示應用的首頁(見圖1)。

 

  圖一 應用的首頁

  代碼清單4:index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<html:html locale="true">
<head>
<title><bean:message key="index.title"/></title>
<html:base/>
</head>
<body bgcolor="white">
……
</body>
</html:html>
 


  我們僅以index.jsp中的<bean:message/>標籤的解析為例進行分析,看容器是怎樣把這個自訂標籤解析成HTML輸出的。上面代碼省略了頁面的其它顯示部分。首先,查看上面瀏覽器中頁面的源檔案:

<html lang="zh">
<head>
<title>MailReader Demonstration Application (Struts 1.0)</title>
</head>
<body bgcolor="white">
……
</body>
</html>
 


  可見,容器已經把<bean:message key="index.title"/>替換成一個字串,顯示為頁面的標題。

  解析過程

  那麼,JSP容器是怎樣完成解析的呢?查看在工作目錄jakarta-tomcat-4.1.17\work\Standalone\localhost\struts-example下解析後的index_jsp.java檔案:

  代碼清單5:index_jsp.java

package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;
public class index_jsp extends HttpJspBase {
 //為所有的定製標籤定義處理器池類的引用
 private org.apache.jasper.runtime.TagHandlerPool ;
 _jspx_tagPool_bean_message_key;
 ……
 //頁面類構造方法
 public index_jsp() {
  _jspx_tagPool_bean_message_key =
  new org.apache.jasper.runtime.TagHandlerPool();
   ……
 }

 public void _jspService(HttpServletRequest request,
   HttpServletResponse response)
   throws java.io.IOException, ServletException {
  ……
  _jspxFactory = JspFactory.getDefaultFactory();
  response.setContentType("text/html;charset=UTF-8");
  pageContext = _jspxFactory.getPageContext(this,
    request, response,null, true, 8192, true);
  application = pageContext.getServletContext();
  config = pageContext.getServletConfig();
  session = pageContext.getSession();
  out = pageContext.getOut();
  _jspx_out = out;
  ……
  if (_jspx_meth_html_html_0(pageContext))
  return;
  ……
 }
 //頁面在處理退出時釋放所有定製標籤的屬性
 public void _jspDestroy() {
  _jspx_tagPool_bean_message_key.release();
  ……
 }
}
 


  產生的index_jsp.java繼承於org.apache. jasper.runtime.HttpJspBase。研究這個檔案為我們瞭解定製標籤的運行機理提供了途徑。

  從上面可以看出,Tomcat在解析一個JSP頁面時,首先為每一個定製標籤定義並執行個體化了一個TagHandlerPool對象。頁面的處理方法覆蓋父類的_ jspService()方法,_jspService方法首先初始化環境,為內建對象賦值。由於index.jsp頁面整體由一個<html:html/>標籤包裹,Tomcat對每一個標籤都產生一個私人方法加以實現。<html:html/>標籤的處理方法是_jspx_meth_html_html_0()。這個方法的命名規範大家也可以從這裡看出,就是“_jspx_meth + 標籤的首碼 + 標籤名 + 該標籤在JSP頁面同類標籤中出現的序號”。其它標籤都被包含在該標籤中,所以其它標籤在_jspx_meth_html_html_0()方法中進行解析。具體的代碼實現請參見賽迪網http://linux.ccidnet.com期刊瀏覽2003年第6期。

  在_jspx_meth_html_html_0()方法中,首先從_jspx_tagPool_html_html_locale池中得到一個org.apache.struts.taglib.html.HtmlTag的執行個體,然後設定這個tag執行個體的頁面上下文及上級標籤,由於html:html標籤是頁面的最頂層標籤,所以它的parent是null。然後對該標籤的內容進行解析。HTML代碼直接輸出,下面主要看看<html:html></html:html>標籤之間包含的<bean:message key="index.title"/>標籤的解析。對bean:message標籤的解析類似於html:html,Tomcat也將其放入一個單獨的方法_jspx_meth_bean_message_0()中進行。

  bean:message標籤的解析

  代碼清單7:_jspx_meth_bean_message_0()方法片斷

//對message定製標籤的處理方法
private boolean _jspx_meth_bean_message_0(
javax.servlet.jsp.tagext.Tag _jspx_th_html_html_0,
javax.servlet.jsp.PageContext pageContext) throws Throwable {
 JspWriter out = pageContext.getOut();
 /* ----  bean:message ---- */
 org.apache.struts.taglib.bean.MessageTag
 _jspx_th_bean_message_0 =
 (org.apache.struts.taglib.bean.MessageTag)
 _jspx_tagPool_bean_message_key.get(
 org.apache.struts.taglib.bean.MessageTag.class);
 _jspx_th_bean_message_0.setPageContext(pageContext);
 _jspx_th_bean_message_0.setParent(_jspx_th_html_html_0);
 _jspx_th_bean_message_0.setKey("index.title");
 int _jspx_eval_bean_message_0 = _jspx_th_bean_message_0.doStartTag();
 if (_jspx_th_bean_message_0.doEndTag()== javax.servlet.jsp.tagext.Tag.SKIP_PAGE)
  return true;
 _jspx_tagPool_bean_message_key.reuse(_jspx_th_bean_message_0);
  return false;
}
 


  同樣,對html:bean也需要從池中得到一個標籤類的執行個體,然後設定環境。這裡不再贅述。我們只專註對MessageTag定製標籤類特殊的處理部分。定製標籤類的開發不在本文討論範圍之內。在index.jsp中定義了一個bean:message標籤,並設定了一個屬性:<bean:message key="index.title"/>。Tomcat在解析時,調用MessageTag對象的key屬性設定方法setKey(),將該屬性置入。然後調用MessageTag的doStartTag()和doEndTag()方法,完成解析。如果doEndTag()方法的傳回值為javax.servlet.jsp.tagext.Tag. SKIP_PAGE,表明已經完成解析,返回true,Tomcat將立即停止剩餘頁面代碼的執行,並返回。否則把該MessageTag的執行個體放回池中。

  標籤類對象執行個體的池化

  為了提高運行效率,Tomcat對所有的定製標籤類進行了池化,池化工作由org.apache.jasper. runtime.TagHandlerPool類完成。TagHandlerPool類主要有兩個方法,代碼如下:

  代碼清單8:TagHandlerPool.java

public class TagHandlerPool {
 private static final int MAX_POOL_SIZE = 5;
 private Tag[] handlers;
 public synchronized Tag get(Class handlerClass) throws JspException {……}
 public synchronized void reuse(Tag handler) {……}
}
 


  TagHandlerPool簡單地實現了對標籤類的池化,其中MAX_POOL_SIZE是池的初始大小,handlers是一個Tag的數組,儲存標籤類的執行個體。get(Class handlerClass)得到一個指定標籤類的執行個體,如果池中沒有可用執行個體,則新執行個體化一個。reuse(Tag handler)把handler對象放回池中。

  至此,我們對JSP在容器中的運行過程已經瞭然於胸了。雖然每種JSP容器的解析結果會有差異,但其中的原理都雷同。對於編寫JSP應用,我們並不需要幹涉容器中的運行過程,但如果你對整個底層的運行機制比較熟悉,就能對JSP/Servlet技術有更深的認識。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.