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程序片中的局部变量互不干扰。