JavaEE jsp自定义标签

来源:互联网 发布:机蜜租机划算么 知乎 编辑:程序博客网 时间:2024/06/10 01:42

通过使用jsp的自定义标签,可以在简单的标签中封装复杂的功能,可以用以取代jsp脚本。

开发jsp2自定义标签的步骤如下:
- 定义标签处理类;
- 定义*.tld文件;
- 在jsp页面中使用自定义标签。

简单标签定义

说到标签,可以联想到标签的属性和标签体。为了重点展示自定义标签的编写,我们先定义一个最简单的标签,即无属性无标签体的标签。这个简单标签将显示当前的日期和时间。

定义标签处理类

就像一开始所说的那样,我们需要先定义标签处理类,这个类需要继承自javax.servlet.jsp.tagext.SimpleTagSupport,并重写doTag方法:

// DateTimeTag.javapublic class DateTimeTag extends SimpleTagSupport {    private static final SimpleDateFormat sdf=            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");    public void doTag() throws JspException, IOException {        super.doTag();        getJspContext().getOut().write(sdf.format(new Date()));    }}

DateTimeTag类很简单,它继承自SimpleTagSupport,并重写了doTag方法。doTag方法中获取了jsp页面上下文的输出流来输出当前的日期和时间。

定义TLD文件

接着,我们需要定义*.tld文件。TLD是Tag Library Definition的缩写,即标签库定义。每个TLD文件对应一个标签库,标签库中可以定义多个标签。TLD文件需要放在WEB-INF目录或其任意子目录下,这里我们将其放在WEB-INF/jsp2目录下。

下面我们在eclipse中创建一个TLD文件。先在WEB-INF目录下创建jsp2目录,再右击jsp2目录选择New-Other-XML-XML File,输入文件名(我们输入mytaglib.tld),Next,选择Creat XML file from a DTD fileNext,选择Select XML Catalog entry,在出现的XML文本选择下拉框中选择-//Sun Microsystems,Inc.//DTD JSP Tag Library 1.2//ENNextFinish

在新创建的TLD文件中可以看到,它的根元素是taglib,里面可以包含多个tag子元素,我们就是通过tag元素来定义标签的。下面我们在mytaglib.tld文件中定义第一个标签:

<!-- mytaglib.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>tlib-version</tlib-version>  <jsp-version>jsp-version</jsp-version>  <short-name>short-name</short-name>  <uri>http://zzw.com/mytaglib</uri>  <tag>    <name>datetime</name>    <tag-class>com.zzw.DateTimeTag</tag-class>    <body-content>empty</body-content>  </tag></taglib>

taglib元素中有一个uri元素,这个元素指定标签库的URI,我们在使用标签库时就依赖于此元素。在tag元素中,name元素指定标签的名字,我们在使用标签时需要指定标签名;tag-class元素指定标签的处理类;body-content元素和标签体有关,它可以是下面几个值:
- empty:表示没有标签体;
- JSP:表示标签体可以包含JSP代码;
- scriptless:表示标签体可以包含EL表达式和JSP动作元素,但不能包含JSP的脚本元素;
- tagdependent:表示标签体交由标签本身去解析处理,即在标签体中所写的任何代码都会原封不动地传给标签处理器;
- dynamic-attributes:指定该标签是否支持动态属性,只有当定义动态属性标签时才需要指定该值,关于动态属性标签将在后面介绍。

这里我们只需要指定body-content>元素值为empty即可。

在jsp页面中使用自定义标签

接下来我们就可以在jsp页面中使用自定义标签了:

<!-- mytag.jsp --><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><%@ page language="java" contentType="text/html; charset=GBK"    pageEncoding="UTF-8"%><%@ taglib uri="http://zzw.com/mytaglib" prefix="mytag"%><meta http-equiv="Content-Type" content="text/html; charset=GBK"><title>自定义标签</title></head><body>    当前时间<mytag:datetime/><hr></body></html>

可以看到,要使用自定义标签,需要先导入自定义标签库,这可以借助jsp编译指令taglib完成。在taglib中需要指定自定义标签库的URI和标签库前缀,URI即在TLD文件中的taglib元素的子元素uri,前缀指定在此标签库中定义的所有标签的前缀。

使用自定义标签的语法格式为:

<prefix:tagname attribute="expression" ...>    <tag-body></prefix:tagname>

由于datetime标签没有属性,也不需要标签体,我们可以简单写成<mytag:datetime/>,前缀mytag表示此标签属于URI为"http://zzw.com/mytaglib"的标签库。

运行效果如下:
datetimetag

看,我们只在jsp页面中使用了一个简单的标签,就可以显示出当前时间,是不是要比jsp脚本方便多了。看到这,相信你也基本了解了自定义标签的开发,不过这只是最简单的标签,下面我们将介绍自定义标签的更多内容。

带属性的标签

上面我们介绍的简单标签没有任何属性,但通常情况下大多数标签都有一个或多个属性,现在我们就介绍带属性标签的开发。

基本步骤和之前一样,我们先编写标签处理类:

// EmailTag.javapublic class EmailTag extends SimpleTagSupport {    private String from;    private String to;    private Date date;    private String message;    private static final SimpleDateFormat sdf=            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");    public String getFrom() { return from; }    public String getTo() { return to; }    public Date getDate() { return date; }    public String getMessage() { return message; }    public void setFrom(String f) { from=f; }    public void setTo(String t) { to=t; }    public void setDate(Date d) { date=d; }    public void setMessage(String m) { message=m; }    public void doTag() throws JspException, IOException {        super.doTag();        JspWriter out=getJspContext().getOut();        out.write("<div style=\"padding:10px;border:1px solid;border-color:#ff7f00;color:#ff7f00\">");        out.write("<b>FROM</b>: "+from+"<br>");        out.write("<b>TO</b>&nbsp;&nbsp: "+to+"<br>");        out.write("<b>DATE</b>: "+sdf.format(date)+"<br>");        out.write("<p>"+message+"</p>");        out.write("</div>");    }}

EmailTag类和DatetimeTag类的不同之处在于,它多了几个“属性”,而且这些“属性”都有对应的get和set方法。实际上jsp页面正是通过get/set方法来获取/设置标签的属性的。

我们在mytaglib.tld文件中添加这个新标签:

<!-- mytaglib.tld -->  <tag>    <name>email</name>    <tag-class>com.zzw.EmailTag</tag-class>    <body-content>empty</body-content>    <attribute>      <name>from</name>      <required>true</required>      <rtexprvalue>true</rtexprvalue>      <type>java.lang.String</type>    </attribute>    <attribute>      <name>to</name>      <required>true</required>      <rtexprvalue>true</rtexprvalue>      <type>java.lang.String</type>    </attribute>    <attribute>      <name>date</name>      <required>true</required>      <rtexprvalue>true</rtexprvalue>      <type>java.util.Date</type>    </attribute>    <attribute>      <name>message</name>      <required>true</required>      <rtexprvalue>true</rtexprvalue>      <type>java.lang.String</type>    </attribute>  </tag>

可以看到,这个新的tag元素多了几个attribute子元素,我们正是通过attribute元素来设置标签的属性的。attribute元素又有如下几个子元素:
- name:指定属性名;
- required:指定此属性是否是必要的;
- rtexprvalue:全称是Run-time Expression Value,它表示是否可以使用JSP表达式;
- type:指定此属性的类型。

现在我们就可以使用这个新标签了:

<!-- mytag.jsp -->发送邮件<mytag:email date="<%=new Date()%>" message="hello, client" to="client" from="server"/>

注意,由于我们之前已经通过taglib指令导入了标签库,因此才能直接使用此标签;否则需要先导入标签库。

运行一下,发现出现了一个异常:
jsp_version_exception

大致是说jsp-version在转换成double类型时发生转换错误。检查我们的代码,发现只有在mytaglib.tld文件的taglib元素中出现了jsp-version

<jsp-version>jsp-version</jsp-version>

jsp-version修改为2.0,再次运行时可以正常显示:
emailtag

如果修改jsp-version后还是不能正常运行,可以将%TOMCAT_HOME%\webapps\examples\WEB-INF\jsp2目录下的任意TLD文件中的taglib元素拷贝到自己的TLD文件中,如果使用eclipse则会报TLD文件有语法错误,不过不用管它,照样可以成功运行。

带标签体的标签

有些标签除了具有属性外,还有标签体,我们也可以开发具有标签体的标签。

我们定义一个迭代器标签,它会依次输出容器中的元素。还是先定义标签处理类:

// IteratorTag.javapublic class IteratorTag extends SimpleTagSupport {    private String collection;    private String item;    public String getCollection() { return collection; }    public String getItem() { return item; }    public void setCollection(String c) { collection=c; }    public void setItem(String i) { item=i; }    public void doTag() throws JspException, IOException {        super.doTag();        // 从当前jsp页面上下文获取属性        Collection<?> items=(Collection<?>) getJspContext().getAttribute(collection);        for(Object i : items) {            // 给当前jsp页面上下文设置属性            getJspContext().setAttribute(item, i);            // 输出标签体            getJspBody().invoke(null);        }    }}

doTag方法中有一句getIspBody().invoke(null);,它负责输出标签体。通常标签体是一系列EL表达式或jsp动作指令,我们不打算在这里详细介绍EL表达式,有关EL表达式的内容可以参考这两篇文章:《EL表达式(详解)》和《EL表达式简介》。

然后在TLD文件中定义标签:

<!-- mytaglib.tld -->  <tag>    <name>iterator</name>    <tag-class>com.zzw.IteratorTag</tag-class>    <body-content>scriptless</body-content>    <attribute>      <name>collection</name>      <required>true</required>      <rtexprvalue>true</rtexprvalue>      <type>java.lang.String</type>    </attribute>    <attribute>      <name>item</name>      <required>true</required>      <rtexprvalue>true</rtexprvalue>      <type>java.lang.String</type>    </attribute>  </tag>

注意这里的body-content元素值为scriptless,表示标签体可以包含EL表达式和JSP动作元素,但不能包含JSP的脚本元素。

最后在jsp页面中使用此标签:

<!-- mytag.jsp --><%    ArrayList<String> list=new ArrayList<String>();    for(int i=0; i<10; i++)        list.add("item "+i);    pageContext.setAttribute("list", list);%>    迭代器<br><mytag:iterator item="item" collection="list">        ${pageScope.item}<br>    </mytag:iterator>

我们先用jsp脚本将容器对象添加到page作用域中,IteratorTag类才能取得此容器对象,然后在doTag方法中又将此容器对象中的元素添加到page作用域中,然后标签体${pageScope.item}(这是一个EL表达式)负责取得元素对象引用,最后getJspBody().invoke(null);语句输出此元素对象。

结果如下:
iteratotag

说到这,我们已经介绍了简单标签、带属性标签和带标签体标签的开发,下面我们再介绍几种特殊的自定义标签。

属性为页面片段的标签

之前我们已经介绍了带属性标签的开发,其实标签的属性也可以是一个页面片段。这种标签的处理类中必须有一个类型为javax.servlet.jsp.tagext.JspFragment的属性,我们将一开始的datetime标签作为片段来进行带属性标签的开发。

定义标签处理类:

// FragmentTag.javapublic class FragmentTag extends SimpleTagSupport {    private JspFragment fragment;    public JspFragment getFragment() { return fragment; }    public void setFragment(JspFragment f) { fragment=f; }    public void doTag() throws JspException, IOException {        super.doTag();        JspWriter out=getJspContext().getOut();        out.println("<div style=\"padding:10px;border:1px solid;border-color:#000000\">");        fragment.invoke(null);        out.println("</div>");    }}

FragmentTag类也遵循带属性标签处理类的编写规范,只不过它的“属性”类型为JspFragment。在doTag方法中fragment.invoke(null);语句负责输出包含的页面片段。

然后在TLD文件中定义此标签:

<!-- mytaglib.tld -->  <tag>    <name>fragment</name>    <tag-class>com.zzw.FragmentTag</tag-class>    <body-content>empty</body-content>    <attribute>      <name>fragment</name>      <required>true</required>      <rtexprvalue>true</rtexprvalue>      <fragment>true</fragment>    </attribute>  </tag>

注意到,attribute元素中多了一条子元素fragment,如果使用eclipse则会报错,我们依然不用管它。如果不设置fragment元素或设置fragment元素为false,则运行时会发生异常:
fragment_exception

我们在jsp页面中使用此标签:

<!-- mytag.jsp -->    页面片段<mytag:fragment>              <jsp:attribute name="fragment">                  <mytag:datetime/>              </jsp:attribute>          </mytag:fragment>

我们在标签体中使用jsp:attribute动作指定了fragment标签的属性,注意在TLD文件中fragment标签的body-content元素值为empty

运行结果如下:
fragmenttag

动态属性标签

有时,我们不能确定标签有多少个属性或者我们不知道属性的名字,这种情况下可以使用动态属性标签。动态属性标签的处理类必须实现javax.servlet.jsp.tagext.DynamicAttributes接口,并重写setDynamicAttribute方法。

我们定义一个简单的动态属性标签,并在jsp页面中显示此标签的属性。

标签处理类:

// DynamicTag.javapublic class DynamicTag extends SimpleTagSupport implements DynamicAttributes {    private HashMap<String, Object> attrs=new HashMap<String, Object>();    public void doTag() throws JspException, IOException {        super.doTag();        if(attrs!=null && !attrs.isEmpty()) {            JspWriter out=getJspContext().getOut();            out.println("<ol>");            Set<String> keys=attrs.keySet();            Iterator<String> iter=keys.iterator();            while(iter.hasNext()) {                String key=iter.next();                out.println("<li>"+key+"="+attrs.get(key)+"</li>");            }            out.println("</ol>");        }    }    @Override    public void setDynamicAttribute(String uri, String localName, Object value) throws JspException {        attrs.put(localName, value);    }}

可以看到,我们通过setDynamicAttribute方法来动态添加标签的属性。

在TLD文件中定义此标签:

<!-- mytaglib.tld -->  <tag>    <name>dynamic</name>    <tag-class>com.zzw.DynamicTag</tag-class>    <body-content>empty</body-content>    <dynamic-attributes>true</dynamic-attributes>  </tag>

此标签多了一个dynamic-attributes元素,它指定此标签是否是动态属性标签。同样的,在eclipse中设置此元素会报错,但我们同样忽略它。如果不设置此元素或设置此元素值为false,则在运行时会发生异常:
dynamic_attributes_exception

在jsp页面中使用此标签:

<!-- mytag.jsp -->动态属性<mytag:dynamic user="zzw" password="******" action="signin"/>

运行结果如下:
dynamictag

Tag File

Tag File是自定义标签的简化用法,使用Tag File无需定义标签处理类和TLD文件,但仍然可以在jsp页面中使用自定义标签。下面我们创建一个Tag File,实现对图的迭代。

创建Tag File

Tag File的命名必须以.tag结尾,文件名就是此标签的标签名。该文件需要放在web应用的某个目录下,通常放在WEB-INF/tags目录中,这个路径相当于标签库的URI,在jsp页面的taglib指令中用tagdir属性指定。

在eclipse中,先在WEB-INF目录下创建tags目录,再右击tags目录选择New-Other-Web-JSP Tag,输入文件名(我们输入iterator.tag),在出现的模板选择框中选择New Tag FileFinish

<!-- iterator.tag --><%@ tag language='java' pageEncoding='GBK' body-content="scriptless"%><%@ tag import="java.util.*" %><%@ attribute name="bgcolor" required="true"%><%@ attribute name="cellcolor" required="true"%><%@ attribute name="collection" required="true" type="java.util.HashMap"%><%@ variable name-given="end" variable-class="java.util.Date" scope="AT_END"%><%    Set<?> keys=collection.keySet();    Iterator<?> iter=keys.iterator();%>开始迭代&nbsp;<%=new Date()%><br><jsp:doBody/><table bgcolor="${bgcolor}" border="1"><%    while(iter.hasNext()) {        String key=(String) iter.next();%>    <tr bgcolor="${cellcolor}">        <td><%=key%></td>        <td><%=collection.get(key)%></td>    </tr><%}%></table><%    jspContext.setAttribute("end", new Date());%>

Tag File的语法和jsp很相似,它具有以下编译指令:
- taglib:作用类似于jsp页面中的taglib指令,用于导入其他标签库;
- include:作用类似于jsp页面中的include指令,用于包含其他jsp页面或静态页面;
- tag:作用类似于jsp页面中的page指令,用于设置语言、编码等;
- attribute:用于设置标签属性,对应于TLD文件中tag元素的attribute子元素;
- variable:用于设置标签变量,这些变量将传给jsp页面使用。

在Tag File中也可以使用jsp动作指令和编写静态文本,我们使用了jsp:doBody动作指令来输出标签体。

在Tag File中还可以定义变量供jsp页面使用,此功能可借助variable指令完成。variable指令有如下属性:
- name-given:指定变量名;
- variable-class:指定变量类型,默认是java.lang.String
- scope:指定变量的作用域,有如下3个值:
- AT_BEGIN:在标签开始使用后的任何地方都可以使用此变量;
- NESTED:只能在标签体中使用此变量;
- AT_END:只能在标签结束后使用此变量。

下面在jsp页面中使用此tag:

<!-- mytag.jsp --><%@ taglib tagdir="/WEB-INF/tags" prefix="tags"%><%    HashMap<String, Object> map=new HashMap<String, Object>();    map.put("name", "张三");    map.put("old", 22);    map.put("sex", "male");    map.put("date", new Date());%>    TAG迭代器<br>    <tags:iterator bgcolor="#7f00ff" cellcolor="#ffff00" collection="<%=map%>">        键值对为:    </tags:iterator>    结束迭代&nbsp;<%=end%>

mytag.jsp文件中,使用taglib指令导入了Tag File,tagdir属性指定了Tag File的路径,prefix和导入自定义标签库时具有相同的意义。另外在使用tag时,prefix后跟着的是Tag File的名字。

运行效果如下:
tagiterator

Tag File的实质

就如同jsp的实质是Servlet一样,Tag File的实质就是标签处理类,它最后会被处理成一个xxx_tag.java文件,并被编译成一个xxx_tag.class文件。

%ECLIPSE_WORKSPACE%\.metadata\.plugins\org.eclipse.wst.server.core\tmpX\work\Catalina\localhost\<PROJECT-NAME>\org\apache\jsp\tag\web目录下可以找到若干xxx_tag.java和相应的CLASS文件。查看上述Tag File对应的iterator_tag.java文件:

// iterator_tag.javapublic final class iterator_tag    extends javax.servlet.jsp.tagext.SimpleTagSupport    implements org.apache.jasper.runtime.JspSourceDependent,                 org.apache.jasper.runtime.JspSourceImports {  // jsp页面上下文  private javax.servlet.jsp.JspContext jspContext;  // 在Tag File中定义的属性  private java.lang.String bgcolor;  private java.lang.String cellcolor;  private java.util.HashMap collection;  // bgcolor、cellcolor和collection的get/set方法类似  public java.lang.String getBgcolor() {    return this.bgcolor;  }  public void setBgcolor(java.lang.String bgcolor) {    this.bgcolor = bgcolor;    jspContext.setAttribute("bgcolor", bgcolor);  }  // 在Tag File中的jsp脚本都会在doTag()方法中被转换为静态文本输出  public void doTag() throws javax.servlet.jsp.JspException, java.io.IOException {    javax.servlet.jsp.PageContext _jspx_page_context = (javax.servlet.jsp.PageContext)jspContext;    javax.servlet.http.HttpServletRequest request = (javax.servlet.http.HttpServletRequest) _jspx_page_context.getRequest();    javax.servlet.http.HttpServletResponse response = (javax.servlet.http.HttpServletResponse) _jspx_page_context.getResponse();    javax.servlet.http.HttpSession session = _jspx_page_context.getSession();    javax.servlet.ServletContext application = _jspx_page_context.getServletContext();    javax.servlet.ServletConfig config = _jspx_page_context.getServletConfig();    javax.servlet.jsp.JspWriter out = jspContext.getOut();    // ...    try {      // 和带标签体标签一样,都是在doTag()方法中调用getJspBody().invoke(null);      _jspx_sout = null;      if (getJspBody() != null)        getJspBody().invoke(_jspx_sout);      // ...    } catch( java.lang.Throwable t ) {      // ...    } finally {      // ...    }  }}

我们在iterator_tag类中找到了三个数据成员bgcolorcellcolorcollection,它们都有get/set方法。实际上这三个数据成员就是我们在Tag File中定义的属性,可见我们在Tag File中定义的属性都会成为相应标签处理类的数据成员。

我们还发现,和jsp转换成Servlet一样,在doTag方法中也定义了一系列内置对象,它们和tag类的数据成员jspContext都能在Tag File中直接使用:

对象 类型 表示 jspContext javax.servlet.jsp.JspContext jsp页面的上下文 request javax.servlet.http.HttpServletRequest 一次客户端请求 response javax.servlet.http.HttpServletResponse 一次服务器响应 session javax.servlet.http.HttpSession 一次会话 application javax.servlet.ServletContext 整个web应用 config javax.servlet.ServletConfig 应用配置 out javax.servlet.jsp.JspWriter 输出流

关于这几个对象的使用可以参考我的另一篇文章:《JavaEE jsp的内置对象及其在Servlet中的表示》。

源代码

上述所有代码已经上传到github:
https://github.com/jzyhywxz/WebDemo.git

0 0
原创粉丝点击