Tomcat是如何將JSP代碼編譯成Servlet代碼的?

作者:郭無心
鏈接:https://www.zhihu.com/question/37962360/answer/74311244
來源:知乎
著作權歸作者所有,轉載請聯繫作者獲得授權。

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

由於包括大量的HTML標籤、大量的靜態文本及格式等,導致Servlet的開發效率極爲低下。所有的表現邏輯,包括佈局、色彩及圖像等,都必須耦合在Java代碼中,這的確讓人不勝其煩。

JSP的出現彌補了這種不足,JSP通過在標準的HTML頁面中插入Java代碼,其靜態的部分無須Java程序控制,只有那些需要從數據庫讀取並根據程序動態生成信息時,才使用Java腳本控制。

從表面上看,JSP頁面已經不再需要Java類,似乎完全脫離了Java面向對象的特徵。
事實上,JSP是Servlet的一種特殊形式,每個JSP頁面就是一個Servlet實例——JSP頁面由系統編譯成Servlet,Servlet再負責響應用戶請求。

JSP其實也是Servlet的一種簡化,使用JSP時,其實還是使用Servlet,因爲Web應用中的每個JSP頁面都會由Servlet容器生成對應的Servlet。對於Tomcat而言,JSP頁面生成的Servlet放在work路徑對應的Web應用下。

看下面一個簡單的JSP頁面:
<!-- 表明此爲一個JSP頁面 -->
<%@ page contentType="text/html; charset=gb2312" language="java" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
    <HEAD>
          <TITLE>第一個JSP頁面</TITLE>
    </HEAD>
    
    <BODY>
    <!-- 下面是Java腳本-->
        <%for(int i = 0 ; i < 10; i++)
          {
            out.println(i);
        %>
       <br>
        <%}
        %>
    </BODY>
</HTML>

當啓動Tomcat之後,可以在Tomcat的Catalina/localhost/jsp/test/org/apache/jsp目錄下找到如下文件(假如Web應用名爲jsptest,上面JSP頁的名爲test1.jsp):test1_jsp.java和test1_jsp.class。

這兩個文件都是Tomcat生成的,Tomcat根據JSP頁面生成對應Servlet的Java文件及class文件。

下面是test1_jsp.java文件的源代碼,這是一個特殊的Java類,是一個Servlet類:

//JSP頁面經過Tomcat編譯後默認的包(不同的servlet容器提供商生成的servlet文件是不同的)


package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
    
//繼承HttpJspBase類,該類其實是個HttpServlet的子類(jasper是tomcat的jsp engine)
public final class test1_jsp extends org.apache.jasper.runtime.HttpJspBase
         implements org.apache.jasper.runtime.JspSourceDependent
    {
         private static java.util.Vector _jspx_dependants;
         public java.util.List getDependants() {
             return _jspx_dependants;
         }
         //用於響應用戶的方法
         public void _jspService(HttpServletRequest request,
            HttpServletResponse response)
            throws java.io.IOException, ServletException
         {   //built-in objects(variavles) are created here.
             //獲得頁面輸出流
             JspFactory _jspxFactory = null;
             PageContext pageContext = null;
             HttpSession session = null;
             ServletContext application = null;
             ServletConfig config = null;
             //獲得頁面輸出流
             JspWriter out = null; //not PrintWriter. JspWriter is buffered defautly.
             Object page = this;
             JspWriter _jspx_out = null;
             PageContext _jspx_page_context = null;
             //開始生成響應
             try
             {
                 _jspxFactory = JspFactory.getDefaultFactory();
                //設置輸出的頁面格式
            response.setContentType("text/html; charset=gb2312");
            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("rn");
            //下面輸出HTML標籤
            out.write("<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0
            Transitional//EN">rn");
            out.write("<HTML>rn");
            out.write("<HEAD>rn");
            out.write("<TITLE>first Jsp</TITLE>rn");
            out.write("</HEAD>rn");
            out.write("<BODY>rn");
            //頁面中的循環,在此處循環輸出
            for(int i = 0 ; i < 10; i++)
            {
             out.println(i);
             out.write("rn");
             out.write("<br>rn");
            }
            out.write("rn");
            out.write("</BODY>rn");
            out.write("</HTML>rn");
            out.write("rn");
           }
           catch (Throwable t)
           {
            if (!(t instanceof SkipPageException))
            {
               out = _jspx_out;
               if (out != null && out.getBufferSize() != 0)
                 out.clearBuffer();
               if (_jspx_page_context != null) _jspx_page_context.handle
               PageException(t);
            }
          }
           finally
         {
           if (_jspxFactory != null) _jspxFactory.releasePageContext(_jspx_
           page_context);
        }
      }
}

對比test1.jsp和test1_jsp.java文件,可得到一個結論:該JSP頁面中的每個字符都由test1_jsp.java文件的輸出流生成。

根據上面的JSP頁面工作原理圖,可以得到如下四個結論:

JSP文件必須在JSP服務器內運行。

JSP文件必鬚生成Servlet才能執行。

每個JSP頁面的第一個訪問者速度很慢,因爲必須等待JSP編譯成Servlet。

JSP頁面的訪問者無須安裝任何客戶端,甚至不需要可以運行Java的運行環境,因爲JSP頁面輸送到客戶端的是標準HTML頁面。


JSP和Servlet會有如下轉換:

JSP頁面的靜態內容、JSP腳本都會轉換成Servlet的xxxService()方法,類似於自行創建Servlet時service()方法。

JSP聲明部分,轉換成Servlet的成員部分。所有JSP聲明部分可以使用private,protected,public,static等修飾符,其他地方則不行。

JSP的輸出表達式(<%= ..%>部分),輸出表達式會轉換成Servlet的xxxService()方法裏的輸出語句。

九個內置對象要麼是xxxService()方法的形參,要麼是該方法的局部變量,所以九個內置對象只能在JSP腳本和輸出表達式中使用。// 不能在jsp Declaration中使用

<TOMCAT_HOME>/conf/web.xml這個文件,裏面有這樣一段
    <servlet>
        <servlet-name>jsp</servlet-name>
        <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
        <init-param>
            <param-name>fork</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>xpoweredBy</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>3</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>jsp</servlet-name>
        <url-pattern>*.jsp</url-pattern>
    </servlet-mapping>

然後再去看看org.apache.jasper.servlet.JspServlet這個類,跟着就會看到org.apache.jasper.servlet.JspServletWrapper這個類jsaper包下的類就是解析JSP代碼的類了



/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
作者:chainho
鏈接:https://www.zhihu.com/question/37962360/answer/76489736
來源:知乎
著作權歸作者所有,轉載請聯繫作者獲得授權。

JSP做爲一種特殊的Servlet,在運行時接受請求開始編譯,如果jsp文件已經更新或者被生成的servlet文件被刪除,則會重新編譯生成。具體生成時使用JDTCompiler。
&amp;lt;img src=&quot;https://pic2.zhimg.com/491b546497950691b4ff12b472655e21_b.jpg&quot; data-rawwidth=&quot;702&quot; data-rawheight=&quot;368&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;702&quot; data-original=&quot;https://pic2.zhimg.com/491b546497950691b4ff12b472655e21_r.jpg&quot;&amp;gt;

而整個生成的過程類似於將jsp中的內容翻譯下,輸出字符串到文件一樣。
整個處理過程類似於下面這個樣子,是我大概畫的一個時序圖。
&amp;lt;img src=&quot;https://pic1.zhimg.com/57894e9d5fb4f55b0f13506dcc69fb34_b.png&quot; data-rawwidth=&quot;1974&quot; data-rawheight=&quot;805&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;1974&quot; data-original=&quot;https://pic1.zhimg.com/57894e9d5fb4f55b0f13506dcc69fb34_r.png&quot;&amp;gt;

其中生成的文件你看到的類似下面這個樣子:
&amp;lt;img src=&quot;https://pic1.zhimg.com/ca14a719f4b2e1330194bb7f7fe77310_b.jpg&quot; data-rawwidth=&quot;536&quot; data-rawheight=&quot;596&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;536&quot; data-original=&quot;https://pic1.zhimg.com/ca14a719f4b2e1330194bb7f7fe77310_r.jpg&quot;&amp;gt;
而相應的生成代碼則是這樣的:
	 private void generateCommentHeader() {
        out.println("/*");
        out.println(" * Generated by the Jasper component of Apache Tomcat");
        out.println(" * Version: " + ctxt.getServletContext().getServerInfo());
        out.println(" * Generated at: " + timestampFormat.format(new Date()) +
                " UTC");
        out.println(" * Note: The last modified time of this file was set to");
        out.println(" *       the last modified time of the source file after");
        out.println(" *       generation to assist with modification tracking.");
        out.println(" */");
    }

再比如,生成類聲明的代碼大概是這個樣子:
// Generate class declaration
        out.printin("public final class ");
        out.print(servletClassName);
        out.print(" extends ");
        out.println(pageInfo.getExtends());
        out.printin("    implements org.apache.jasper.runtime.JspSourceDependent,");
        out.println();
        out.printin("                 org.apache.jasper.runtime.JspSourceImports");
        if (!pageInfo.isThreadSafe()) {
            out.println(",");
            out.printin("                 javax.servlet.SingleThreadModel");
        }
        out.println(" {");
        out.pushIndent();

---------------------------------------------------------------------------------------------------------------------------------

有機會自己找一些例子來分析下


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

先把java代碼和html代碼通過解析,分離。然後new一個servlet,函數裏面加jsp裏的java代碼,把一些變量的值跟html拼起來,作爲response。

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

看一下jvm類加載機制就知道了,tomcat是把jvm封裝了一層,jsp編譯後和servlet編譯後是一樣的,然後使用jvm加載進來

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章