内容


评论专栏

Scott Johnson:JavaServer Pages 新手入门

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 评论专栏

敬请期待该系列的后续内容。

此内容是该系列的一部分:评论专栏

敬请期待该系列的后续内容。

摘自 IBM WebSphere 开发者技术期刊

历史回顾

JavaServer Pages 上个世纪就已推出,但我认为大家对其的了解仍然不够。作为 WebSphere® Application Server 的 JSP 容器的负责人,我非常关心是否有唾手可得的针对新手的信息,用以说明 JSP 容器如何工作。但我没有发现任何足够详细的东西,同时我也认为并非所有人都了解 JSP 技术。我撰写本文的目的就是为了填补这个空白:作为说明两个主要 JSP 处理阶段(转换请求阶段)实践细节的初级读本,我希望这其中的信息对您有用,另外还希望您能从中得到一些意外的惊喜。本文甚至还可能会提高您的 JSP 的质量!

示例代码

本文最后提供了一个示例 JSP 文件 codegen.jsp,在本文中我将多次引用其中的内容。该 JSP 说明了两种在 Nameclass 类中设置 name 属性的不同方法。JSP 包含了各种 JSP 语法元素,这使得我们的讨论非常有意思。我对在本文中讨论的代码行进行了突出显示(以粗体显示)。如果您希望自己运行该 JSP,请在浏览器中使用与以下所示类似的 URL 调用 codegen.jsp(请求参数 nameParam 和 tableColor 是可选的):

http://localhost:9080/yourapp/codegen.jsp?nameParam=Your Name&tableColor=red

如果使用此 URL 调用 codegen.jsp,浏览器将显示下图所示的内容:

图 1. 调用 codegen.jsp
图 1. 调用 codegen.jsp
图 1. 调用 codegen.jsp

转换阶段

那么,什么是 JSP 页?

在转换阶段,JSP 容器将对 JSP 页进行处理。JSP 页是一种文本文档。它可以完全由单个文本文件组成。它也可以包括一个主文本文件(“顶级”JSP 文件)和其他通过 JSP include 指令静态包括的文本文件。JSP include 指令是与以下内容类似的 JSP 语法元素:

<%@ include file="relativeURL" %>

在本文最后的 codegen.jsp 代码示例中提供了一个示例 <%@ include file="nameclass.jspf" %>。“静态地包括”文件,指所包括的文件的实际内容(JSP include 指令中的“relativeURL”)准确放置在 JSP 页中 include 指令所在的位置,而 include 指令本身将被丢弃。从某种意义而言,JSP 页是内存中的构造,因为除非使用能识别 JSP 的特殊开发工具,能在相应的位置看到所包括的内容,否则在 JSP 中包含静态包括进来的文件时,就永远无法实际看到 JSP 页的整个内容。

JSP 文件中根本不需要任何 JSP 语法内容。它并不必包括特定于 JSP 文件的任何内容。有效的 JSP 文件甚至可以仅仅包括“Hi there!”。之所以它是 JSP 文件,是因为要将其交付给 JSP 容器进行处理。在应用服务器中,对任何带 jsp 或 jspx 扩展名的文件的请求都将发送到 JSP 容器进行处理,而不管其中的内容如何。不过,文件中的内容也很重要!并非所有采用 JSP 扩展名的文件都是有效的 JSP 文件。

在转换阶段会发生什么?

在转换期间,必须将 JSP 页(也称为转换单元)从文本转换为可执行形式,以便能在应用服务器内运行。JSP 页的可执行形式是实现 javax.servlet.Servlet 接口的 Java 类。这意味着会将 JSP 页转换为传统 Servlet(更具体一些,HttpServlet),以在应用服务器的 Web 应用程序内运行。在 JSP 术语中,此 Servlet 称为 JSP 页实现类。WebSphere Application Server 的 JSP 容器通过三个步骤执行此转换过程:

  1. 验证
  2. Java 源代码生成
  3. Java 源代码编译

JavaServer Pages 规范将此称为 JSP 处理的转换阶段

1. 验证

JSP 容器确保 JSP 页的语法正确,当遇到错误时会生成提供相关信息的错误消息。为了验证 JSP 页语法是否正确,JSP 容器将逐字符读取页内容,对页的内容进行分析。JSP 容器将寻找其能识别的字符序列,此类序列指示其中存在需要进行处理的语法内容。

a. include 指令

例如,我在前面给出的 JSP include 指令以 <%@ 作为开头。这三个字符形成了 JSP 指令语法的开始序列。(JSP 总共定义了六个指令,而仅三个指令对 JSP 标记文件有效。)

以 codegen.jsp 中的 include 指令为例:

<%@ include file="nameclass.jspf"%>

JSP 容器将识别 <%@,并将随后查找有效的 JSP 指令名,如“include”或“page”。在我们的示例中将找到“include”,JSP 容器知道下一个字符序列一定为“file=”,因为 include 指令只有一个有效属性。如果该指令最后以 %> 正确结束,且“file=”的值为 JSP 容器能够找到并读取的文件名(在我们的示例中为 nameclass.jspf),则会随后将该文件的内容包括在 JSP 页的相应位置中,其方式如我前面所述。对所包括的文本也会进行验证。必须对其进行验证,因为它现在是 JSP 转换单元的一部分,必须符合 JSP 语法规则。

如果静态包括的文件也通过 include 指令静态包括其他文件,则也会将其包括在相应的位置中。对于嵌套 include 的数量并没有实际限制(不过,如果嵌套过深,会非常容易混淆,难以理清关系)。唯一的限制是,不允许循环 include;即不能 JSP 文件 A 静态地包括 JSP 文件 B,然后 B 又静态地包括 A。JSP 容器会识别这种情况,如果出现,会挂起验证过程,并给出一条错误消息。

在 WebSphere Application Server 中,所有这些静态 include 都实际在验证真正开始前执行——以便在内存中具有完整的 JSP 页/转换单元,从而在确定最终的 JSP 页中包含了创建者希望包含的每个文件的情况下进行验证。

b. 自定义标记验证(使用属性中的表达式语言表达式)

任何人都可以编写 JSP 自定义标记,然后以标记库的形式将其提供给 JSP 创建者使用。标记库是自定义标记实现的集合。JavaServer 标准标记库(JavaServer Pages Standard Tag Library,JSTL) out 标记之类的自定义标记将具有一些必选属性和一些可选属性。以下是 codegen.jsp 中的 JSTL out 标记示例用法:

<c:out value='Just set the name from request!
[${nameBean.name}]' default="null"/>

JSTL out 标记只具有一个必选属性,即 value 属性。如果 value 属性及其值未出现,则 JSP 容器将生成错误。在此示例中,value 属性是一些文本的组合内容:

Just set the name from request! []

在文本中嵌入了一个表达式语言(Expression Language,EL)表达式:

${nameBean.name}

EL 表达式以“${”开头,以“}”结束。在我们的示例中,JSP 容器必须验证表达式 ${nameBean.name} 的语法。在这种情况下,该表达式语法有效。不过请注意,如果创建者犯了错误(例如 nameBean 上没有 name 属性),表达式可能会无法成功执行。我们到目前为止所进行的全部工作都是对表达式的语法进行验证,而不是验证逻辑。

在缺省情况下,我们遇到的其他属性会被视为有效属性,但这些属性是可选的,其值可以为字符串常量(在我们的示例中就是如此)。因此这个自定义标记的示例用法通过了验证。我们将稍后看到在实现 Servlet 中生成的 Java 代码的情况。

c. 标记库描述符中的代码片段

JSP 容器如何知道自定义标记的哪些属性是必选的哪些是可选的?它通过读取标记的元数据来确定需要知道自定义标记的哪些信息。元数据位于标记库描述符(Tag Library Descriptor,TLD)文件中,自定义标记实现的创建者必须提供此文件。(上面的示例是 out 标记在 JSP 页中的用法,另外存在实际进行此工作的 out 标记的实现。)

以下说明 JSP 容器要读取和分析什么内容来知道哪些属性在 out 标记中有效。出于篇幅考虑,我省略了一些内容,并将相关的部分用粗体加以突出显示。

清单 1
<tag>
  <description>
     Like <%= ... >, but for expressions.
  </description>
  <name>out</name><tag-class>org.apache.taglibs.standard.tag.rt.core.OutTag</tag-class>This is the  class for the out tag.	
  <body-content>JSP</body-content>
  <attribute>
     <description>
        Expression to be evaluated.
     </description>
     <name>value</name>The  attribute is required...<required>true</required>
     <rtexprvalue>true</rtexprvalue>
		
  </attribute>
  <attribute>
     <description>
        Default value if the resulting value is null.
     </description>
     <name>default</name>The  attribute is not required.<required>false</required>
     <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
     <name>escapeXml</name>
		The  attribute is not required.
     <required>false</required>
     <rtexprvalue>true</rtexprvalue>
  </attribute>
</tag>

d. jsp:useBean 验证

另一个例子是对强大的 jsp:useBean 标准操作的验证。验证 jsp:useBean 标记时,如以下标记:

<jsp:useBean id="nameBean"
class="codegen.Nameclass" scope="request" />

JSP 容器必须验证很多东西。每个属性具有自己的验证规则,另外还有关于如何处理属性组合的规则。与自定义标记不同,jsp:useBean 之类的 JSP 标准操作并不具有类似 TLD 的元数据文件。相反,关于哪个属性是必选属性的规则(可以为运行时表达式等等)要由容器的创建者写入到 JSP 容器的验证代码中(当然,创建者需要已经阅读并全面了解 JSP 规范关于 jsp:useBean 操作规则的六页详细说明)。

在这个简单的示例中,JSP 容器验证所需的 ID 属性的值“nameBean”在转换单元中是唯一的。例如,如果此 JSP 页中的另一个 jsp:useBean 标记也使用“nameBean”作为 ID,则 JSP 容器将生成错误。jsp:useBean 操作要求必须给出 class 或 type 属性,本例中给出了 class。最后,容器将验证可选的 scope 属性的值是允许的四个值之一:“page”、“request”、“session”或“application”。这个 useBean 标记通过了验证,我们将稍后讨论生成的代码。

e. 模板文本

JSP 页的验证期间,如果 JSP 容器并不能识别文本序列,则不会尝试对其进行处理,而是准确地按照其在 JSP 页中出现的样子直接将其放入 JSP 页实现类中。

例如,仅包含“Hi there!”的简单 JSP 文件并不具有 JSP 容器需要进行处理的任何文本序列。这样的序列称为模板文本或模板数据。JSP 规范按以下方式对其进行处理:

JSP 页具有元素和模板数据。元素是 JSP 容器已知的元素类型的实例。其他的都是模板数据,即所有 JSP 转换器不知道的内容。

HTML 是模板文本;JSP 技术并不知道如何解析或验证 HTML 标记(如 codegen.jsp 中的 <html><head><title>)。这意味着可以将 HTML 嵌入到 JSP 页,会在页执行时直接将此内容发送到浏览器。(事实上,这是在二十世纪九十年代发明 JSP 技术时的初衷之一。)另外,还可以将 HTML 与表达式组合,从而得到动态 HTML。以 codegen.jsp 的代码为例:

<table bgcolor=${tableColorVar} border="0" width="25%">

JSP 容器解析此文本时,会将 HTML 文本 <table bgcolor= 直接发送到实现 Servlet。但它会将 ${tableColorVar} 识别为 EL 表达式,并验证其语法是否正确(正确)。正如我们将要在 Java 源代码生成主题中看到的,容器将为 EL 表达式生成特殊的 Java 代码。不会对此行剩下的内容 border="0" width="25%"> 进行验证,将按原样置于实现 Servlet 中。

f. 脚本元素

JSP 中的脚本元素包括声明、scriptlet 和表达式。(JSP 脚本元素与 JSP EL 表达式差别很大。)在脚本元素内放置的是 Java 代码。声明语法为 <%!... %>,scriptlet 语法为 <% ...%>,表达式语法为 <%= ... %>。

JSP 容器并不验证这三个元素内的内容。JSP 容器并不知道如何解析 Java 代码。无论在声明、scriptlet 或表达式中是什么内容,都必须为有效的 Java,否则就无法成功编译实现类中的 Java 代码。从这个方面来说,脚本元素与模板文本非常相似,因为 JSP 容器并不会对其中的内容进行验证,而会直接将这些内容传递到 JSP 页实现类中。我在脚本元素的代码生成主题中对此进行了说明。

g. 二进制数据试验

前面我说过,JSP 页是可阅读的文本文档。如果在文本文件编辑器查看二进制文件,会看到类似于 PyY y? üüy÷÷ùyì 之类的字符。此数据可能在图形交换格式(Graphics Interchange Format,GIF)文件中有意义,但在 JSP 文件中就没有意义了。不过,作为试验,请尝试将 GIF 文件更名为带 .jsp 扩展名的文件,在 WebSphere Application Server 中请求该文件,看看会发生什么情况。尽管 GIF 是二进制文件类型,GIF 图像仍然很可能在浏览器中正常显示。这可能有效的原因是,如果 JSP 容器在转换阶段未找到任何可识别的字符序列,会直接将所有数据按原样包含在其创建的 JSP Servlet 中。Servlet 将执行并将输出数据,而数据是 GIF 文件的原始内容。浏览器将接受响应数据,并将其重新组织为 GIF,然后显示图像。不过,如果文件碰巧包含 JSP 容器在转换期间识别的字符序列(例如 <%@),则会尝试处理后续字符,而这些后续字符很可能在 JSP 语法中无效。JSP 容器将引发转换异常,甚至都不会继续创建页实现 Servlet。

h. 验证完成时

当 JSP 容器确定 JSP 页的语法有效时,会进入下一个处理阶段:代码生成。容器在验证阶段会实际收集大量的信息,现在将使用这些信息来恰当地生成 Java 代码。

2. Java 源代码生成

如果 JSP 页有效,JSP 容器会将 JSP 页中的文本转换为 Java 源代码,而此源代码将成为 JSP 页实现类。在本文最后提供了实际 .java 代码一个非常详细的 _codegen.java,此文件是由 WebSphere Application Server 为 codegen.jsp 示例所生成的。在此代码中,将看到 out.write() 和 out.print() 的很多实例。这些 Java 方法是向浏览器发送响应数据供显示的方法。

a. 自定义标记属性中的 EL 表达式的代码生成

以下面的代码为例,这是我们在前面了解自定义标记验证如何工作时所涉及的内容。现在我们将讨论 JSP 容器生成的 Java 代码。

<c:out value='Just set the name from request!
[${nameBean.name}]' default="null"/>

前面我说过,此示例说明了 JSTL out 标记的用法,存在实际进行此工作的标记的实现。JSP 容器生成的 Java 代码必须能够在运行时调用该实现类。JSP 容器需要发现实现 out 标记的类的名称。它在标记库描述符中查找此信息。out 标记的实现类的名称位于以下位置:

<tag-class>org.apache.taglibs.standard.tag.rt.core.OutTag</tag-class>
this is the implementation class for the out tag

获得此信息后,容器将生成以下 Java 代码,此代码位于 _codegen.java 中的方法 _jspx_meth_c_out_1() 内。

清单 2
Line 1  private boolean _jspx_meth_c_out_1(JspTag _jspx_th_c_when_0, 
          PageContext pageContext) throws Throwable {
Line 2  org.apache.taglibs.standard.tag.rt.core.OutTag _jspx_th_c_out_1 =
          new OutTag();
Line 3  _jspx_th_c_out_1.setValue((Object) PageContextImpl.proprietaryEvaluate
          ("Just set the name from request![${nameBean.name}]"));
Line 4  _jspx_th_c_out_1.setDefault("null");
清单 2 的表
代码行描述
2创建 c:out 标记的实现类的实例,并将其放入变量 _jspx_th_c_out_1 中。
3对该实例变量调用 setValue() 方法,以设置我们希望 out 标记根据浏览器返回的值。不过此值包含一个 EL 表达式 ${nameBean.name}。EL 表达式要求特殊处理,因为其表示的是运行时动态发现的值,与从来不会改变的静态文本不同。WebSphere Application Server 用于支持运行 nameBean.name 值的运行时发现的机制是调用 proprietaryEvaluate() 方法。在运行时执行 OutTag 类时,它将使用 proprietaryEvaluate() 来查找 nameBean.name 的当前值,并会将表达式 ${nameBean.name} 替换为当前值,然后将文本字符串返回给浏览器。
4调用 setDefault() 来设置 default 属性的值。

b. 另一个自定义标记代码生成示例

codegen.jsp 文件中下面的代码片段检查是否存在名为 tableColor 的请求参数。如果该参数存在,它会将 tableColorVar 变量设置为该请求参数的值。(将在下面看到如何使用 tableColorVar。)

清单 3
<c:if test='${not empty param.tableColor}'>
   <c:set var="tableColorVar" value="${param.tableColor}"/>
</c:if>

这些操作是通过 _codegen.java 的 _jspService() 方法中的单个方法调用来调用的。

if (_jspx_meth_c_if_0(pageContext)) return;

方法 _jspx_meth_c_if_0() 包含对 JSTL c:if 标记的实现的调用。以下是对该方法最重要的部分的说明:

清单 4
Line 1  private boolean _jspx_meth_c_if_0(PageContext pageContext) throws 
          Throwable {
Line 2    org.apache.taglibs.standard.tag.rt.core.IfTag _jspx_th_c_if_0 = 
            new IfTag();
Line 3    _jspx_th_c_if_0.setPageContext(pageContext);
Line 4    _jspx_th_c_if_0.setParent(null);
Line 5    _jspx_th_c_if_0.setTest(((Boolean) 
            PageContextImpl.proprietaryEvaluate
            ("${not empty param.tableColor}")).booleanValue());
Line 6    int _jspx_eval_c_if_0 = _jspx_th_c_if_0.doStartTag();
Line 7    if (_jspx_eval_c_if_0 != Tag.SKIP_BODY) {
Line 8      do {
Line 9        if (_jspx_meth_c_set_1(_jspx_th_c_if_0, pageContext)) 
                return true;
清单 4 的表
代码行描述
2创建 c:if 标记的实现类的实例,并将其放入变量 _jspx_th_c_if_0 中。
5对该实例变量调用 setValue() 方法,以设置我们希望在方法执行时评估运行时的测试条件。对于上面的 c:out 示例,EL 表达式 ${not empty param.tableColor} 将由 proprietaryEvaluate() 方法进行评估。
6c:if 标记实现类通过 doStartTag() 方法执行。doStartTag() 将在 _jspx_eval_c_if_0 中返回一个值。
7如果 _jspx_eval_c_if_0 不等于常量值 Tag.SKIP_BODY,则意味着 EL 表达式计算结果为 true:存在名为 tableColor 的请求参数!
9将执行另一个方法:_jspx_meth_c_set_1()。可以在 _codegen.java 代码片段中看到此方法。此方法检索请求参数 tableColor 的值(我们现在已经知道存在此参数),并将该值放入 tableColorVar 变量中(我们将稍后使用)。

c. jsp:useBean 操作的代码生成

我们在前面讨论模板文本的验证时使用了这个模板文本示例:

<jsp:useBean id="nameBean"
class="codegen.Nameclass" scope="request" />

JSP 规范对 jsp:useBean 操作的描述如下:

jsp:useBean 操作相当灵活;其准确语义取决于所给定的属性。基本语义尝试使用 id 和 scope 找到现有对象。如果未找到对象,将会尝试使用其他属性创建对象。

我们的 jsp:useBean 用法满足上面描述的“基本语义”。WebSphere Application Server 生成以下代码:

清单 5
Line 1  codegen.Nameclass nameBean = null;  
Line 2  synchronized (pageContext) {	
Line 3    nameBean = (codegen.Nameclass) pageContext.getAttribute("nameBean", 
          PageContext.REQUEST_SCOPE);
Line 4  if (nameBean == null) {
Line 5    nameBean =  new codegen.Nameclass();
Line 6    pageContext.setAttribute
            ("nameBean", nameBean, PageContext.REQUEST_SCOPE);
Line 7  }
Line 8  }
清单 5 的表
代码行描述
1定义在 class="codegen.Nameclass" 中给定的类型的 nameBean 变量
2同步对 pageContext 的访问,因为可以并发执行其他线程。
3在“request”范围中获得名为“nameBean”的对象。
4-6如果未找到 nameBean,使用“request”范围创建它并存储。

此生成的代码直接遵循 JSP 规范为这个简单的 jsp:useBean 用法描述的规则。

d. 包含嵌入 EL 表达式的模板文本的代码生成

我们在前面讨论模板文本的验证时使用了这个模板文本示例:

<table bgcolor=${tableColorVar} border="0" width="25%">

WebSphere Application Server 从此示例生成以下代码:

清单 6
Line 1	out.write("\r\n<table bgcolor=");
Line 2	out.write((java.lang.String) 
		PageContextImpl.proprietaryEvaluate("${tableColorVar}"));
Line 3	out.write(" border=\"0\" width=\"25%\">);
清单 6 的表
代码行描述
1JSP 容器在遇到 ${tableColorVar} 前并未发现任何需要处理的语法。因此第一部分将按原样输出到 _codegen.java 中(包括回车和换行)。
2但 ${tableColorVar} 是 EL 表达式,直接将 out.write("${tableColorVar}"); 放置到 _codegen.java 中将无法完成 JSP 页的创建者希望进行的工作。创建者希望 ${tableColorVar} 为动态运行时值。与上面描述的 c:out 标记生成类似,JSP 容器将生成对 proprietaryEvaluate() 的调用,而后者将为 tableColorVar 发现运行时值。
3模板文本的最后一部分将按原样输出到 _codegen.java 中——JSP 容器会添加反斜杠,以便将引号作为文本输出。

此模板文本示例的代码生成结果为实现类中的三行 Java 代码。浏览器中所得到的 HTML 将与以下所示类似:

<table bgcolor=blue border="0"
width="25%">

HTML 表将在浏览器中以蓝色背景显示。

上面的示例假定,与以下类似的请求 URL 导致 ${tableColorVar} 在运行时解析得到的值为“blue”:

http://localhost:9080/yourapp/codegen.jsp?nameParam=Your Name&tableColor=blue

e. 脚本元素的代码生成

JSP 容器对 scriptlet、表达式和声明的处理各不相同。

表达式(如 codegen.jsp 中的 <%=nameClass.getName()%>)转换为 _codegen.java 中与以下所示类似的 Java 代码:

out.print(nameClass.getName());

<%=nameParam%> 之类的表达式转换为与以下所示类似的 Java 代码:

out.print(nameParam);

在这两个示例中,无论在表达式中找到什么,都会将其作为页实现类的服务方法内的 out.print() 语句(有时候也可以是 out.write())参数。

有效声明(如 nameclass.jsp 中 Nameclass 的声明)将不加修改地放入 _codegen.java 中的页实现类的类级别,而不是在服务方法内。这是 _codegen.java 的一部分,以下面的代码开头:

public class Nameclass {
private String name="";

我们在前面将 nameclass.jsp 以静态方式包括到 JSP 页中 JSP include 指令所在的位置。在 Java 代码生成期间,JSP 容器接受此代码块,根据声明 JSP 规则将其移动到一个完全不同的地方(在类级别)。

<% if (nameParam != null){nameClass.setName(nameParam);%> 之类的有效 scriptlet 将直接被发送到实现类的服务方法中:

if (nameParam != null){nameClass.setName(nameParam);

请记住,JSP 容器将以逐个字符的方式对页进行解析。标识了这三个脚本元素之一的开头部分后,还必须确定其结束位置。因此,如果碰巧错误理解了 scriptlet 的工作方式,并尝试在 scriptlet 中使用表达式来动态地初始化 Java 变量,将会失败。下面的示例说明了这个错误:

清单 7
<% if (nameParam != null){nameClass.setName(nameParam);
   String theName=new String("<%=nameParam%>");
//   trying to instantiate a Java variable called theName
   out.print(theName);
//   using a JSP expression inside a scriptlet.
%>

新手可能认识不到脚本并非设计用于处理三个元素的任何组合,从而不能编写出正常的代码。在本例中,JSP 容器识别到 scriptlet 的开始部分 (<% if (nameParam != null){...),但随后遇到 %>(表达式 <%=nameParam%> 的结尾)时会将其视为 scriptlet 的结尾。这会导致生成的 Java 代码无效。

多年前,JSP 2.0 规范建议避免脚本元素。为何我(以及其他很多人)反对在 JSP 内使用 Java 代码呢?

  1. 这非常危险。我最近花了很多时间对客户的故障应用程序进行调试。开发人员迷失了方向。我们对他们的所有 JSP 进行了分析。最后,我发现他们通过声明元素声明了很多全局变量(类实例变量)。当 JSP 在多用户环境中执行时,一个特别重要的变量被一个用户的请求线程更改了,而此时正在处理另一个用户的请求线程。这样做的结果是,响应输出被严重地破坏了。这些开发人员非常尴尬,但我告诉他们这是一个很常见的错误,特别在没有注意到是单个 Servlet 处理所有请求时更容易出现这样的问题!如果选择采用脚本元素则很容易犯这样的错误,因此最好彻底避免使用脚本元素。

  2. 它使得 JSP 难以阅读和调试。通常将看到与 codegen.jsp 中类似的语法,after <!-- ***** but scripting is used here ***** -->.分析这个微型 JSP 中的脚本代码并不容易。零散的大括号(例如 <%}%>)很容易混淆,但在 JSP 中嵌入 Java 时需要此类括号来让 Java 代码保持有效和完整。当 JSP 包含数百或数千行 HTML、JSP 语法和 Java 脚本的混合代码时,将很容易成为您的维护与调试的拦路虎。

f. jspInit() 与 jspDestroy()

如果 JSP 页创建者希望 JSP 页具有 init 和 destroy 方法时,必须使用脚本。具体来说,必须在 JSP 页中包含声明这些方法的 JSP 声明。此类 JSP 页必须与以下所示类似:

清单 8
<%!
public void jspInit()
{
  // do some initialization work
}
public void jspDestroy()
{
  // do some finalization work
}
%>
<HTML>
  <BODY>
      Your JSP stuff here!
  </BODY>
</HTML>

g. 一首打油诗

我在这里给出了一首关于 JSP 脚本元素的打油诗(虽然有点不押韵),虽不太出名但非常切题。

There once was a coder from France(法国有个程序员)
Who wrote by the seat of his pants(每天编码轮轴转)
When his JSPs broke(他的 JSP 崩溃后)
He collapsed, and then spoke:(他也崩溃着忍不住低声吼:)
“I used scriptlets in my ignorance!”(使用 scriptlet 真是难!)

h. 代码生成完成时

当 JSP 容器完成 .java 文件生成后,将进入下一个处理阶段:Java 编译。

3. Java 源代码编译

JSP 容器编译其生成的 .java 文件。Java 编译工作将创建二进制类(JSP 页实现类),这些二进制类将作为 .class 文件驻留在磁盘上。* 如果出现 Java 编译错误,将生成标识错误的错误消息、JSP 行号以及出现错误的对应 .java 源文件行号。

WebSphere Application Server 和大多数 JSP 容器一样,支持 Eclipse Java Development Tools (JDT) 编译器JDK 的 javac。WebSphere Application Server 从 V6.1 开始支持 JDT 编译器。JDT 编译器是 JSP 容器的缺省 Java 编译器,因为该编译器在开放服务网关协议(Open Services Gateway Initiative,OSGi)环境中可非常高效地操作。

请求阶段

当在运行的应用服务器中请求 JSP 时,会调用其页实现 Servlet 类。对于每个请求,Servlet 的服务方法将在自己的线程(即执行请求的线程)上执行。这样,单个 JSP Servlet 可以处理多个并发请求。

WebSphere JSP 容器通过两种方式参与请求阶段的工作:

  1. 运行时支持:提供 Servlet 所使用的类和方法。这称为运行时支持。例如,我们前面看到的 proprietaryEvaluate() 方法就是一个运行时支持方法。
  2. 重载:检查以确定用于创建 Servlet 的 JSP 源文件是否被修改而需要进行重新转换。请参见下面的 JSP 重载

可在此处找到关于 WebSphere JSP 容器在请求阶段的角色的详细讨论。

JSP Servlet 的性能障碍

尽管 JSP Servlet 只是一个普通 Servlet,但永远不会像不采用 JSP 技术而手动编写的等价 Servlet 那样执行。这是因为 JSP 容器生成的 JSP Servlet 必须满足很多使 JSP 技术更为强大和易用的要求。

例如,生成的 JSP Servlet 必须提供名为 pageContext 的 Java 对象(将在下面说明),每个 _jspService() 方法调用必须获得此对象并初始化一些其他变量:

清单 9
pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 
	8192, true);
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();

以下的 pageContext 对象的说明摘自 JSP 规范:

PageContext 可为页/组件创建者和页实现人员提供一系列机制,包括:

  • 用于管理各种范围的命名空间的单个 API
  • 用于访问各种公共对象的一系列使用方便的 API
  • 获取 JspWriter 进行输出的机制
  • 用于通过页管理会话用法的机制
  • 用于将页指令属性公开给脚本环境的机制
  • 用于转发或将对其他活动组件的当前请求包括到应用程序中的机制
  • 用于处理 errorpage 异常处理的机制

如果要在不使用 JSP 技术的情况下编写 _codegen.java Servlet,则编写 codegen.jsp 并让 JSP 容器生成 Servlet 的工作将更为困难。但如果手动编写的 Servlet 并不需要上面所述的机制,则不必提供这些东西,得到的 Servlet 将比生成的 Servlet 更为轻量,执行的速度更快。

JSP 重载

当 JSP 源文件被修改且随后被请求时,可以对其进行重载。重载涉及到对请求的 JSP 执行整个转换阶段,从而生成新的 JSP 实现类。这个新类反映对 JSP 源代码的修改,将随后被重载并调用。

JSP 规范并不强制 JSP 容器在 JSP 正在运行的 Web 应用程序中被修改时转换和重载 JSP。不过,所有的 JSP 容器均提供此功能。运行时 JSP 重载是 JSP 源代码频繁更改的环境中一项非常方便的功能。

WebSphere Application Server 提供了多种方式来配置 JSP 重载

静态 include 与动态 include

JSP 技术提供两种 include(静态与动态)。这两种 include 类型经常彼此混淆。

我们已经看到过以下的静态 include 示例:

<%@ include file="nameclass.jspf" %>

静态 include 由 JSP 容器在转换阶段进行处理(此时 JSP 容器正在内存中构造 JSP 页)。

动态 include(称为 jsp:include 标准操作)在请求阶段执行。

动态 include 的语法如下:

<jsp:include page="urlSpec"
flush="true|false"/>

如果 JSP 包含以下 include 操作:

<jsp:include page="myInclude.jsp" flush="true "/>

则 JSP 容器将生成以下 Java 代码:

JspRuntimeLibrary.include(request, response, "myInclude.jsp", out, true);

在请求阶段执行此代码行时,将调用 myInclude.jsp,且将其输出包含在当前 JSP 的输出中。JSP 规范这样描述 include 操作:

include 操作将 JSP 页之类的资源视为动态对象;即请求将被发送到该对象,且将包含处理的结果。

结束语

我希望本文能帮助您了解 JSP 容器如何验证 JSP 页、为页的实现 Servlet 生成 Java 代码以及参与请求处理。请研究下面的示例代码和生成的源代码,通过实际的例子巩固本文讨论的内容。

示例 A

清单 10. codegen.jsp(顶级 JSP 文件)
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html><head><title>Code Validation and Generation Sample</title></head>
<body>

<!-- ***** no scripting is used here ***** -->
<jsp:useBean id="nameBean" class="codegen.Nameclass" scope="request" />
<table>
<tr><td><c:out value='Checking to see if name is given in the request...'/></td></tr>
<c:choose>
    <c:when test='${not empty param.nameParam}'>
        <jsp:setProperty name="nameBean" property="name" value='${param.nameParam}'/>
        <tr><td><c:out value='Just set the name from request! [${nameBean.name}]'default="null"/></td></tr>
    </c:when>
    <c:otherwise>
        <tr><td><c:out value='Name not supplied on request...'/></td></tr>
        <jsp:setProperty name="nameBean" property="name" value="J. Doe"/>
        <tr><td><c:out value='... so we set it to [${nameBean.name}]'/></td></tr>
    </c:otherwise>
</c:choose>
</table>
</br>

<!-- ***** but scripting is used here ***** -->
<%@ include file="nameclass.jspf" %>
<%Nameclass nameClass = null;String nameParam=(String)request.getParameter("nameParam");%>
<%nameClass=new Nameclass();%>
<table>
<tr><td>Checking to see if name is given in the request...</td></tr>
<% if (nameParam != null){nameClass.setName(nameParam);%>
   <tr><td>Just set the name from request! [<%=nameParam%>]</td></tr>
<%}else{ %>
   <tr><td>Name not supplied on request...</td></tr>
   <% nameClass.setName("J. Doe");%>
   <tr><td>...so we set it to [<%=nameClass.getName()%>]</td></tr>
<%}%>
</table>
</br>

<!-- ***** results table ***** -->
<c:set var="tableColorVar" value="blue" scope="request" />   
	<!-- ***** blue is the default table color ***** -->
<c:if test='${not empty param.tableColor}'><c:set var="tableColorVar" value="${param.tableColor}"/></c:if><table bgcolor=${tableColorVar} border="0" width="25%">
<tr><td bgcolor="lightgray">Name from EL expression <b>(\${nameBean.name})</b></td>
<td><font color="white">${nameBean.name}</font></td></tr>
<tr><td bgcolor="lightgray">Name from JSP expression <b>(<\%=nameClass.getName()%>)
	</b></td>
<td><font color="white"><%=nameClass.getName()%></font></td></tr>
</table>
</body></html>

------------------- nameclass.jspf  (statically included in codegen.jsp and used in 
	the scriptlet <%nameClass=new Nameclass();%>) -------------------
<%!
// This class will be statically included in codegen.jsp,
//    and will be placed in the class section of the generated code, _codegen.java, 
//    because it is enclosed in a JSP declaration.
public class Nameclass {
    private String name="";
    public Nameclass() {
    }
    public Nameclass(String name) {
        this.name=name;
    }
    public void setName(String name) {
        this.name=name;
    }
    public String getName() {
        return this.name;
    }
}
%>
------------------- Nameclass.java  (must be compiled into a .class file;  Nameclass is 
	used by the <jsp:useBean> element in codegen.jsp) -------------------
package codegen;
public class Nameclass {
    private String name="";
    public Nameclass() {
    }
    public Nameclass(String name) {
        this.name=name;
    }
    public void setName(String name) {
        this.name=name;
    }
    public String getName() {
        return this.name;
    }
}

示例 B

(此代码片段中的详细代码只是用于演示目的;将无法对其进行编译。)

清单 11. 示例生成代码
------------------- _codegen.java -------------------
public final class _codegen extends com.ibm.ws.jsp.runtime.HttpJspBase {
  private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();

  // This declaration of the class called Nameclass was statically included from 
  // nameclass.jspf where it is defined as a JSP declaration  (<%! ... %>).  Because 
  // it was defined as a JSP declaration, it is placed in _codegen.java at the class 
  // level (as opposed to inside the _jspService() method).
  public class Nameclass {
        private String name="";
        public Nameclass() {
        }
        public Nameclass(String name) {
              this.name=name;
        }
        public void setName(String name) {
              this.name=name;
        }
        public String getName() {
              return this.name;
        }
  }

  // this _jspService() method is invoked when a request for codegen.jsp is received by 
  // the application server
  public void _jspService(HttpServletRequest request, HttpServletResponse  response)
      throws java.io.IOException, ServletException {

    PageContext pageContext = null;
    ServletContext application = null;
    ServletConfig config = null;
    JspWriter out = null;

    try {
      pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 
	8192, true);

      application = pageContext.getServletContext();
      config = pageContext.getServletConfig();
      out = pageContext.getOut();

      out.write("\r\n<html><head><title>Code Validation and Generation Sample</title>
	</head>\r\n<body>\r\n\r\n");

      out.write("<!-- ***** no scripting is used here ***** -->");

      // This is code generated from the jsp:useBean statement in codegen.jsp
      codegen.Nameclass nameBean = null;
      synchronized (pageContext) {
        nameBean = (codegen.Nameclass) pageContext.getAttribute("nameBean", 
		PageContext.REQUEST_SCOPE);
        if (nameBean == null) {
          nameBean =  new codegen.Nameclass();
          pageContext.setAttribute("nameBean", nameBean, PageContext.REQUEST_SCOPE);
        }
      }
      out.write("\r\n<table>\r\n<tr><td>");

      // This is code generated from the <c:choose> custom tag in codegen.jsp
      if (_jspx_meth_c_choose_0(pageContext)) return;
      out.write("\r\n</table>\r\n</br>\r\n\r\n");

      out.write("<!-- ***** but scripting is used here ***** -->");

      Nameclass nameClass = null;String nameParam=(String)request.getParameter
	("nameParam");

      nameClass=new Nameclass();
      out.write("\r\n<table>\r\n<tr><td>Checking to see if name is given in the 
	request...</td></tr>\r\n");
       if (nameParam != null){nameClass.setName(nameParam);
        out.write("\r\n   <tr><td>Just set the name from request! [");
        out.print(nameParam);
        out.write("]</td></tr>\r\n");
      }else{
        out.write("\r\n   <tr><td>Name not supplied on 
		request...</td></tr>\r\n   ");
        nameClass.setName("J. Doe");
        out.write("\r\n   <tr><td>...so we set it to [");
        out.print(nameClass.getName());
        out.write("]</td></tr>\r\n");
      }
      out.write("\r\n</table>\r\n</br>\r\n\r\n");
      out.write("<!-- ***** results table ***** -->");

      // This is code generated from the first <c:set> custom tag in codegen.jsp
      if (_jspx_meth_c_set_0(pageContext)) return;

      // This is code generated from the <c:if> custom tag in codegen.jsp
      if (_jspx_meth_c_if_0(pageContext)) return;
      out.write("\r\n<table bgcolor=");
      out.write((String) PageContextImpl.proprietaryEvaluate("${tableColorVar}"));
      out.write(" border=\"0\" width=\"25%\">\r\n<tr><td bgcolor=\"lightgray\">Name from 
	EL expression
                  <b>(${nameBean.name})</b></td>\r\n<td><font color=\"white\">");
      out.write((String) PageContextImpl.proprietaryEvaluate("${nameBean.name}"));
      out.write("</font></td></tr>\r\n<tr><td bgcolor=\"lightgray\">Name from JSP 
	expression 
                  <b>(<%=nameClass.getName()%>)</b></td>\r\n<td><font color=\"white\">");
      out.print(nameClass.getName());
      out.write("</font></td></tr>\r\n</table>\r\n</body></html>\r\n\r\n");

    } finally {
      _jspxFactory.releasePageContext(pageContext);
    }
  }

  private boolean _jspx_meth_c_out_1(JspTag _jspx_th_c_when_0, PageContext pageContext) 
	throws Throwable {
    org.apache.taglibs.standard.tag.rt.core.OutTag _jspx_th_c_out_1 = new OutTag();
    _jspx_th_c_out_1.setValue((Object) PageContextImpl.proprietaryEvaluate("Just set the 
	name from request! [${nameBean.name}]"));
    _jspx_th_c_out_1.setDefault("null");
    int _jspx_eval_c_out_1 = _jspx_th_c_out_1.doStartTag();
    if (_jspx_th_c_out_1.doEndTag() == Tag.SKIP_PAGE) {
      return true;
    }
    return false;
  }

  private boolean _jspx_meth_c_set_1(JspTag _jspx_th_c_if_0, PageContext pageContext) 
	throws Throwable {
    org.apache.taglibs.standard.tag.rt.core.SetTag _jspx_th_c_set_1 = new SetTag();
    _jspx_th_c_set_1.setPageContext(pageContext);
    _jspx_th_c_set_1.setParent((Tag) _jspx_th_c_if_0);
    _jspx_th_c_set_1.setVar("tableColorVar");
    _jspx_th_c_set_1.setValue(new JspValueExpression("/codegen.jsp(42,4)
                 '${param.tableColor}'",_el_expressionfactory.createValueExpression(new
                  ELContextWrapper(pageContext.getELContext(),_jspx_fnmap),
		 "${param.tableColor}",Object.class))
                  .getValue(pageContext.getELContext()));
    int _jspx_eval_c_set_1 = _jspx_th_c_set_1.doStartTag();
    if (_jspx_th_c_set_1.doEndTag() == Tag.SKIP_PAGE) {
      return true;
    }
    return false;
  }

  private boolean _jspx_meth_c_if_0(PageContext pageContext) throws Throwable {
    org.apache.taglibs.standard.tag.rt.core.IfTag _jspx_th_c_if_0 = new IfTag();
    _jspx_th_c_if_0.setPageContext(pageContext);
    _jspx_th_c_if_0.setParent(null);
    _jspx_th_c_if_0.setTest(((Boolean) PageContextImpl.proprietaryEvaluate("${not 
	empty param.tableColor}")).booleanValue());
    int _jspx_eval_c_if_0 = _jspx_th_c_if_0.doStartTag();
    if (_jspx_eval_c_if_0 != Tag.SKIP_BODY) {
      do {
        if (_jspx_meth_c_set_1(_jspx_th_c_if_0, pageContext)) return true;
        int evalDoAfterBody = _jspx_th_c_if_0.doAfterBody();
        if (evalDoAfterBody != BodyTag.EVAL_BODY_AGAIN) break;
      } while (true);
    }
    if (_jspx_th_c_if_0.doEndTag() == Tag.SKIP_PAGE) {
      return true;
    }
    return false;
  }
}

相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=WebSphere
ArticleID=209789
ArticleTitle=评论专栏: Scott Johnson:JavaServer Pages 新手入门
publish-date=04172007