隨筆-295  評論-26  文章-1  trackbacks-0
          JSP的運行內幕

          文章作者:
          責任編輯: 錄入時間:2004-11-1 18:26:13 來源:第七頻道
          頻道聲明:本頻道的文章除部分特別聲明禁止轉載的專稿外,可以自由轉載.但請務必注明出出處和原始作者 文章版權歸本頻道與文章作者所有.對于被頻道轉載文章的個人和網站,我們表示深深的謝意.?

          經常有朋友問起,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技術有更深的認識。


          大盤預測 國富論
          posted on 2007-09-10 16:53 華夢行 閱讀(196) 評論(0)  編輯  收藏 所屬分類: Tomcat

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 台北县| 洪泽县| 静安区| 凤庆县| 镇雄县| 华宁县| 浦城县| 佛坪县| 江源县| 新乡县| 台山市| 布尔津县| 淮北市| 铜川市| 友谊县| 商水县| 易门县| 彭州市| 元朗区| 彩票| 永宁县| 普兰县| 麟游县| 大渡口区| 简阳市| 安龙县| 九江市| 延吉市| 绥滨县| 靖远县| 稻城县| 吉林市| 威信县| 梨树县| 会东县| 焉耆| 寿宁县| 尼玛县| 云林县| 抚松县| 德兴市|