欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JSP的运行内幕

程序员文章站 2023-12-16 09:15:46
    经常有朋友问起,jsp和servlet之间有什么区别,两者之间又有什么联系?其实servlet技术的出现时间很早,是当时为了java的...

    经常有朋友问起,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技术有更深的认识。

上一篇:

下一篇: