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


出處不明:

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頁面:

&
&<%@ page contentType="text/html; charset=gb2312" language="java" %&>
&
&
&
&第一個JSP頁面&
&

&
&
&<%for(int i = 0 ; i &< 10; i++) { out.println(i); %&>
&
&<%} %&>
&

&

當啟動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("&rn");
out.write("&rn");
out.write("&rn");
out.write("&first Jsp&rn");
out.write("&
rn");
out.write("&rn");
//頁面中的循環,在此處循環輸出
for(int i = 0 ; i &< 10; i++) { out.println(i); out.write("rn"); out.write("&rn");
}
out.write("rn");
out.write("&
rn");
out.write("&
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中使用

&/conf/web.xml這個文件,裡面有這樣一段
&
&jsp&
&org.apache.jasper.servlet.JspServlet&
&
&

fork& &

false& &
&
&

xpoweredBy& &

false& &
&3&
&

&
&jsp&
&*.jsp&
&

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

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

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


JSP做為一種特殊的Servlet,在運行時接受請求開始編譯,如果jsp文件已經更新或者被生成的servlet文件被刪除,則會重新編譯生成。具體生成時使用JDTCompiler。

而整個生成的過程類似於將jsp中的內容翻譯下,輸出字元串到文件一樣。

整個處理過程類似於下面這個樣子,是我大概畫的一個時序圖。

其中生成的文件你看到的類似下面這個樣子:

而相應的生成代碼則是這樣的:

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。


題主的這個問題問的是JSP編譯成Servlet的機制,而高票答案其實只是科普了JSP和Servlet的關係,以及JSP的成份和轉化成的Servlet的組成成份,並沒有講到伺服器是如何將jsp轉化成servlet的。

下面是我的表演:

  1. 將jsp頁面部署在tomcat/webapps目錄下或者tomcat/webapps子目錄下,啟動伺服器。
  2. 當客戶在第一次請求JSP頁面時JSP Engine(JSP引擎)將JSP網頁轉譯為Servlet
  3. JSPC(jspc編譯器對轉譯生成的Servlet編譯成Servlet.class
  4. 由伺服器訪問Servlet.class並且將對應內容響應給客戶

核心名稱解釋:

jspc compiler. Handles all options associated with the command line and creates compilation contexts which it then compiles according to the specified options.

【譯文】:jspc編譯器處理所有與指定命令行相關聯的選項,並創建編譯上下文。


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


推薦閱讀:

tomcat中對靜態資源的訪問也會用servlet來處理嗎?

TAG:Java | ApacheTomcat | JavaWeb | Java程序員 | Servlet |