JSP簡介
JSP的核心實質是Servlet技術。
JSP是后來添加的基於Servlet的一種擴展技術。但二者在使用上有不同的方向。
由於Servlet實質是一個Java類,因此非常適合用來處理業務邏輯。而如果Servlet要展示網頁內容,就必須通過輸出流對象將view層的代碼通過字符串的形式輸出,非常麻煩,且不易閱讀和維護。另一方面,
在JSP中可以直接編寫視圖層的代碼如HTML,因此JSP的它主要用來展示網頁內容。但是由於JSP實質是Servlet,因此JSP也是一種動態網頁技術。
而我們實際開發時,JSP只會用來展示網頁視圖內容,用Servlet來處理業務邏輯。因為 JSTL標簽庫 以及第三方框架提供的標簽足夠強大,我們甚至可以自定義標簽 ,因此根本沒有理由在JSP中寫Java代碼。
JSP的視圖代碼可以是任何文本內容,如 HTML / XHTML ,XML , JSON , 甚至是txt。通常我們叫這些直接寫在JSP中的文本叫做 模板文本數據。
JSP, Servlet 和 JSP引擎
我們常說,Tomcat是一個Servlet容器,而不說它是JSP容器,因為JSP實質是被轉換為Servlet后再工作的。
那我們為什么不直接使用Servlet呢?因為在Servlet中寫視圖層代碼(HTML)非常狗血。但是Tomcat又只“認識”Servlet,因此就需要JSP引擎做一個轉換工作。
舉個的例子,一切程序都是計算機可執行的機器代碼,而直接編寫機器代碼是非常困難的,於是我們可以用C語言,用C寫代碼更加直觀和易於閱讀理解,C編譯器會將C代碼轉換成對應的機器代碼。這個例子中,Servlet就好比是機器代碼,JSP好比是C語言,而JSP引擎 就好比是C編譯器。
因此:如果客戶端請求的是一個JSP,則該JSP文件傳遞給JSP引擎,JSP引擎將JSP文件轉譯為Servlet的java文件,其實質就是這個Servlet來處理客戶端的請求。

JSP轉換為Servlet的細節
JSP按如下規則轉換為Servlet:
1、所有的 非 JSP 文本 ( 如HTML代碼,XML代碼),都將在生成的_jspService方法中以字符串的形式使用out對象輸出。
2、所有的<% %> 和 <%= %>腳本,將在他所在的地方原原本本對應插入到_jspService方法中去。所有的<%! %>都將成為Servlet的類級別的成員。
因此<%! %>寫在JSP頁面代碼的任何地方都沒有任何區別。 <%-- --%> JSP注釋 將只保留在JSP代碼中,不會存在轉換后的servlet代碼中
3、EL,JSTL等被JSP引擎使用特殊轉換。

public void _jspService() { //..... try { response.setContentType("text/html; charset=UTF-8"); pageContext = _jspxFactory.getPageContext( this, request, response, null, /*page指令配置的error page 的URL*/ true, /*page質量配置的是否使用session*/ 8192, /*page指令配置的out對象的緩存大小(kb)*/ true); /*page指令配置的out對象是否autoFlush*/ //..... }
JSP轉譯后的Servlet的繼承結構
JSP轉譯后的java文件在tomcat home下的work目錄下找到。
public interface Servlet { public void init(ServletConfig config) throws ServletException; public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; public void destroy(); public String getServletInfo(); public ServletConfig getServletConfig(); } public interface JspPage extends Servlet { public void jspInit(); public void jspDestroy(); } public interface HttpJspPage extends JspPage { public void _jspService(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException; }
public abstract class HttpJspBase extends HttpServlet implements HttpJspPage { private static final long serialVersionUID = 1L; protected HttpJspBase() { } @Override public final void init(ServletConfig config) throws ServletException { super.init(config); jspInit(); _jspInit(); } @Override public String getServletInfo() { return Localizer.getMessage("jsp.engine.info"); } @Override public final void destroy() { jspDestroy(); _jspDestroy(); } /** * Entry point into service. */ @Override public final void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { _jspService(request, response); } @Override public void jspInit() { } public void _jspInit() { } @Override public void jspDestroy() { } protected void _jspDestroy() { } @Override public abstract void _jspService(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException; }
可以看出,轉譯后的Servlet要實現上面的3個接口中的共 8 個 接口方法。然而,由於HttpJspBase 繼承了HttpServlet,因此,7個方法已經全部間接實現了(只有_jspService方法沒實現 )。
由於JSP的特殊性,HttpJspBase 還是 重寫了來自HttpServlet中的3方法,這3個方法正是一個Servlet的標准生命周期方法。而這3個方法內部又委托了 jspInit() 、 jspDestroy() 、 _jspService() 方法去實現。
因此:
1、可以認為,
一個JSP的生命周期方法分別是通過 jspInit() 、 jspDestroy() 、 _jspService() 來實現的。如果某個JSP要做初始化和清理工作,則可以重寫jspInit() 和 jspDestroy()方法實現。
2、JSP轉譯后的Servlet必須實現_jspService方法,作為處理響應的邏輯方法。這點我們不用關心,JSP轉譯后自動根據你寫的JSP代碼實現。
3、 因為HttpJspBase重寫了 HttpServlet中的service方法,覆蓋了根據請求發生選擇不同處理方法doXXX的派發邏輯,對一個JSP使用任何HTTP請求方法都會調用_jspService方法處理。

JSP的生命周期
要理解JSP的生命周期就必須理解Servlet的生命周期,因為JSP的生命周期相比於Servlet只多了最開始的一步:轉譯工作。且這個工作只做一次(在JSP文件不改變的情況下)。
在JSP被請求時,容器會先檢查這個JSP是否被修改過,如果修改過,則重新轉譯,然后編譯,其后的過程和Servlet生命周期一致。 如果沒有,則直接調用內存中駐留的實例的方法。
1、如果JSP文件是新的,則轉譯為Servlet java文件,然后編譯為class文件。加載類到內存,創建一個(僅僅一個)Servlet對象。並執行jspInit,表示這個servlet被啟用。
如果不是,則直接調用駐留在內存上的實例的_jspService 方法。
2、通過_jspService方法處理請求。多個請求同時請求同一個JSP的servlet實例,則這些請求會使用獨立的線程去調用_jspService 方法。
3、當此Servlet實例不再被使用、或者服務器關機時,調用jspDestroy,GC
