级别: 初级 杨健 (ytjcopy@263.net)中南工业大学
2001 年 7 月 23 日 业务逻辑层与表示层的分离一直是基于WEB应用开发的一大目标。因为这样可以使开发人员更加专注于自己领域的开发(程序工程师负责业务逻辑,而网页制作人员等负责表示层)。其中JavaBean就是一个很好的解决方案。自JSP1。0以后,出现了Tag Extension API(标记扩展API)。这使得WEB开发人员可以自定义标记,将其嵌在JSP页面中,来完成复杂的业务逻辑。这样,网页制作人员只需向JSP页面中添加简单、熟悉的标记就可以实现相应业务逻辑,而这些业务逻辑的封装则由程序工程师来完成。本文将介绍如何使用Tag Extension API来实现这一目标。
Tag Extension API 提供了一系列标记管理器(Tag
Handler)。这些标记管理器是由容器管理的、执行JSP页面时运行的对象。它们通常以<
prefix:CustomTag ></ prefix:CustomTag >或者<
prefix:CustomTag
/>的形式出现在JSP页面中,而它们的实现则是一个.class文件。标记管理器是基于JavaBean组件技术的。但它在WEB编程中比纯粹的JavaBean更具优势。下面给出一个查询的例子来说明这一点。
一般的,我们使用JavaBean来实现查询,业务逻辑封装的不是很好。下面是在JSP中使用JavaBean实现某商品查询的一段代码:
<jsp:useBean id="DataDAO" class="package.DataDAO"></jsp:useBean>
<!?D?D 查询数据库,返回记录集 ?D?D>
<%
java.sql.ResultSet resultset=DataDAO.Query();
%>
<!?D?D 将结果循环输出 ?D?D>
<table>
<%
while(resultset.next()){
%>
<tr>
<td><%=resultset.getString("id");%></td>
<td><%=resultset.getString("name");%></td>
<td><%=resultset.getString("price");%></td>
<td><%=resultset.getString("vendor");%></td>
</tr>
<%}%>
</table>
........
|
可以看到,JSP页面中仍然出现了大量的Java代码。这使得网页制作人员很不习惯,因为他们对这些Java代码并不熟悉。对比一下利用Tag Extension API的实现,他们一定会为之惊讶。
<%@ taglib uri="/WEB-INF/tlds/taglib.tld" prefix="shopping" %>
<table>
<!-- 循环输出已封装 -->
<shopping:goodsheader>
<tr>
<td><shopping:goodsdetail detail="id"/></td>
<td><shopping:goodsdetail detail="name"/></td>
<td><shopping:goodsdetail detail="price"/></td>
<td><shopping:goodsdetail detail="vendor"/></td>
</tr>
</shopping:goodsheader>
</table>
|
以上JSP页面中Java代码几乎为零。网页制作人员会欣喜的说:"这才是我想要的"。代码第一行指定了自定义标记库描述文件(Tag
Liabary
Descriptor)的位置。这是一个xml的描述文件(注意后缀必须是tld),用来描述自定义标记。以上代码使用了两个标记(goodsheader
和 goodsdetail),在标记库描述文件
taglib.tld中对它们的描述如下:
<!-- goodsheader -->
<tag>
<name>goodsheader</name><!-- 标记名,在JSP页面中使用 -->
<tagclass>student.taglib.GoodsHeaderTag</tagclass><!-- 实现该标记的类名 -->
<bodycontent>JSP</bodycontent><!-- body content 的类型,一般为JSP -->
<info>Insert Goods Infomation</info><!-- 标记信息 -->
<attribute><!-- 标记属性 -->
<name>id</name><!-- 标记属性名 -->
<required>false</required><!-- 标记属性是否必须,false为不是必须 -->
<rtexprvalue>true</rtexprvalue><!-- 标记属性值是否是运行时确定,true为是运行时确定 -->
</attribute>
</tag>
<!-- goodsdetail -->
<tag>
<name>goodsdetail</name>
<tagclass>student.taglib.GoodsDetailTag</tagclass>
<bodycontent>JSP</bodycontent>
<info>Insert Goods Information Detail</info>
<attribute>
<name>id</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>detail</name>
<required>true</required><!-- 该标记属性是必须的 -->
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
|
其中<tagclass></tagclass>标记间的类声明就是该标记的实现类的类名。这样,网页制作人员只需查阅相应的标记描述说明,就能简单迅速的在JSP页面中插入动态数据。那么,我们怎样来实现这些标记呢?
Tag Extension API 中提供了两种类型的标记管理器:Simple Tag
Handler 和 Tag Handler that want access to the body
content(简单标记管理器和可以访问其主体内容的标记管理器).这里仅用到第一类,即简单标记管理器。这类标记管理器不能操纵它的主体内容(body
content,即在标记之间的内容)。本例中我们并不需要操纵标记间的内容。
这里使用的两个标记管理器都继承自TagSupport类。TagSupport类实现了Tag
和
IterationTag两个接口。有必要介绍一下它的几个主要的、由容器管理的方法:
-
doStartTag():
在标记开始处由容器调用,返回值将决定是否对body
content计值。返回值为Tag.EVAL_BODY_INCLUDE时计值,为Tag.SKIP_BODY时忽略body
content。(所谓对body content计值,就是将body
content写入输出流中输出;body content,即标记之间的内容);
-
doAfterBody():
当doStartTag()方法调用完毕并且返回Tag.EVAL_BODY_INCLUDE时,将对该标记的body
content进行计值处理。计值完以后,将由容器调用该方法。因为只有对body
content计值,才可能触发"after body"这一事件;
-
doEndTag():
当doStartTag()方法调用完毕并且返回Tag.SKIP_BODY时或者doAfterBody()方法调用完毕并且返回IterationTag.SKIP_BODY时调用,一般在该方法中输出结果,即将结果写入JspWriter输出流中输出;该方法可以返回Tag.SKIP_PAGE或者Tag.EVAL_PAGE。前者不再对该标记以后的页面计值,后者继续对该标记以后的页面计值;
-
getParent():
获得最近的、包围了自己的一个标记管理器实例。即自己在该标记管理器实例的body
content
中。本例中GoodsDetailTag实例将调用getParent()来获得GoodsHeaderTag的一个实例。
-
setters方法: 设置Tag属性的方法,当Tag
Handler实例化时由容器调用,从而获取JSP页面中的参数。如本例中GoodsDetailTag类中的setDetail()方法。
我们需要重载doStartTag()、doEndTag()以及在GoodsHeaderTag类中重载doAfterBody()方法。GoodsDetailTag类中有一个setDetail()方法,用来从JSP页面中获取detail参数,以决定如何在表格中输出商品信息。
GoodsHeaderTag类的实现(这里仅给出核心代码,源代码附后
GoodsHeaderTag.java):
//在doStartTag()中完成数据库查询并保存结果集于resultset中
public int doStartTag(){
resutlset=query();
return EVAL_BODY_INCLUDE; //返回结果表明对body content 计值
}
//在doAfterBody()中判断resultset是否已输出完毕
public int doAfterBody(){
if(resultset.isAfterLast()){
return SKIP_BODY;// 输出完毕则继续往下执行
}else{
resultset.next();
return EVAL_BODY_AGAIN;//还有未输出,重新对其body content计值
}
}
//返回EVAL_PAGE,继续对该页面计值,即对标记以后的部分进行处理
public int doEndTag(){
return EVAL_PAGE;
} |
GoodsDetailTag 类的实现(
GoodsDetailTag.java):
//设置属性
public void setDetail(String detail){
this.detail=detail;
}
//在doStartTag()中调用getParent()获取GoodsHeaderTag的实例及其结果集
public int doStartTag(){
GoodsHeaderTag rht = (ResultHeaderTag)this.findAncestorWithClass(this,GoodsHeaderTag.class);
if (rht != null) {
resultset = rht.resultset;
}
… …
return SKIP_BODY;
}
//在doEndTag()中根据属性detail将结果输出
public int doEndTag(){
pageContext.getOut().print(resultset.getString(detail));
return EVAL_PAGE;
} |
值得注意的是,IterationTag API是在JSP1.1以后才出现,请读者在选择JSP、SERVLET容器时注意这一点。所附源代码没有考虑中文处理,显示中文请参考其它相关资料。该实例在windows2000+Oracle8i+Tomcat4.0环境下执行通过。
参考资料
《JavaServer Pages? Specification version1.2》
关于作者  | |  | 杨健,中南工业大学计算机科学与技术专业硕士研究生,参与过大型MIS系统开发,网站建设等,现在正致力于Java技术的研究。Email:
ytjcopy@china.com。
|
对本文的评价
|