JSP自定义标签第一篇之分析Struts2的标签

1、自定义标签的作用

在后端程序开发过程中,会把一些比较经常使用到的代码封装起来,重复利用加快开发的速度和减少错误,有bug时可以一次修改其他引用位置自动修复。前端Web页面上,如果我经常用到一些固定格式的显示功能,并涉及到服务端逻辑操作的时候,就需要使用到自定义标签,自定义标签使我们可以很方便的去调用一段共同的代码。对于Spring MVC及Struts2等框架都定义了自己的JSP页面标签,这样就可以通过标签的复用,实现一些固定的功能,比如struts2的<s:property value=“message”/>标签,可以使用标签读取到action中的属性以及session、request等域中的属性值,下面我们就从分析property标签开始,认识java中针对自定义标签的定义过程。

2、struts2中property标签的类层次结构分析

struts2中property标签的类层次结构
通过上面的类层次图可以看出,property标签的顶层父类是TagSupport该类的包为javax.servlet.jsp.tagext,通过观察整个层次图中类的包名可以看出TagSupport类和BodyTagSupport这两个类是Java的JDK中定义的,而其他三个类是struts2框架中定义的,所以如果要实现自定义标签需要从TagSupport类或BodyTagSupport类进行继承,当然也可以直接继承各框架中定义的从TagSupport类和BodyTagSupport扩展的子类,但是这会造成对于具体框架如struts2的依赖,如无必要最好是从上述TagSupport类和BodyTagSupport类中扩展。

3、TagSupport类和BodyTagSupport类分析

TagSupport与BodyTagSupport的区别主要是标签处理类是否需要与标签体交互,如果不需要交互的就用TagSupport,否则就用BodyTagSupport。交互就是标签处理类是否要读取标签体的内容和改变标签体返回的内容。用TagSupport实现的标签,都可以用BodyTagSupport来实现,因为BodyTagSupport继承了TagSupport。
标签处理类:就是指从TagSupport类或者其子类派生出的子类。比如图中的PropertyTag类就是一个标签处理类。
标签体:也就是我们开篇是说的标签如<s:property value=“message”/>标签,它具体的定义需要在WEB-INF下建立tld文件tag.tld。对于tag.tld的定义格式可以参考struts2的标签tld定义文件。我使用的是struts21.3.37版本,具体tld文件在struts2-core-2.3.37.jar包中的META-INF中名称为struts2-tags.tld的文件,具体代码如下:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd">
  <description><![CDATA['To make it easier to access dynamic data the Apache Struts framework includes a library of custom tags. The tags interact with the framework validation and internationalization features to ensure that input is correct and output is localized. The Struts Tags can be used with JSP FreeMarker or Velocity.']]></description>
  <display-name>Struts Tags</display-name>
  <tlib-version>2.3</tlib-version>
  <short-name>s</short-name>
  <uri>/struts-tags</uri>
  <tag>
    <description><![CDATA[Print out expression which evaluates against the stack]]></description>
    <name>property</name>
    <tag-class>org.apache.struts2.views.jsp.PropertyTag</tag-class>
    <body-content>empty</body-content>
    <attribute>
      <description><![CDATA[The default value to be used if <u>value</u> attribute is null]]></description>
      <name>default</name>
      <required>false</required>
      <rtexprvalue>false</rtexprvalue>
    </attribute>
    <attribute>
      <description><![CDATA[Deprecated. Use 'escapeHtml'. Whether to escape HTML]]></description>
      <name>escape</name>
      <required>false</required>
      <rtexprvalue>false</rtexprvalue>
    </attribute>
    <attribute>
      <description><![CDATA[Whether to escape CSV (useful to escape a value for a column)]]></description>
      <name>escapeCsv</name>
      <required>false</required>
      <rtexprvalue>false</rtexprvalue>
    </attribute>
    <attribute>
      <description><![CDATA[Whether to escape HTML]]></description>
      <name>escapeHtml</name>
      <required>false</required>
      <rtexprvalue>false</rtexprvalue>
    </attribute>
    <attribute>
      <description><![CDATA[Whether to escape Javascript]]></description>
      <name>escapeJavaScript</name>
      <required>false</required>
      <rtexprvalue>false</rtexprvalue>
    </attribute>
    <attribute>
      <description><![CDATA[Whether to escape XML]]></description>
      <name>escapeXml</name>
      <required>false</required>
      <rtexprvalue>false</rtexprvalue>
    </attribute>
    <attribute>
      <description><![CDATA[Value to be displayed]]></description>
      <name>value</name>
      <required>false</required>
      <rtexprvalue>false</rtexprvalue>
    </attribute>
    <dynamic-attributes>false</dynamic-attributes>
  </tag>
  </taglib>

在struts2-tags.tld定义了很多标签也就是在<tag></tag>标记中进行定义的,由于篇幅太长我只把property把标签的定义贴出来了,一个tld文件(标签库文件)可以定义任意多个tag,每个tag的定义使用一个<tag></tag>进行定义。

3.1、标签库(tld文件)的定义

下面介绍一下tld文件(标签库文件)的格式定义(注意一个tld文件对应的就是一个标签库,具体的代码定义可以参照上面struts2-tags.tld文件的定义格式):

1.在taglib标记中引入命名空间(xmlns)
<taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd">
2. description标记 定义标签库的描述,注意要在<![CDATA[内容]]>标记中进行描述;
3.display-name标记 定义当前标签库的显示名称,在struts2-tags.tld文件中定义的是Struts Tags;
4.tlib-version标记 定义当前标签库的版本,在struts2-tags.tld文件中定义的是2.3;
5.short-name标记 定义当前标签库在引用时可使用的前缀,在struts2-tags.tld文件中定义的是s ;
6.uri标记 定义当前标签库在jsp页面引用时需要制定的uri属性,在struts2-tags.tld文件中定义的是/struts-tags;
上面6个参数定义好后,特别是后两个参数在JSP页面引用标签库时具体配置如下,前缀和uri标记必须与tld文件中定义的一致:

<%@ taglib prefix="s" uri="/struts-tags" %>

在jsp页面页头引入<%@ taglib prefix="s" uri="/struts-tags" %>标签库后,在页面中就可以通过使用<s:property value="message"/>标签

3.2、标签(tag)的定义

上面定义好了<taglib>标记,但是还不能使用,需要在<taglib>标记中定义具体的<tag>标记,每个<tag>标记对应一个标签,<taglib>标记中包含四个元素:

  1. <description>标签的描述,注意要在<![CDATA[内容]]>标记中进行描述;

  2. <name> 标签的名称,比如上述代码中定义的property;

  3. <tag-class> 标签的处理类,注意要写含包路径的全类名,上述代码中写的是org.apache.struts2.views.jsp.PropertyTag。这里引入本章开篇所说的标签处理类也就是TagSupport类或者其子类派生出的子类

  4. <body-content>body-content的值有下面4种:
    tagdependent:标签体内容直接被写入BodyContent,由自定义标签类来进行处理,而不被JSP容器解释,如下:

           <test:myList>
                 select name,age from users
           </test:myList>
    

    JSP:接受所有JSP语法,如定制的或内部的tag、scripts、静态HTML、脚本元素、JSP指令和动作。如:

          <my:test>
    
               <%=request.getProtocol()%>    // ②
        
          </my:test>
    

    empty:空标记,即起始标记和结束标记之间没有内容。下面几种写法都是有效的,

           <test:mytag />
    
           <test:mytag uname="Tom" />
    
          <test:mytag></test:mytag>
    

    scriptless:接受文本、EL和JSP动作。如上述②使用<body-content> scriptless </body-content>则报错。
    通过以上四个值的描述,就可以清晰的理解了body-content元素的含义了,就是指标签开始和结束符号之间可以接受的值,如<s:property value="message">这个位置不可以写值,因为是定义的empty</s:property>

  5. <attribute>标签的特性,先看<s:property value=“message”>标签的一个例子:

<attribute>
      <description><![CDATA[Value to be displayed]]></description>
      <name>value</name>
      <required>false</required>
      <rtexprvalue>false</rtexprvalue>
</attribute>

上面的代码是<s:property/>标签的一个特性,<description>元素和前面说的是一样的,故名思议描述这个特性。<name>元素定义这个特性的名称,这个名字不能谁便写,而是要与标签处理类中的属性值一致,也就是说在<s:property/>标签的处理类中有一个value字段,它必须有一个setValue()方法,也就是一个Setter,这个是强制规范,或者反过来说在一个标签处理类中有几个需要标签体向处理类传入值的Setter就必须在<tag>标记中定义几个 <attribute><required>元素指定当前属性是否是必须的,如果指定为true那么这个属性必须在标签中进行设置。如<s:property/>标签的value属性被指定为<required>false</required>则,<s:property/>标签使用时必须是这种形式<s:property value="具体的值"/>。关于<rtexprvalue>元素解释:rtexprvalue的全称是 Run-time Expression Value, 它用于表示是否可以使用JSP表达式,当在标签里指定true时, 表示该自定义标签的某属性的值可以直接指定或者通过动态计算指定,如<myTag:cupSize cupSize="1" cupSizes="${result}"></myTag:cupSize>

通过以上的介绍,就可以完整的定义一个标签库(tld)文件了,虽然定义了tld文件但是标签还不能使用,因为标签的处理类还没有定义,具体从TagSupport类或者其子类派生出标签处理类的方法下一节进行讲述。

3.3 标签处理类的定义

3.3.1 TagSupport类的类图

TagSupport类的类图描述
可以看出来TagSupport实现了接口IterationTag,这个接口继承了Tag接口,Tag接口继承了JspTag接口。其中JspTag接口是空接口和Serializable接口一样是个标记接口。下面先看Tag接口,我直接用注释写在代码里:

package javax.servlet.jsp.tagext;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;

public interface Tag extends JspTag {
   //四个静态常量,等同于修饰符public static final(默认不写)
   //表示不显示标签间的文字
    int SKIP_BODY = 0;
    //表示将显示标签间的文字
    int EVAL_BODY_INCLUDE = 1;
    //表示不处理接下来的JSP网页
    int SKIP_PAGE = 5;
    //表示处理完标签后继续执行以下的JSP网页
    int EVAL_PAGE = 6;
   //页面上下文
    void setPageContext(PageContext var1);
    //设置父Tag
    void setParent(Tag var1);
   //获取父Tag
    Tag getParent();
   //遇到标签开始时会呼叫的方法,其合法的返回值是
   //EVAL_BODY_INCLUDE与SKIP_BODY,前者表示将显示标签间的文字,后者表示不显示标签间的文字
    int doStartTag() throws JspException;
   //在遇到标签结束时呼叫的方法,其合法的返回值是EVAL_PAGE与SKIP_PAGE
   //前者表示处理完标签后继续执行以下的JSP网页,后者是表示不处理接下来的JSP网页
    int doEndTag() throws JspException;
   //释放资源
    void release();
}

package javax.servlet.jsp.tagext;

import javax.servlet.jsp.JspException;

public interface IterationTag extends Tag {
    //会再显示一次标签间的文字
    int EVAL_BODY_AGAIN = 2;
    //这个方法是在显示完标签间文字之后呼叫的,其返回值有EVAL_BODY_AGAIN与SKIP_BODY
    //前者会再显示一次标签间的文字,后者则继续执行标签处理的下一步
    int doAfterBody() throws JspException;
}

IterationTag接口从方法的定义和命名上可以看出作用是提供一个重复动作。
下面我们看TagSupport类

package javax.servlet.jsp.tagext;

import java.io.Serializable;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;

public class TagSupport implements IterationTag, Serializable {
    private Tag parent;
    private Hashtable values;
    protected String id;
    protected PageContext pageContext;
public static final Tag findAncestorWithClass(Tag from, Class klass) {
    boolean isInterface = false;
    if (from != null && klass != null && ((class$javax$servlet$jsp$tagext$Tag == null ? (class$javax$servlet$jsp$tagext$Tag = class$("javax.servlet.jsp.tagext.Tag")) : class$javax$servlet$jsp$tagext$Tag).isAssignableFrom(klass) || (isInterface = klass.isInterface()))) {
        while(true) {
            Tag tag = from.getParent();
            if (tag == null) {
                return null;
            }

            if (isInterface && klass.isInstance(tag) || klass.isAssignableFrom(tag.getClass())) {
                return tag;
            }

            from = tag;
        }
    } else {
        return null;
    }
}

public TagSupport() {
}

public int doStartTag() throws JspException {
    return 0;表示不显示标签间的文字
}

public int doEndTag() throws JspException {
    return 6;//表示处理完标签后继续执行以下的JSP网页
}

public int doAfterBody() throws JspException {
    return 0;表示不显示标签间的文字
}

public void release() {
    this.parent = null;
    this.id = null;
    if (this.values != null) {
        this.values.clear();
    }

    this.values = null;
}

public void setParent(Tag t) {
    this.parent = t;
}

public Tag getParent() {
    return this.parent;
}

public void setId(String id) {
    this.id = id;
}

public String getId() {
    return this.id;
}

public void setPageContext(PageContext pageContext) {
    this.pageContext = pageContext;
}

public void setValue(String k, Object o) {
    if (this.values == null) {
        this.values = new Hashtable();
    }

    this.values.put(k, o);
}

public Object getValue(String k) {
    return this.values == null ? null : this.values.get(k);
}

public void removeValue(String k) {
    if (this.values != null) {
        this.values.remove(k);
    }

}

public Enumeration getValues() {
    return this.values == null ? null : this.values.keys();
}

}

doStartTag()和doAfterBody()方法默认返回不显示标签间的文字;doEndTag()方法默认返回处理完标签后继续执行以下的JSP网页,这三个方法是自定标签时最有用的方法,另外类中通过setter注入了pageContext,pageContext可以操作当前页面的上下文,包括绘画、请求、out输出流等。
下面看BodyTagSupport类的类图
BodyTagSupport类关系图
BodyTagSupport类实现了BodyTag接口,继承了TagSupport类,BodyTagSupport类中新增加的一个protected BodyContent bodyContent;字段的getter和setter,也就是直接持有了一个JspWriter,jsp页面的字符输出流,可以向页面输出字符。到此可以看出来BodyTagSupport类是通过bodyContent与标签交互的。
另外doStartTag()的返回值为2,EVAL_BODY_AGAIN也就是会再显示一次标签间的文字。
好了自定义标签第一篇就写这么多,下一篇记录怎样写一个自定义标签。

猜你喜欢

转载自blog.csdn.net/u011930054/article/details/88199420