IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope:Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Java technology | Web development  >

利用Tag Extension API实现业务逻辑与表示的分离

运用JSP的标记扩展应用程序接口编程

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

杨健 (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




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?




回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款