用jsp/servlet开发 Web应用,最大的问题是如何让JSP文件中的脚本语言尽可能的少。我们现在广泛采用的jsp/servlet/javabean模式,虽然保证了在 jsp中不会出现大量的商业逻辑代码和数据库的访问,但是不可避免的是仍然会有大量的循环、条件等流程控制语句,降低了代码的可读性,不利于页面设计人员的修改。
为此,JSP1.1提供了javax.servlet.jsp.tagext包,允许我们定制自己的标签。
下面我们用四个实例,一步步阐明它的具体使用。
首先从hello world开始。
在jsp中我们用<sharetop:helloworld />代替<% out.println("hello world")%>
为了让JSP引擎能够解析我们上面写的标签,我们需要在jsp文件中引入我们的标签库,如下句:
<%@ taglib uri="/hello" prefix="sharetop" %>
同时修改web.xml文件,加上:
<taglib>
<taglib-uri>/hello</taglib-uri>
<taglib-location>/WEB-INF/tlds/hello.tld</taglib-location>
</taglib>
这样,JSP引擎就知道到哪里去找我们的标签定义声明了。然后开始编写我们自己的标签库定义文件 hello.tld
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>sharetop</shortname>
<info />
<tag>
...
</tag>
</taglib>
这就是最基本的tld文件格式,也是基于XML的,各个标签什么意思我就不多说了。我们主要操作的是其中的<tag>标签内容。
比如上例,我们建立如下<tag>段:
<tag>
<name>helloworld</name>
<tagclass>com.sharetop.tagext.bbb.HelloWorldTag</tagclass>
<bodycontent>JSP</bodycontent>
<info/>
</tag>
很明显,到这步我们就把定义的标签与将要用于解析这个标签的类关联上了。下面我们要做的事就是实现这个HelloWorldTag类。JSP引擎对自定义标签的解析是采用SAX方式的。所以,我们要做的事就是实现一些接口定义的回调函数。
在javax.servlet.jsp.tagext包里有两个接口:Tag 和 BodyTag,从字面上可以看出它们是分别解析标签和标签体的。在接口Tag中定义了两个方法:
public int doStartTag() throws JspTagException; //标签开始解析触发此事件
public int doEndTag() throws JspTagException; //标签结束解析触发此事件
BodyTag接口继承了Tag,新增了两个方法:
public int doInitBody() throws JspTagException;//准备解析标签体触发此事件
public int doAfterBody() throws JspTagException;//完成标签体解析触发此事件
注意这几个方法的返回值都是整数,JSP引擎需要根据返回值决定下一步的操作。
在Tag接口定义的返回常量是EVAL_BODY_INCLUDE、 EVAL_PAGE、 SKIP_BODY和 SKIP_PAGE 。而 BodyTag又新增了一个常量EVAL_BODY_TAG,要注意的是如果在BodyTag中的doStartTag()方法是不能返回 EVAL_BODY_INCLUDE的,只能返回EVAL_BODY_TAG,并且也只有BodyTag的doStartTag()方法可以返回 EVAL_BODY_TAG。
我们不需要直接实现这两个接口(主要是太麻烦),可以继承tagext包为我们提供的两个类:TagSupport 和 BodyTagSupport。
上面HelloWorldTag的代码如下:
package com.sharetop.tagext.bbb;
import java.io.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class HelloWorldTag extends TagSupport {
public HelloWorldTag() { }
public int doEndTag() throws JspTagException {
try{
pageContext.getOut().println("hello world");
}
catch(Exception ex){
//写失败
throw new JspTagException("can't write");
}
//继续分析页面其它部分
return super.EVAL_PAGE;
}
}
我们的处理很简单,就是用pageContext得到一个JspWriter,输出一句hello world。
注意:pageContext很重要,是标签处理类与jsp页面交互的主要工具。它被定义在TagSupport类里是protected类型的。
第二个例子。如果我们的有属性值怎么办?
比如这句:<sharetop:hellofriend name="superfeel">,我们希望输出是hello superfeel.
好,同样在jsp页引入标签库的定义文件(以下同),在tld文件中加入新的<tag>定义:
<tag>
<name>hellofriend</name>
<tagclass>com.sharetop.tagext.bbb.HelloFriendTag</tagclass>
<bodycontent>JSP</bodycontent>
<info />
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>sex</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
上面我们定义了两个属性,name和sex,其中sex是可有可无的(因为下面的例子要用,所以先定义在这里)。
然后就是实现文件HelloFriendTag的代码如下(限于篇幅,后面提供的代码都省略包和import语句):
public class HelloFriendTag extends TagSupport {
public HelloFriendTag() { }
public int doStartTag() throws JspTagException {
try{
pageContext.getOut().println("Hello "+name);
}
catch(Exception ex){
throw new JspTagException(ex.getMessage());
}
return super.EVAL_PAGE;
}
/**
*成员属性即为标签的属性
*/
public void setName(String name) { this.name = name; }
public String getName() { return name; }
public void setSex(String sex) { this.sex = sex; }
public String getSex() { return sex; }
private String name;
private String sex;
}
其实很简单,类的成员属性就是标签的属性,所以你只需为这个HelloFriendTag定义两个同名的属性即可。
好,到这里我们了解了扩展标签的基本实现。下一篇我们将讨论标签体、循环和标签嵌套的内容。