javaweb|封装taglib组件
第 9 章 封装taglib组件
注意
这里讲介绍自定义标签库(taglib),将原本需要写在jsp中的java代码封装起来,成为可复用的组件。
taglib本意是为了弥补jsp的先天不足,但它的笨重与复杂也颇为经典,可惜有的地方又不得不用,如果对其没有耐心尽可跳过。
如果你不满足以下任一条件,请继续阅读,否则请跳过此后的部分,进入下一章:第 10 章 综合电子留言板。
-
了解taglib的使用和制作。
-
根本不想消除jsp中的java代码,也不打算写一些可以复用的组件。
回到联系簿的例子第 5.2 节 “Read(读取)”,不觉得这个list.jsp中的java代码太碍眼了吗?
<% List list = contactDao.getAll(); for (int i = 0; i < list.size(); i++) { pageContext.setAttribute("contact", list.get(i)); pageContext.setAttribute("row", i % 2 != 0 ? "odd" : "even"); %> <tr class="${row}" onmouseover="this.className='highlight';" onmouseout="this.className='${row}';"> <td>${contact.username}</td> <td>${contact.sex}</td> <td>${contact.email}</td> <td>${contact.qq}</td> <td>${contact.descn}</td> <td><a href="edit.jsp?id=${contact.id}">修改</a> | <a href="remove.jsp?id=${contact.id}">删除</a></td> </tr> <% } %>
如果能像使用jsp动作(action)一样,使用<jsp:xxx>的形式进行循环该多好啊?可惜jsp动作(action)的功能太少了,它没办法进行循环,我们只好自己实现taglib。
比较一下使用taglib前后jsp中的样子。
<lingirl:for var="contact" items="${list}"> <tr class="${contact_row}" onmouseover="this.className='highlight';" onmouseout="this.className='${contact_row}';"> <td>${contact.username}</td> <td>${contact.sex}</td> <td>${contact.email}</td> <td>${contact.qq}</td> <td>${contact.descn}</td> <td><a href="contact.do?method=edit&id=${contact.id}">修改</a> | <a href="contact.do?method=remove&id=${contact.id}">删除</a></td> </tr> </lingirl:for>
taglib的写法和jsp动作(action)很相似,是由taglib前缀,冒号,标签名三者的组合体。其中taglib前缀是用jsp指令(direction)定义的。
<%@ taglib uri="WEB-INF/tld/lingirl.tld" prefix="lingirl" %>
这里的jsp指令(direction)是专门用来定义标签库的,uri指定tld定义文件的位置,prefix指定对应的taglib前缀。通过这里的定义才能在下面使用taglib。
看看taglib带给了我们什么?
-
items="${list}"表示将对list变量进行循环操作。
-
var="contact"表示循环得到的每个元素对应的变量名。
taglib中循环list,每获得一个数据就通过pageContext.setAttribute("contact", contact);放到pageContext中,接着处理标签中包含的内容,这样标签中间的内容就可以通过${context.username}的形式获得每一行的数据。
了解过如何使用我们的taglib,现在可以看具体实现了,首先我们要编写一个ForTag.java。
-
第一步,让ForTag继承BodyTagSupport。
BodyTagSupport专门用来制作带内容的taglib,它为我们提供了几个好用的方法来处理数据。
-
第二步,为ForTag设置两个自定义参数:var和items。
对应标签中的<lingirl:for var="contact" items="${list}">,我们需要在ForTag中写两个与其名称对应的setter方法。
public void setVar(String var) { this.var = var; } public void setItems(Collection items) { this.iterator = items.iterator(); }
这两个方法会在标签使用的时候,自动获得参数的值,供以后使用。
-
第三步,让ForTag处理标签内容。
public int doStartTag() throws JspException { this.index = 0; if (this.process()) { return EVAL_BODY_INCLUDE; } else { return EVAL_PAGE; } } public int doAfterBody() { if (this.process()) { return EVAL_BODY_AGAIN; } else { return EVAL_PAGE; } }
为了实现循环,我们需要监听两个事件。
doStartTag()方法在标签开始时执行,要记住每次都要对类进行初始化,避免上一次的遗留数据对操作造成影响。然后判断是否有数据需要处理,如果有,则返回EVAL_BODY_INCLUDE开始处理标签里的内容,如果没有,返回 EVAL_PAGE跳过标签内容执行标签下面的内容。
doAfterBody()方法在每次处理完标签内部内容后执行,判断循环是否已经结束,如果可以继续循环,返回EVAL_BODY_AGAIN用循环得到新的数据再次处理标签内部内容,如果循环结束就返回EVAL_PAGE结束标签。
-
第四步,进行循环时的处理。
private boolean process() { if (this.iterator.hasNext()) { String row = this.index % 2 != 0 ? "odd" : "even"; pageContext.setAttribute(var + "_index", this.index); pageContext.setAttribute(var + "_row", row); Object item = this.iterator.next(); pageContext.setAttribute(var, item); this.index++; return true; } else { return false; } }
process()方法在doStartTag()和doAfterBody()中都会用到,它的用途是判断循环是否结束,如果还可以继续循环就返回true,否则返回false。
如果还可以继续循环,则从iterator中循环获得下一个数据,根据var的值放到pageContext中,同时放到pageContext里的还有index索引值和row索引值的奇偶,odd代表奇数行,even代表偶数行。var="contact"的情况下,${contact}表示循环数据,${contact_index}表示索引值,${context_row}表示奇偶性,这些都可以在标签内部的jsp中直接使用。
经过如此一番周折,ForTag可以从标签获得参数,并对数据进行循环处理了。最后一步还要为它编写tld(taglib definition)标签库定义文件,提供给jsp指令(direction)引用。
<?xml version="1.0" encoding="UTF-8"?> <!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>lingirl</short-name> <uri>http://www.family168.com/lingirl</uri> <tag> <name>for</name> <tag-class>anni.ForTag</tag-class> <attribute> <name>var</name> <required>true</required> <rtexprvalue>true</rtexprvalue> <type>java.lang.String</type> </attribute> <attribute> <name>items</name> <required>true</required> <rtexprvalue>true</rtexprvalue> <type>java.util.Collection</type> </attribute> </tag> </taglib>
前面一大堆复杂难懂的标签指定我们使用taglib规范的版本,进入tag部分才开始定义名字为for的标签,使用tag-class指定对应的类,再定义两个参数:var和items。required说明参数不能省略必须手工设置。rtexprvalue表示参数部分可以使用el,否则就只能用字符串。type对应的是类中使用的真实类型,taglib会根据它做类型转换。
全部的例子在09-01目录下,注意编译taglib需要将jsp-api.jar加入classpath,参考WEB-INF/src/compile.bat。
结果,为了替换4,5行java代码,我们需要编写一个ForTag.java,一个对应tld文件,在jsp中引用tld,最后才能使用ForTag对list进行循环。不得不说一句:“太麻烦啦。”
taglib太笨重,也太复杂了。编写一个taglib花费的力气太大,又不容易修改或扩展。一般情况下,taglib都是由别人写好,我们再直接调用。sun就为标签库定义了一套标准,叫做jstl(java standard taglib)java标准标签库,可以去http://jakarta.apache.org/taglibs/index.html下载apache实现的jstl。
想在项目里使用jstl,首先要把jstl.jar和standard.jar两个文件放到/WEB-INF/lib/目录下。
然后在list.jsp中加入jsp指令(direction)引用jstl中定义的标签库。
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
这里的uri是固定写法,只要写成这个就可以使用jstl了,jstl中包含多个标签库,这里我们只用到core。
经过上述配置,现在可以使用jstl了,代码如下:
<c:forEach var="contact" items="${list}" varStatus="status"> <c:set var="row" value="${status.index % 2 != 0 ? 'odd' : 'even'}"/> <tr class="${row}" onmouseover="this.className='highlight';" onmouseout="this.className='${row}';"> <td>${contact.username}</td> <td>${contact.sex}</td> <td>${contact.email}</td> <td>${contact.qq}</td> <td>${contact.descn}</td> <td><a href="contact.do?method=edit&id=${contact.id}">修改</a> | <a href="contact.do?method=remove&id=${contact.id}">删除</a></td> </tr> </c:forEach>
这里使用的是c:forEach,它也是一个执行循环的标签,var和items参数的意义与上边谈到的lingirl:for标签已知,分别代表循环变量和循环数据。唯一不同的是多了一个varStatus参数,这个参数表示当前行的状态,其中status.index表示当前行的序号,我们就通过序号计算奇偶行。
在c:forEach标签中,我们还看到一个c:set标签,它的作用是可以将指定的变量保存到作用域中,默认作用域是page,这里我们使用status.index计算出行的奇偶性,然后保存到row中,后面就可以直接使用${row}调用了。
jstl中的c:forEach不但可以处理Collection,还可以处理数组和Map,使用jstl我们更容易写出结构一致的代码,以初学jsp来说,自定义taglib还是太复杂了,所以还是先学习一些常用的jstl为好。
例子在lingo-sample/09-02下,其中只有list.jsp中使用了jstl。
No comments:
Post a Comment