JSP的运行内幕

经常有朋友问起,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" %>
<html:html locale="true">
<head>
<title><bean:message key="index.title"/></title>
<html:base/>
</head>
<body bgcolor="white">
……
</body>
</html:html>

  我们仅以index.jsp中的<bean:message/>标签的解析为例进行分析,看容器是怎样把这个自定义标签解析成HTML输出的。上面代码省略了页面的其它显示部分。首先,查看上面浏览器中页面的源文件:

<html lang="zh">
<head>
<title>MailReader Demonstration Application (Struts 1.0)</title>
</head>
<body bgcolor="white">
……
</body>
</html>

  可见,容器已经把<bean:message key="index.title"/>替换成一个字串,显示为页面的标题。

  解析过程

  那么,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 {
 //为所有的定制标签定义处理器池类的引用
 private org.apache.jasper.runtime.TagHandlerPool ;
 _jspx_tagPool_bean_message_key;
 ……
 //页面类构造方法
 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;
  ……
 }
 //页面在处理退出时释放所有定制标签的属性
 public void _jspDestroy() {
  _jspx_tagPool_bean_message_key.release();
  ……
 }
}

  生成的index_jsp.java继承于org.apache. jasper.runtime.HttpJspBase。研究这个文件为我们了解定制标签的运行机理提供了途径。

  从上面可以看出,Tomcat在解析一个JSP页面时,首先为每一个定制标签定义并实例化了一个TagHandlerPool对象。页面的处理方法覆盖父类的_ jspService()方法,_jspService方法首先初始化环境,为内置对象赋值。由于index.jsp页面整体由一个<html:html/>标签包裹,Tomcat对每一个标签都产生一个私有方法加以实现。<html:html/>标签的处理方法是_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代码直接输出,下面主要看看<html:html></html:html>标签之间包含的<bean:message key="index.title"/>标签的解析。对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标签,并设置了一个属性:<bean:message key="index.title"/>。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技术有更深的认识。

(0)

相关推荐

  • JSP的运行内幕

    经常有朋友问起,JSP和Servlet之间有什么区别,两者之间又有什么联系?其实Servlet技术的出现时间很早,是当时为了Java的服务器端应用而开发的.大家都知道Applet是应用小程序,Servlet就是服务器端小程序了.但在Microsoft公司的ASP技术出现后,使用Servlet进行响应输出时一行行的输出语句就显得非常笨拙,对于复杂布局或者显示页面更是如此.JSP就是为了满足这种需求在Servlet技术之上开发的.可见,JSP和Servlet之间有着内在的血缘关系,在学习JSP时,如

  • 快速建立Servlet和JSP的运行、调试和编译环境

    相信很多刚开始学习Servlet和JSP的朋友都有一个困扰,就是如何快速方便的建立一个Servlet和JSP的运行.调试和编译环境.本人现在正在国外攻读MIT(Master of Information Technology),现在选修的这一科是WBIS(Web Based Information Systems),其包含了许多种技术,如Java,Perl,CGI,Servlet,ASP,Java Script,JSP,OO,UML等等.下面的这篇文章是学校所提供的相关软件的下载地址.安装方法以

  • JSP程序运行原理、文档结构及简单输入输出实例分析

    本文实例讲述了JSP程序运行原理.文档结构及简单输入输出.分享给大家供大家参考.具体如下: 目标: 掌握Web应用的文档结构: 掌握JSP的运行原理: 掌握JSP的简单输入和输出. 主要内容: 通过一个简单实例介绍Web应用的文档结构和运行原理: 通过一个简单的注册功能介绍基本的输入输出. 实现内容:客户端验证. 1. 文档结构 每个应用都有一个根目录,例如ch2:理论上可以放在任何地方,但是需要配置,简单的做法,直接放在了webapps这个目录下,在这个目录的应用会被自动加载. 在根目录下会有

  • jsp页面中表达式语言中的$符号不起作用的解决方法

    今天myeclipse里部署了之前做的一个测试项目,发现jsp里的$符号tomcat启动后页面上显示出来了,百度搜了下别人也有类似的问题出现过.经提醒原来是web.xml配置的version设置的是2.5而我tomcat5启动的.是tomcat的版本低于web的版本,从而导致$符号不能正常使用. 后将tomcat5改用tomcat6.jdk采用1.6 启动spring2.5项目.$显示问题解决. 以下是网上摘录的详细说明: 在jsp页面中用表达式语言中的$符号,如${pageScope.titl

  • Windows 2003 IIS 6.0 搭建可建虚拟机的asp+.net+php+jsp+mysql+mssql

    1.预安装好windows 2003 打好sp2以上补丁  安装好IIS6.0  并选择asp和.net支持.IIS默认支持asp.net 1.1 asp.net 2.0可以在网上下载安装.2.安装php.mysql及mssql.网上很多正确介绍.这里就不讲了.重点讲jsp的环境搭配.3.1.安装JAVA 2 SDK 我装在D:\JDK目录下.接下来设置环境变量,这一步很重要,别忘了:JAVA_HOME = D:\JDKCLASSPATH = D:\JDK\LIB\tools.jar;D:\JD

  • JSP中include指令和include行为的区别

    < %@ include file=" "%> < jsp:include page=" " flush="true"/> 前者是指令元素.后者是行为元素.具体它们将在何处用?如何用及它们有什么区别?这应该是很多人看到它都会想到的问题.下面一起来看看吧. 通常当应用程序中所有的页面的某些部分(例如标题.页脚和导航栏)都相同的时候,我们就可以考虑用include.具体在哪些时候用< %@ include file=&q

  • 通过JSP的预编译消除性能瓶颈

    欢迎来到"管理角"这个版,新一期的月刊专栏专注于 WebLogic 服务器的管理.配置.处理和开发方面. 开辟这个专栏的目的是为了向大家介绍在使用WebLogic Sever时,能普遍用到的非J2EE开发方面的问题.开发者和管理者同样会发现这个专栏非常有价值,因为这些文章既适用于开发又适用于最终产品的应用.此外,它很大程度上利用了来自于该领域和工程实验室的经验,它提供了对实际问题的详细解答. JSP预编译的必要性 本月的文章着眼于移除潜在的系统性能瓶颈,它通过解决一个最普通的问题――在

  • Windows下JSP开发环境的配置

    摘 要 要学习JSP开发,必须先搭建一个符合JSP规范的开发环境.本文以实用为原则,介绍了以Tomcat服务器为核心的3种配置JSP环境的方法. 关键字 JSP引擎,Web服务器,配置 Sun推出的JSP(Java Server Pages)是一种执行于服务器端的动态网页开发技术,它基于Java技术.执行JSP时需要在Web服务器上架设一个编译JSP网页的引擎.配置 JSP 环境可以有多种途径,但主要工作就是安装和配置Web服务器和JSP引擎. 下面就以Tomcat作为JSP引擎,配合Tomca

  • JSP/Servlet 中的汉字编码问题

    JSP/Servlet 中的汉字编码问题 网上就 JSP/Servlet 中 DBCS 字符编码问题有许多优秀的文章和讨论,本文对它们作一些整理,并结合 IBM WebSphere Application Server 3.5(WAS)的解决方法作一些说明,希望它不是多余的.内容: 问题的起源 ??????-80,GBK,GB18030-2000 汉字字符集及 Encoding 中文转码时'?'.乱码的由来 JSP/Servlet 汉字编码问题及在 WAS 中的解决办法 结束语 参考文章 1.

  • 浅谈jsp九大内置对象及四个作用域

    request           请求对象             类型 javax.servlet.ServletRequest        作用域 Request response          响应对象              类型 javax.servlet.SrvletResponse        作用域 Page pageContext       页面上下文对象        类型 javax.servlet.jsp.PageContext       作用域 Page

随机推荐