深入理解JSP(一)

  • 2019 年 10 月 10 日
  • 筆記

本文首發於微信公眾號:"算法與編程之美",歡迎關注,及時了解更多此系列文章。

在Java Web的開發過程中,可能有過這樣的疑問,Tomcat是一個Servlet運行環境(容器),所有經過Tomcat的請求都是由一個Servlet來處理的。Servlet是一個Java類,可是JSP不是,那JSP又是怎麼在Tomcat裏面運行的呢?

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

根據上面的分析,我們又有一個疑問:為什麼說JSP就是一個Servlet,是根據什麼來判定的呢?

1 為什麼說JSP是Servlet

首先在IntelljIdea裏面編寫一個簡單的test.jsp頁面

當啟動Tomcat後,可以在Tomcat的workCatalinalocalhostROOTorgapachejsp目錄下找到如下文件:

這兩個文件便是test.jsp經過系統編譯生成的對應的 .java文件和 .class文件,打開test_jsp.java文件

可以看到,這個test_jsp類繼承了HttpJspBase,並且實現了org.apache.jasper.runtime.JspSourceDependent與org.apache.jasper.runtime.JspSourceImports這兩個接口,我們知道判定一個類是Servlet的方法是看這個類是否間接或直接實現了Servlet接口,所以我們要看test_jsp這個類是否間接或直接實現了該接口,這裡它並沒有直接實現Servlet接口,所以現在我們還不能從這裡看出test_jsp這個類就是一個Servlet,我們便可以猜想,會不會是HttpJspBase這個類實現了此接口呢。

於是我們進一步分析HttpJspBase這個類的繼承結構。

我們去查看HttpJspBase類的源碼

從上面就可以看出,該類繼承了HttpServlet,我們之前便知道HttpServlet間接實現了Servlet接口。如果不知道的我們可以在IntelljIdea裏面將鼠標定位在HttpJspBase上面按住ctrl+alt+u查看他的繼承關係圖:

圖 1-1 HttpJspBase繼承關係

到這裡我們便能清晰的知道為什麼說JSP是一個Servlet了。

2 Tomcat處理JSP文件流程分析

經過上面的分析,我們已經知道了JSP就是一個Servlet。那JSP又是如何轉換成Servlet的呢?每一個Servlet都需要在web.xml 文件進行配置,這樣瀏覽器才能訪問得到這個Servlet。可是現在這個test.jsp並沒有在我們項目的web.xml裏面做任何配置,那瀏覽器是怎麼訪問到jsp頁面的呢?回答這個問題之前我們先來了解一下Tomcat如何響應靜態資源。

DefaultServlet介紹

本質上講,Tomcat對於所有的靜態資源會做統一處理,也就是在所有你沒有配置URL匹配的地方,Tomcat這個全局統一處理的配置就開始接管工作了。在Tomcat的conf目錄下面可以看到有一個web.xml文件,打開後你會發現這樣的說明:

在向下你會看到關於這個全局處理的Servlet聲明,如下圖

這個DefaultServlet的servlet-mapping是這樣配置的:

到這裡不禁有人會問,既然url-pattern 配置的是 / ,那不就應該可以響應所有的請求了嗎?其實上面的圖中已經給出了解釋,事實上這是匹配所有你沒有定義的Servlet-mapping的請求。之所以自己定義的Servlet可以優先生效,則是因為Tomcat內的Servlet的mapping配置是嚴格按照聲明順序初始化,並按此順序響應請求,一層層按此比對,有一個可以響應請求,就用其處理。有相關疑問可以參考一下博文:

http://k1121.iteye.com/blog/1564241

綜上所述,所有經過Tomcat的請求都是由一個servlet來處理的,如果一個請求沒有匹配到任何應用指定的Servlet,那麼就會流到Tomcat的默認的servlet來,這個Servlet名字叫做DefaultServlet,DefaultServlet是配置在/conf/web.xml裏面。

JspServlet介紹

上一節我們了解到Tomcat使用DefaultServlet處理所有的靜態資源。這一節我們來看一個jsp請求又是怎麼被響應的。同DefaultServlet類似,jsp的處理也不需要我們單獨配置,而是在/conf/web.xml中做為全局配置存在。其對應的處理類為JspServlet,用於處理所有的jsp請求。同樣我們打開/conf/web.xml,可以看到以下代碼與注釋:

通過看注釋我們便對該配置的作用一目了然,往下看我們會看到JspServlet的mapping配置,其url-pattern為*.jsp和*.jspx。所以它可以攔截所有的jsp請求並作出相應的反應。

到這裡我們便知道了為什麼瀏覽器在我們自己沒對jsp文件做任何配置的情況下依舊能訪問的原因。

JSP轉化為Servlet

在文章開頭我們知道當Tomcat啟動過後,一個xxx.jsp文件會在orgapachejsp目錄下生成相應的xxx_jsp.java文件與xxx_jsp.class文件,打開我們之前已經生成的test_jsp.java 文件,這個文件結構如下圖所示:

主要的轉換動作是在方法_japService()中實現的,如下的Servlet類的代碼截圖可以看出,其中插入了session、application等對象的初始化,這幾個對象都是通過頁面級別的對象pageContext獲取到的。

頁面中的java代碼去哪兒了呢,轉換過程中,HTML頁面元素內容可以理解為通過out.write()直接輸出給前端頁面,java代碼(<%%>包含的內容)直接去掉<%%>寫到類中執行。部分代碼截圖如下:

圖中紅色方框內的內容就是我們在jsp頁面中<% %>中的Java代碼。在轉化中直接去除<% %>後放到類代碼中,而其餘的可以理解為直接out.write()輸出給前端頁面。至此我們便解釋了Tomcat如何處理jsp文件的問題。

3 總結

本文對JSP的運行原理進行了詳細的分析,可以得出下面的流程圖:

圖3-1 jsp頁面工作原理圖

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

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

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

JSP和Servlet會有如下轉換:

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

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

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

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

where2go 團隊