浅论taglib设计
[概述]
Taglib是JSP比较高级的技术。做为JSP开发人员来讲,不了解taglib是可以接受的。因为JSP的风格或者JAVA的风格就是一种简洁的美。如果技术太过复杂或者繁琐,就会在技术的汪洋中失去自我。
但是,当我们的项目变得越来越大,或者团队有了一些技术积累之后,很自然就会有将我们的开发工作提高一个层次的需要。当我们面对一些非常类似的界面或者程序单元的时候,我们会想到把这样的工作成果直接用于下一个项目。这样的问题可以由taglib来解决。
到此还没有真正说明一下taglib是什么。只要你从事过JSP的开发工作,你就已经使用过taglib了。只不过是在不知不觉中使用的。你一定不会忘记 <jsp:include />标记。实际上这就是一个taglib。Taglib直译做标记库,是JSP定义给开发人员可以使用自行定义的标记体系。也就是说,开发人员可以在JSP中使用自己定义的特殊标记。而该标记可以用作特定的用途。比如显示一个每个页都需要的公司版权信息。就可以不用复制粘贴相同的代码到每个页去了。
但是taglib可以完成的工作远远不止这些。由于每个自定义标记一定是一个完全的JAVA类,我们可以定义非常丰富的一组行为,并且可以通过自定义的attribute来控制它。
[实例]
我习惯用实例来说明问题。大家也许都对用户会话状态的检查不陌生。当用户登录到系统后,我们希望自动保持用户的登录状态。而这个过程在每个需要认证用户才能访问的程序单元都需要实现。通常我们需要访问预定义的session服务器变量,当这个变量不满足某值时即判定改用户为非法访问或者会话状态丢失。
我们来看一下使用taglib如何实现。我们需要编写一个仅处理该逻辑的标记。实现的最简单的逻辑:检查用户状态session值,不满足某值时即将用户转向一个预先设置的报错页。
以下是源码(CheckSessionTag.java):
(省略了细节)
{
public int doEndTag()
{
try
{
String member_id = (String) pageContext.getSession().getAttribute("member_id");
if(member_id == null || member_id.equals(""))
{
pageContext.forward("/home/check_session_fail.jsp");
}
}
catch(Exception e)
{
// 报告异常过程省略.
}
return EVAL_PAGE;
}
}
[分析]
在以上源码中,我们在使tag自动检查用户session变量中的"member_id"值,如果该值为空,则立即判定用户没有访问权限,则立即将流程导向一个预先设定的报错页:/home/check_session_fail.jsp.
类CheckSessionTag派生自:TagSupport(javax.servlet.jsp.tagext.TagSupport). 是一个从JAVA 1.3就开始支持的类库,位于servlet.jar包。java文档给出的描述是:
A base class for defining new tag handlers implementing Tag. The TagSupport class is a utility class intended to be used as the base class for new tag handlers. The TagSupport class implements the Tag and IterationTag interfaces and adds additional convenience methods including getter methods for the properties in Tag. TagSupport has one static method that is included to facilitate coordination among cooperating tags. Many tag handlers will extend TagSupport and only redefine a few methods.
(该类为所有taglib的基类。该类定义了实现标记的一系列接口。)
在实例CheckSessionTag类中,我们仅仅重写了doEndTag方法。没有向其容器:jsp页输出任何东西。但是该类在实际应用中是切实可行的。
以下是在一个成品项目中的某jsp中截取的片断:
<logic:checkSession />
当我们直接使用输入url的方式访问本页时,我们被立即带到了报告:用户会话状态丢失或者未经登录的页。
这就省却了我们习以为常的一项工作:以前我们必须复制相同的scriplet到每个jsp页。(实际上,也可以使用类似intercept filter的模式来处理此需求)。这使我们的开发工作变得reusable, flexiable, 和extendable。可以想象,如果我们想改变检查session的逻辑,则可以仅仅通过改变CheckSessionTag内部的逻辑就可以通盘改变。并且我们可以通过给logic:checkSession标记加上类似target=admin的attribute来限定只有管理员才可以访问的区域。这就是形成组件化开发的基本过程。
如何实施呢?
[实施过程]
我们需要做一系列工作来将taglib引入我们的工程。
web.xml
为了使jsp解析器可以识别我们的taglib必须将其配置在web.xml内。web.xml位于/WEB-INF目录内。
使用特定语法来配置我们的taglib:
web.xml(片断)
<taglib>
<taglib-uri>/WEB-INF/logic.tld</taglib-uri>
<taglib-location>/WEB-INF/logic.tld</taglib-location>
</taglib>
<web-app>
该语法告诉容器到什么地方去寻找所有logic:开头的tag.
tld是taglib defination的缩写。即taglib定义。该文件定义了我们使用的标记,Java类如何加载,并且该标记如何工作。让我们来看一段实际的tld:
logic.tld(片断)
<!DOCTYPE taglib
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2/EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<short-name>logic</short-name>
<uri>/taglibs/logic</uri>
<tag>
<name>checkSession</name>
<tag-class>mbajob.common.tags.logic.CheckSessionTag</tag-class>
<body-content>empty</body-content>
</tag>
我们注意到taglib标记内.
1. short-name: 标记的prefix名。
2. uri: 识别该taglib的名称。
tag标记:
1. name: 标记名(prefix:之后)
2. tag-class:类名(包含包名)
3. body-content: 标记内容模式,如果该标记没有内容则为empty.
将logic.tld放置在/WEB-INF下,此时确保编译好的CheckSessionTag类可以被容器访问到。即可完成配置。必须注意的是,不同的jsp容器的配置可能有差别。本文的配置是基于Resin 2.1.11
[应用]
建立一个jsp页。在源码的开头加入如下的预编译指令:(page)
<%@ taglib prefix="logic" uri="/WEB-INF/logic.tld" %>
此指令告诉编译器到哪里去寻找所有以logic:开头的标记的定义。
接下来在任意位置加入标记:<logic:checkSession />即可以工作了。当标记被实例化后,即自动执行doEndTag过程,检查session服务器变量值,之后将页过程跳转。
[深入一些:attribute]
有时候我们希望可以对标记进行一些定制。依旧拿checkSession做例:现在我们要限制两类用户访问系统:某
些部分仅允许具有管理员权限的用户访问。这样我们设想可以在<logic:checkSession target=admin />进行进一步定义。这需要在CheckSessionTag类中增加一些额外的逻辑。检查的过程很简单,取决于你的安全系统的分析,但是,我们写了target attribute如何是类可以得到该数据,这是一个关键的问题。
为了实现对tag增加可用的attribute, 需要做如下工作:
1. 为类增加相应的成员及相应读写器:
CheckSessionTag.java(片断):
{
private String target;
public void setTarget(String t)
{
this.target = t;
}
public String getTarget()
{
return this.target;
}
public int doEndTag()
{
}
2. 更改logic.tld:
<tag>
<name>checkSession</name>
<tag-class>mbajob.common.tags.logic.CheckSessionTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>title</name>
<required>true</required>
</attribute>
</tag>
为tag标记增加arrtibute节点,语法如下:
name: attribute名
required: 是否为必须的attribute
幸运的是,jsp内置地将attribute解释为java类的成员,我们无需显式地获取该值,即可直接使用。也就是说,我们只要在tag内指定了target=admin, 那么,CheckSessionTag在活着的时候就自动获取该值,可以在逻辑中直接使用。
这样,我们就可以给tag增加任意的attribute.
[入门以后]
到此为止,我么就可以写一些类似的简单的tag了。根据不同的需求,完成不同的逻辑。当我们开发一个新的tag的时候,我们在logic.tld里增加一个tag子标记。设置好相应的类型。如果需要,我们可以把我们已经写好的tag们完全的移植到第二个项目中使用,仅仅做很少的更改。而更改也仅仅限于对java源代码。
在我和我的团队的实际经验中,taglib最多的应用还是在设计一系列特殊的UI. 例如类似于选择省市多级行政区域的选择器。为了放置在HTML中嵌入过多scriplet和脚本,我们的做法通常是将其写在组装好的tag里;为了形成整个应用一致的外观,我们设计了一套用于组装页构图的框架;为了向jsp输出集合数据,我们设计了可以绑定数据的呈现器。经过很长一个时期的工作,我们发现我们的项目中jsp页内仅有很少的html, 而完全是有taglib组成的。最终,我们的taglib按照layout, logic, element, form分类,已经形成了比较大的规模。我们甚至完成了apache的struts框架完成的一部分工作。甚至做得更灵活。所有这些工作中积累的思想甚至影响到我在.NET平台下的技术思路。
为了不使篇幅太长,我不准备在本文再做深入的探索。仅仅介绍taglib入门级的一些东西。有时间的话,可能会再次写一些高级一些的taglib的设计方法。
[与ASP.NET Web Custom Control]
不能说taglib与ASP.NET Web Custom Control有什么可比性。但是我习惯将二者放到一起看。因为因为在某些实际项目中,我确实从二者之间找到一些共性。甚至有时候创作一些组件的时候实现是完全相同的。
1. 二者都定义了当标记输出开始标记和结束标记时的过程。
2. 二者都可以跨应用使用。
3. 可以使用相同的HTML和脚本。
但比较起来,ASP.NET Web Custom Control更高级一些。可以在容器中直接以对象的形式访问。并且可以定义方法,事件。意即可以完全控制一个控件。
而taglib同样有优点,例如可以嵌套使用,开发成本低等。最关键的是,taglib具有做为java产品的精细,小巧和活泼。他们分别是有着不同风格的优秀技术。
参考文档
taglib最佳实践(IBM developerWorks中文站)
http://www-900.ibm.com/developerWorks/cn/java/j-jsp07233/
完成于:2004-09-28
修改记录:
2004-09-28 一般性修改