JSP的运行内幕

转载 2005年03月01日 19:53:00
经常有朋友问起,JSP和Servlet之间有什么区别,两者之间又有什么联系?其实Servlet技术的出现时间很早,是当时为了Java的服务器端应用而开发的。大家都知道Applet是应用小程序,Servlet就是服务器端小程序了。但在Microsoft公司的ASP技术出现后,使用Servlet进行响应输出时一行行的输出语句就显得非常笨拙,对于复杂布局或者显示页面更是如此。JSP就是为了满足这种需求在Servlet技术之上开发的。可见,JSP和Servlet之间有着内在的血缘关系,在学习JSP时,如果能够抓住这种联系,就能更深刻地理解JSP的运行机理,达到事半功倍的效果。 本文将通过对一个JSP运行过程的剖析,深入JSP运行的内幕,并从全新的视角阐述一些JSP中的技术要点。 HelloWorld.jsp 我们以Tomcat 4.1.17服务器为例,来看看最简单的HelloWorld.jsp是怎么运行的。 代码清单1:HelloWorld.jsp HelloWorld.jsp <% String message = "Hello World!"; %> <%=message%>   这个文件非常简单,仅仅定义了一个String的变量,并且输出。把这个文件放到Tomcat的webapps/ROOT/目录下,启动Tomcat,在浏览器中访问http://localhost:8080/HelloWorld.jsp,浏览器中的输出为“HelloWorld!”   让我们来看看Tomcat都做了什么。转到Tomcat的/work/Standalone/localhost/_目录下,可以找到如下的HelloWorld_jsp.java,这个文件就是Tomcat解析HelloWorld.jsp时生成的源文件:   代码清单2:HelloWorld_jsp.java package org.apache.jsp; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.jsp.*; import org.apache.jasper.runtime.*; public class HelloWorld_jsp extends HttpJspBase { ...... public void _jspService(HttpServletRequest request, HttpServletResponse response)throws java.io.IOException, ServletException { JspFactory _jspxFactory = null; javax.servlet.jsp.PageContext pageContext = null; HttpSession session = null; ServletContext application = null; ServletConfig config = null; JspWriter out = null; Object page = this; JspWriter _jspx_out = null; try { _jspxFactory = JspFactory.getDefaultFactory(); response.setContentType("text/html;charset=ISO-8859-1"); pageContext = _jspxFactory.getPageContext(this, request, response,null, true, 8192, true); application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); _jspx_out = out; String message = "Hello World!"; out.print(message); } catch (Throwable t) { out = _jspx_out; if (out != null && out.getBufferSize() != 0) out.clearBuffer(); if (pageContext != null) pageContext.handlePageException(t); } finally { if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext); } } }   从上面可以看出,HelloWorld.jsp在运行时首先解析成一个Java类HelloWorld_jsp.java,该类继承于org.apache.jasper.runtime.HttpJspBase基类,HttpJspBase实现了HttpServlet接口。可见,JSP在运行前首先将编译为一个Servlet,这就是理解JSP技术的关键。   我们还知道JSP页面中内置了几个对象,如pageContext、application、config、page、session、out等,你可能会奇怪,为什么在JSP中的代码片断中可以直接使用这些内置对象。观察_jspService()方法,实际上这几个内置对象就是在这里定义的。在对JSP文件中的代码片断进行解析之前,先对这几个内置对象进行初始化。   首先,调用JspFactory的getDefaultFactory()方法获取容器实现(本文中指Tomcat 4.1.17)的一个JspFactory对象的引用。JspFactory是javax.servlet.jsp包中定义的一个抽象类,其中定义了两个静态方法set/getDefaultFactory()。set方法由JSP容器(Tomcat)实例化该页面Servlet(即HelloWorld_jsp类)的时候置入,所以可以直接调用JspFactory.getDefaultFactory()方法得到这个JSP工厂的实现类。Tomcat是调用org.apache.jasper.runtime.JspFactoryImpl类。   然后,调用这个JspFactoryImpl的getPageContext()方法,填充一个PageContext返回,并赋给内置变量pageConext。其它内置对象都经由该pageContext得到。具体过程见上面的代码,这里不再赘述。该页面Servlet的环境设置完毕,开始对页面进行解析。HelloWorld.jsp页面仅仅定义了一个String变量,然后直接输出。解析后的代码如下:   代码清单3:JSP页面解析后的代码片断 String message = "Hello World!"; out.print(message);   定制标签的解析过程   在一个中大型的Web应用中,通常使用JSP定制标签来封装页面显示逻辑。剖析容器对定制标签的解析过程,对我们深入理解定制标签的运行机理非常有帮助。下面我们以Struts1.1b中附带的struts-example应用的主页运行为例加以说明。   包含定制标签的index.jsp   Struts1.1b的下载地址是http://jakarta.apache.org/struts/index.html。将下载的包解压,在webapps目录下可以找到struts-example.war。将该War包拷贝到Tomcat的webapps目录下,Tomcat会自动安装此应用包。在浏览器中通过http://localhost:8080/struts-example访问struts-example应用,将显示应用的首页(见图1)。   图一 应用的首页   代码清单4:index.jsp <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %> <bean:message key="index.title"/> ……   我们仅以index.jsp中的标签的解析为例进行分析,看容器是怎样把这个自定义标签解析成HTML输出的。上面代码省略了页面的其它显示部分。首先,查看上面浏览器中页面的源文件: MailReader Demonstration Application (Struts 1.0) ……   可见,容器已经把替换成一个字串,显示为页面的标题。   解析过程   那么,JSP容器是怎样完成解析的呢?查看在工作目录jakarta-tomcat-4.1.17/work/Standalone/localhost/struts-example下解析后的index_jsp.java文件:   代码清单5:index_jsp.java package org.apache.jsp; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.jsp.*; import org.apache.jasper.runtime.*; public class index_jsp extends HttpJspBase { file://为所有的定制标签定义处理器池类的引用 private org.apache.jasper.runtime.TagHandlerPool ; _jspx_tagPool_bean_message_key; …… file://页面类构造方法 public index_jsp() { _jspx_tagPool_bean_message_key = new org.apache.jasper.runtime.TagHandlerPool(); …… } public void _jspService(HttpServletRequest request,   HttpServletResponse response)   throws java.io.IOException, ServletException { …… _jspxFactory = JspFactory.getDefaultFactory(); response.setContentType("text/html;charset=UTF-8"); pageContext = _jspxFactory.getPageContext(this,   request, response,null, true, 8192, true); application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); _jspx_out = out; …… if (_jspx_meth_html_html_0(pageContext)) return; …… } file://页面在处理退出时释放所有定制标签的属性 public void _jspDestroy() { _jspx_tagPool_bean_message_key.release(); …… } }   生成的index_jsp.java继承于org.apache. jasper.runtime.HttpJspBase。研究这个文件为我们了解定制标签的运行机理提供了途径。   从上面可以看出,Tomcat在解析一个JSP页面时,首先为每一个定制标签定义并实例化了一个TagHandlerPool对象。页面的处理方法覆盖父类的_ jspService()方法,_jspService方法首先初始化环境,为内置对象赋值。由于index.jsp页面整体由一个标签包裹,Tomcat对每一个标签都产生一个私有方法加以实现。标签的处理方法是_jspx_meth_html_html_0()。这个方法的命名规范大家也可以从这里看出,就是“_jspx_meth + 标签的前缀 + 标签名 + 该标签在JSP页面同类标签中出现的序号”。其它标签都被包含在该标签中,所以其它标签在_jspx_meth_html_html_0()方法中进行解析。具体的代码实现请参见赛迪网http://linux.ccidnet.com期刊浏览2003年第6期。   在_jspx_meth_html_html_0()方法中,首先从_jspx_tagPool_html_html_locale池中得到一个org.apache.struts.taglib.html.HtmlTag的实例,然后设置这个tag实例的页面上下文及上级标签,由于html:html标签是页面的最顶层标签,所以它的parent是null。然后对该标签的内容进行解析。HTML代码直接输出,下面主要看看标签之间包含的标签的解析。对bean:message标签的解析类似于html:html,Tomcat也将其放入一个单独的方法_jspx_meth_bean_message_0()中进行。   bean:message标签的解析   代码清单7:_jspx_meth_bean_message_0()方法片断 //对message定制标签的处理方法 private boolean _jspx_meth_bean_message_0( javax.servlet.jsp.tagext.Tag _jspx_th_html_html_0, javax.servlet.jsp.PageContext pageContext) throws Throwable { JspWriter out = pageContext.getOut(); /* ---- bean:message ---- */ org.apache.struts.taglib.bean.MessageTag _jspx_th_bean_message_0 = (org.apache.struts.taglib.bean.MessageTag) _jspx_tagPool_bean_message_key.get( org.apache.struts.taglib.bean.MessageTag.class); _jspx_th_bean_message_0.setPageContext(pageContext); _jspx_th_bean_message_0.setParent(_jspx_th_html_html_0); _jspx_th_bean_message_0.setKey("index.title"); int _jspx_eval_bean_message_0 = _jspx_th_bean_message_0.doStartTag(); if (_jspx_th_bean_message_0.doEndTag()== javax.servlet.jsp.tagext.Tag.SKIP_PAGE) return true; _jspx_tagPool_bean_message_key.reuse(_jspx_th_bean_message_0); return false; }   同样,对html:bean也需要从池中得到一个标签类的实例,然后设置环境。这里不再赘述。我们只专注对MessageTag定制标签类特殊的处理部分。定制标签类的开发不在本文讨论范围之内。在index.jsp中定义了一个bean:message标签,并设置了一个属性:。Tomcat在解析时,调用MessageTag对象的key属性设置方法setKey(),将该属性置入。然后调用MessageTag的doStartTag()和doEndTag()方法,完成解析。如果doEndTag()方法的返回值为javax.servlet.jsp.tagext.Tag. SKIP_PAGE,表明已经完成解析,返回true,Tomcat将立即停止剩余页面代码的执行,并返回。否则把该MessageTag的实例放回池中。   标签类对象实例的池化   为了提高运行效率,Tomcat对所有的定制标签类进行了池化,池化工作由org.apache.jasper. runtime.TagHandlerPool类完成。TagHandlerPool类主要有两个方法,代码如下:   代码清单8:TagHandlerPool.java public class TagHandlerPool { private static final int MAX_POOL_SIZE = 5; private Tag[] handlers; public synchronized Tag get(Class handlerClass) throws JspException {……} public synchronized void reuse(Tag handler) {……} }   TagHandlerPool简单地实现了对标签类的池化,其中MAX_POOL_SIZE是池的初始大小,handlers是一个Tag的数组,存储标签类的实例。get(Class handlerClass)得到一个指定标签类的实例,如果池中没有可用实例,则新实例化一个。reuse(Tag handler)把handler对象放回池中。   至此,我们对JSP在容器中的运行过程已经了然于胸了。虽然每种JSP容器的解析结果会有差异,但其中的原理都雷同。对于编写JSP应用,我们并不需要干涉容器中的运行过程,但如果你对整个底层的运行机制比较熟悉,就能对JSP/Servlet技术有更深的认识

相关文章推荐

JSP运行内幕

  • 2007年04月20日 16:50
  • 11KB
  • 下载

深入体验JavaWeb开发内幕——pageContext对象与JSP中标签的简介和相关应用

前面我们提到了JSP中的其他八个对象及其简单的应用,但是pageContext对象是JSP技术中最重要的一个对象,它代表JSP页面的运行环境,这个对象不仅封装了对其它8大隐式对象的引用,它自身还是一个...

深入体验JavaWeb开发内幕——简述JSP中的自定义标签叫你快速学会

自定义标签,顾名思义,就是自己定义的标签。那么我们为什么要自己定义一些标签呢? 我们知道,如果要在JSP中获取数据我们可以采用通过JSP的隐式对象request来获取相应的请求参数或者属性,也可以通...

深入体验JavaWeb开发内幕——pageContext对象与JSP中标签的简介和相关应用

pageContext对象是JSP技术中最重要的一个对象,它代表JSP页面的运行环境,这个对象不仅封装了对其它8大隐式对象的引用,它自身还是一个域对象,可以用来保存数据。并且,这个对象还封装了web开...

TaskScheduler内幕天机:Spark shell案例,TaskScheduler和SchedulerBackend、FIFO与FAIR、Task运行时本地性算法详解

TaskSchedulerBackend与SchedulerBackend FIFO与FAIR两种调度模式 Task数据本地性资源的分配 一、TaskScheduler运行过程(Spark-shell...

大数据IMF传奇行动绝密课程第35课:打通Spark系统运行内幕机制流程循环图

打通Spark系统运行内幕机制流程循环图 一、TaskScheduler原理解密 1、DAGScheduler在提交TaskSet给底层调度器的时候是面向接口TaskScheduler的,这符合面...

Spark系统运行内幕机制循环流程

一、TaskScheduler原理解密 1.DAGScheduler在提交TaskSet给底层调度器的时候是面向接口TaskScheduler的,这符合面向对象中依赖抽象而不依赖的原则,带来底层资源...

第三步:kafka的server启动过程 源代码运行内幕机制

1、 kafka.kafka ->main ->kafkaServerStartable.startup 2、进入 kafkaServerStartable.scala -〉def startup()...

《Hadoop技术内幕》读书笔记——Task运行过程分析

本文是董西成的Hadoop技术内幕一书的读书章节总结。   第八章 Task运行过程分析   所有Task需要周期性地向TaskTracker汇报最新进度和计数器值,而这正是由Reporter...
  • clamaa
  • clamaa
  • 2017年04月11日 09:54
  • 176
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:JSP的运行内幕
举报原因:
原因补充:

(最多只允许输入30个字)