JSP執行過程詳解
復習JSP的概念
JSP是Java Server Page的縮寫,在傳統的HTML頁面中加入JSP標簽和java的程序片段就構成了JSP。
JSP的基本語法:兩種注釋類型、三個腳本元素、三個元素指令、八個動作指令。
JSP的內置對象常用的有:Request、Response、Out、Session、cookie、Application等。
JSP中的局部變量和全局變量
在JSP基本語法博文中有個小例子counter.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head></head> <body> <%!int count = 0; synchronized void setCount() { count++; }; %> <h2> 歡迎閱讀本文 <h2> <br> 本文閱讀次數: <% setCount(); out.println(count); %> <br> i=0,計算i++= <% int i = 0; out.println(i++); %> </body> </html>
這個例子的目的是為了區分在<%! %>和<%%>中定義的變量:
<%! %>內的變量和方法是一個類內的變量和方法也就是頁面的成員變量和成員方法,每當一個用戶訪問此頁面,count會加一。
<% %>內的變量是一個方法的變量也就是局部變量,無論訪問頁面多少次,i++的值總是0。
我們看到兩種變量的不同,但是并沒理解為什么會造成這樣的不同。下面就詳細講解。
JSP執行過程圖解
第一次請求:
當服務器上的一個JSP頁面被第一次請求執行時,服務器上的JSP引擎首先將JSP頁面文件轉譯成一個.java文件,也就是servlet,并編譯這個java文件生成.class的字節碼文件,然后執行字節碼文件響應客戶端的請求。
再次請求:
JSP引擎將直接執行字節碼文件來響應客戶。
由JSP轉譯的servlet
我們可以查看由JSP轉譯成的servelt,由此來加深多JSP的理解。存放JSP轉譯成的servlt的目錄如下:
apache-tomcat-7.0.59\work\Catalina\localhost\yourwebapp\org\apache\jsp
這些servlet的名字都是以_jsp.java結尾。下面是counter_jsp.java的具體內容:
/* * Generated by the Jasper component of Apache Tomcat * Version: Apache Tomcat/7.0.59 * Generated at: 2015-08-07 05:13:13 UTC * Note: The last modified time of this file was set to * the last modified time of the source file after * generation to assist with modification tracking. */ package org.apache.jsp; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.jsp.*; import java.util.*; public final class counter_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent { int count = 0; synchronized void setCount() { count++; }; private static final javax.servlet.jsp.JspFactory _jspxFactory = javax.servlet.jsp.JspFactory.getDefaultFactory(); private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants; private javax.el.ExpressionFactory _el_expressionfactory; private org.apache.tomcat.InstanceManager _jsp_instancemanager; public java.util.Map<java.lang.String,java.lang.Long> getDependants() { return _jspx_dependants; } public void _jspInit() { _el_expressionfactory = _jspxFactory.getJspApplicationContext (getServletConfig().getServletContext()).getExpressionFactory(); _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory .getInstanceManager(getServletConfig()); } public void _jspDestroy() { } public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException { final javax.servlet.jsp.PageContext pageContext; javax.servlet.http.HttpSession session = null; final javax.servlet.ServletContext application; final javax.servlet.ServletConfig config; javax.servlet.jsp.JspWriter out = null; final java.lang.Object page = this; javax.servlet.jsp.JspWriter _jspx_out = null; javax.servlet.jsp.PageContext _jspx_page_context = null; try { response.setContentType("text/html;charset=UTF-8"); 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("\r\n"); out.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n"); out.write("<html>\r\n"); out.write("<head></head>\r\n"); out.write("<body>\r\n"); out.write("\t"); out.write("\r\n"); out.write("\t<h2>\r\n"); out.write("\t\t歡迎閱讀本文\r\n"); out.write("\t\t<h2>\r\n"); out.write("\t\t\t<br> 本文閱讀次數:\r\n"); out.write("\t\t\t"); setCount(); out.println(count); out.write("\r\n"); out.write("\t\t\t<br>\r\n"); out.write("\t\t\ti=0,計算i++=\r\n"); out.write("\t\t\t"); int i = 0; out.println(i++); out.write("\r\n"); out.write("\t\t\r\n"); out.write("</body>\r\n"); out.write("</html>\r\n"); } catch (java.lang.Throwable t) { if (!(t instanceof javax.servlet.jsp.SkipPageException)){ out = _jspx_out; if (out != null && out.getBufferSize() != 0) try { if (response.isCommitted()) { out.flush(); } else { out.clearBuffer(); } } catch (java.io.IOException e) {} if (_jspx_page_context != null) _jspx_page_context.handlePageException(t); else throw new ServletException(t); } } finally { _jspxFactory.releasePageContext(_jspx_page_context); } } }
也許你回疑問,counter_jsp.java并沒有繼承HttpServlet,為什么稱它們為servlet?請注意下面
public final class counter_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent
在apache-tomcat-7.0.59\java\org\apache\jasper\runtime目錄下存HttpJspBase這個類的源代碼文件,
public abstract class HttpJspBase extends HttpServlet implements HttpJspPage{...}
因為下面的關系,所以這些_jsp.java文件都是servlet。
HttpServlet 繼承者 HttpJspBase 繼承者 counter_jsp
回到counter_jsp.java中,我們可以應用記事本的查找功能,找到我們在JSP頁面定義的變量。可以看到:
<%! %>中定義的變量和方法是類的成員變量和成員方法,是全局變量。
<% %>中定義的變量是_jspService(){}方法中的局部變量。
JSP的內部方法
_jspInit(){}:jsp Page被初始化的時候調用該方法,并且該方法僅在初始化時執行一次,所以可以在這里進行一些初始化的參數配置等一次性工作,由作者創建
_jspDestroy(){}:jsp Page由于某種原因被關閉的時候調用該方法,由作者創建
_jspService(){}:由jsp容器自動創建的處理jsp Page的方法,由jsp容器創建,不能由作者定義。
當jsp文件第一次被處理時,他會被轉化成一個servlet文件。然后再創建一個 Servlet對象,首先執行_jspInit()方法進行初始化操作,由于整個執行過程_jspInit()方法只執行一次,所以可以在這個方法中進行 一些必要的操作比如連接數據庫,初始化部分參數等等,接著執行_jspService()方法,對客戶端的請求進行處理,對每一個請求會創建一個線程,如 果同時有多個請求需要處理的話就會創建多個線程。由于servlet長期貯存與內存中,所以執行速度快,但是由于初始化需要編譯,所以第一次執行還是比較 慢的,如果由于某種原因導致jsp網頁關閉或者銷毀的話會執行jspDestroy()方法。
JSP的多線程思考
當多個用戶請求一個JSP頁面時,Tomcat服務器為每個客戶啟動一個線程,該線程負責執行常住內存的字節碼文件來響應客戶的請求。這些線程有Tomcat服務器來管理。
這些線程共享JSP頁面的成員變量(實例變量),因此任何一個用戶對JSP頁面成員變量的操作,都會影響到其他用戶,這可能導致線程的不安全。 為了保證線程安全,我們不要使用(實例變量+類變量),就這么簡單。也可以使用synchronized同步方法,但是這樣效率不高。
方法中的局部變量是不會影響線程安全的,因為他們是在棧上分配空間,而且每個線程都有自己私有的棧空間,運行在不同線程中的java程序片中的局部變量互不干擾。