您的位置:首页 > Web前端 > JavaScript

关于jsp自定义标签

2015-04-10 17:20 387 查看
本篇博客主要讲解关于如何自定义标签
声明:本篇博客为本人第二篇,如有什么错误希望博友指出,一起学习,本人感激不尽!

JSP标签库概念

JSP标签库,也称自定义标签库,可看成是一种通过JavaBean生成基于XML的脚本的方法。从概念上讲,标签就是很
简单而且可重用的代码结构。比方说,在最新发布的JSPKit(在JSP Insider内)中,使用XML标签实现了对XML文档
的轻松访问。详情请戳这里(来自百度百科)

JSP标签库层次结构

开发自定义标签所涉及到的接口与类的层次结构(其中SimpleTag接口与SimpleTagSupport类是JSP2.0中新引入的)

























JSP自定义标签

自定义一个简单标签

开发一个简单标签,我们只需要继承TagSupport或SimpleTagSupport即可,这里我们以继承TagSupport为例.

首先我们看一下TagSupport的结构



其中我们关注的几个方法
①doStartTag() 该方法表示开始处理标签
②doAfterBody()该方法在处理完开始标签后,用于处理标签体中的内容,由于此时我们并不能获取到标签体中的内容(要处理标签体中的内容请往下看),此方法在这里没有作用
③doEndTag() 处理结束标签,输出内容

自定义标签处理类
package test;

import java.io.IOException;
import java.util.Enumeration;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TagSupport;

public class SimpleTag extends TagSupport {
    private String value;
    @Override
    // ③ 这里开始处理标签
    public int doStartTag() throws JspException {
        return super.doStartTag();
    }
    @Override
    // ④ 处理结束标签
    public int doEndTag() throws JspException {
        try {
            pageContext.getOut().write(value);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return super.doEndTag();
    }
    public int doAfterBody() throws JspException {
        return super.doAfterBody();
    }
    public void release() {
        super.release();
    }
    @Override
    // ② 默认parent tag 为空
    public void setParent(Tag t) {
        super.setParent(t);
    }
    public Tag getParent() {
        return super.getParent();
    }
    @Override
    // ① 设置pageContext
    public void setPageContext(PageContext pageContext) {
        super.setPageContext(pageContext);
    }
    public void setValue(String k, Object o) {
        super.setValue(k, o);
    }
    public Object getValue(String k) {
        return super.getValue(k);
    }
    public Enumeration<String> getValues() {
        return super.getValues();
    }
    public String getValue() {
        return value;
    }
    public void setValue(String value) {
        this.value = value;
    }
}
注:这个简单的标签,是不支持标签体,也就是说不管你有没有标签体,我们都不会处理.

创建标签定义文件
<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3g.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd"
	version="2.0">
	<tlib-version>1.0</tlib-version>
	<jsp-version>2.2</jsp-version>
	<!-- 标签默认前缀 -->
	<short-name>test</short-name>
	<!-- 引入标签URI -->
	<uri>http://www.test.com/tags/test</uri>
	<description>
		A replacement fixed string JSP tag libraries.
	</description>
	<tag>
		<!-- 标签名字 -->
		<name>simple</name>
		<!-- 标签处理类 -->
		<tag-class>test.SimpleTag</tag-class>
		<!-- ① empty        即标记体为空
			 ② scriptless   这个标记不能有脚本元素,但可以有模板文本和EL, 还可以是定制和标准动作
			 ③ tagdependent 标记体要看做是纯文本,所以不会计算EL,也不会出发标记/动作
			 ④ JSP          能放在JSP中的东西都能放在这个标记体中 -->
		<body-content>scriptless</body-content>
		<!-- 标签属性,可以有多个 -->
		<attribute>
			<!-- 属性名 -->
			<name>value</name>
			<!-- 是否必须 -->
			<required>true</required>
			<!-- Run-time Expression Value,是否支持JSP表达式 (脚本和el表达式)-->
			<rtexprvalue>true</rtexprvalue>
			<!-- 属性类型 -->
			<type>java.lang.String</type>
		</attribute>
	</tag>
</taglib>
然后在web.xml中配置标签
<jsp-config>
	<taglib>
		<taglib-uri>http://www.sctour.com/tags/test</taglib-uri>
		<taglib-location><![CDATA[/WEB-INF/tags/test.tld]]></taglib-location>
	</taglib>
</jsp-config>


最后在JSP中引入标签库
<%@taglib uri="http://www.test.com/tags/test" prefix="test"%>


现在使用<test:simple value="testSimpleTag"/>就能在页面上显示testSimpleTag了
一个简单的JSP标签就开发好了.

接下来我们看看标签处理类内部的执行流程
①执行setPageContext方法,注入PageContext
<span style="white-space:pre">	</span>public void setPageContext(PageContext pageContext) {
    <span style="white-space:pre">	</span>    super.setPageContext(pageContext);
<span style="white-space:pre">	</span>}
②执行setParent,设置当前标签的父标签
<span style="white-space:pre">	</span>public void setParent(Tag t) {
 <span style="white-space:pre">	</span>       super.setParent(t);
<span style="white-space:pre">	</span>}
父标签,就是当前标签的外层标签(外层标签必须支持标签体,也就是需要处理标签体中的内容)
<span style="white-space:pre">	</span><sct:value key="88888888">
	<span style="white-space:pre">	</span><test:simple value="testSimpleTag"/>
<span style="white-space:pre">	</span></sct:value>
③如果我们在标签中使用了属性,此时会调用标签处理类中的属性setter方法,本例调用的是setValue方法
public void setValue(String value) {
        this.value = value;
    }
④属性注入过后,就开始处理标签了,此时调用doStartTag方法
public int doStartTag() throws JspException {
        return super.doStartTag();
    }
这里需要返回一个int类型的值,有两个返回值EVAL_BODY_INCLUDE=1和SKIP_BODY=0,实际上是可以返回任意int类型的值得,这里不推荐使用除前面定义好的两个值之外的.

当标签为单标签或双标签标签体为空时,此时显示的值只由后面第⑥步输出的值决定
当标签为双标签,且标签体有内容时,此时返回SKIP_BODY=0,显示的值为第⑥步输出的值,返回EVAL_BODY_INCLUDE=1或任意不为0的int的值时,显示的值为标签体+第⑥步输出的值.

⑤当标签为双标签且标签体有内容时,会执行doAfterBody 方法
public int doAfterBody() throws JspException {
        return super.doAfterBody();
    }
这里需要返回一个int类型的值,也有两个预定义的值EVAL_BODY_AGAIN=2与SKIP_BODY=0,这里同样是可以返回任意int类型的值.

当返回值为SKIP_BODY=0时或任意不等于2的int类型的值,此时会输出标签体中的内容,显示的值为标签体中的内容+第⑥步输出的内容.
当返回值为EVAL_BODY_AGAIN=2,此时同样会输出标签体中的内容,不同的是,此时会循环输出标签体中的内容,直至程序挂掉(特别注意)

⑥处理结束标签,执行doEndTag方法
public int doEndTag() throws JspException {
        try {
            pageContext.getOut().write(value);//这里输出的就是要显示的值
        } catch (IOException e) {
            e.printStackTrace();
        }
        return super.doEndTag();
    }

这里也需要返回一个int类型的值,有两个预定义的返回值EVAL_PAGE=6与 SKIP_PAGE=5,这里同样是可以返回任意int类型的值.

当返回值为SKIP_PAGE=5时,JSP中此标签后面的内容将不做处理,即不会显示.
当返回值为EVAL_PAGE=6或任意值不等于5的int类型值时,JSP中此标签后的内容继续执行.

至此,一个简单自定义标签就处理结束.至于其他方法,本文不做深究.

接下来自定义一个处理带标签体的标签

当我们要处理标签体中的内容时,继承TagSupport不能帮助我们完成此功能,此时我们需要继承BodyTagSupport(实现BodyTag接口也可以,但我们会实现很多我们用不到的方法),TagSupport中有的功能,BodyTagSupport都有,BodyTagSupport继承自TagSupport.
首先看看BodyTagSupport的结构



其中我们关注的几个方法
①doStartTag() 处理开始标签
②doInitBody() 初始化标签体
③doAfterBody() 处理标签体
④doEndTag() 处理结束标签
相比不能处理标签体的TagSupport,我们关注的方法仅仅是多了一个初始化标签体的方法

首先还是创建标签处理类
<pre name="code" class="java">import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.BodyTagSupport;
import javax.servlet.jsp.tagext.DynamicAttributes;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BodyTagTest extends BodyTagSupport implements DynamicAttributes {

    private static final long          serialVersionUID = 1L;

    private Logger                     logger           = LoggerFactory.getLogger(BodyTagTest.class);
    private String                     key;
    @Override
    public int doAfterBody() throws JspException {        
     if (SCTourUtils.isEmpty(value) && bodyContent != null) {
        value = bodyContent.getString();
     }
        /**
         * EVAL_BODY_AGAIN与SKIP_BODY 前者会再显示一次标签间的文字,后者则继续执行标签处理的下一步
         */
        return SKIP_BODY;
    }
    @Override
    public int doEndTag() throws JspException {
        // 当没有标签体 (单标签或标签体为空)时不会调用setBodyContent(BodyContent content)方法,此时bodyContent == null
        JspWriter writer = bodyContent == null ? pageContext.getOut() : bodyContent.getEnclosingWriter();
        try {    
                 if (SCTourUtils.isEmpty(value)) {
                        value = "";
                 }
                 writer.println(value); 
         } catch (IOException e) { 
                 logger.error("输出错误.", e); 
         } finally {
                 clear();
         } /** * EVAL_PAGE与 SKIP_PAGE * 前者表示处理完标签后继续执行以下的JSP网页,后者是表示不处理接下来的JSP网页(标签后面的内容将不再处理和显示) */ 
          return EVAL_PAGE; 
     } 
     @Override 
     public void doInitBody() throws JspException { 
            value = messageService.getContentValue(key, page); 
            super.doInitBody(); 
     } 
     @Override 
     public int doStartTag() throws JspException { 
           return EVAL_BODY_BUFFERED; 
     } 
     @Override 
     public void setBodyContent(BodyContent content) { 
            super.setBodyContent(content); 
     } 
    private void clear() {
        this.key = null;
        this.bodyContent = null;
        this.id = null;
        value = null;
   }
    public String getKey() {
        return key;
    }
    public void setKey(String key) {
        this.key = key;
    }
}




接下来创建标签定义文件
<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3g.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd"
	version="2.0">
	<tlib-version>1.0</tlib-version>
	<jsp-version>2.2</jsp-version>
	<short-name>test</short-name>
	<uri>http://www.test.com/tags/body</uri>
	<description>
		A replacement fixed string JSP tag libraries.
	</description>
	<tag>
		<name>body</name>
		<tag-class>test.BodyTagTest</tag-class>
		<!-- ① empty        即标记体为空
		<span style="white-space:pre">	</span> ② scriptless   这个标记不能有脚本元素,但可以有模板文本和EL, 还可以是定制和标准动作
			 ③ tagdependent 标记体要看做是纯文本,所以不会计算EL,也不会出发标记/动作
			 ④ JSP          能放在JSP中的东西都能放在这个标记体中 -->
		<body-content>scriptless</body-content>
		<attribute>
			<name>key</name>
			<required>true</required>
			<rtexprvalue>true</rtexprvalue>
			<type>java.lang.String</type>
		</attribute>
	</tag>
</taglib>
接下来在web.xml中配置,与上面创建一样,此不再贴出来

经过简单标签的理解,能够处理带标签体的标签理解起来就轻松多了,但是此时的执行顺序稍有不同
①首先执行的是设置属性,本例中执行的是setKey方法
②然后执行的是doStartTag方法
public int doStartTag() throws JspException {
        return EVAL_BODY_BUFFERED;
    }
方法需要返回一个int值,这里不再是两个预定义的值了,而是三个EVAL_BODY_INCLUDE=1、SKIP_BODY=0和EVAL_BODY_BUFFERED=2

当返回EVAL_BODY_INCLUDE=1时,将不再执行setBodyContent和doInitBody方法,此时显示的值为为标签体中的内容+doEndTag输出的内容
当返回SKIP_BODY=0时,将不再执行setBodyContent、doInitBody、doAfterBody,在doStartTag方法后直接跳到doEndTag方法,将不再显示标签体中的内容,显示为输出内容
当返回EVAL_BODY_BUFFERED=2或者任何不等于0、1的int类型的值时,会按照流程执行下去,显示内容为输出内容

③其次在执行setBodyContent方法,该方法有一个BodyContent参数,其中我们的标签体内容就封装在该对象中
④再执行doInitBody方法,该方法不需要返回内容,主要是用于对输出内容做一个预处理

⑤在执行doAfterBody方法,该方法需要返回返回一个int类型的值,此处与TagSupport中效果一致

⑥最后在执行doEndTag方法,此处返回值及效果与TagSupport中一致

至此,一个能处理标签体的标签就开发完成了.

最后补充下,当我们需要的一些参数不是固定的,参数个数也不是固定,此时不好枚举时,这种情况应该怎么解决呢? 此时我们就要用到动态属性这个东西了.
所谓动态属性,就是指属性名不确定,可有可无,个数不确定,最主要的是我们在定义文件中没有定义的属性.

我们要使用动态属性,首先标签处理类要实现javax.servlet.jsp.tagext.DynamicAttributes接口,并实现其中的setDynamicAttribute方法
下面是DynamicAttributes源码
<pre name="code" class="java"><span style="white-space:pre">	</span>public interface DynamicAttributes {
         
          /**
           * Called when a tag declared to accept dynamic attributes is passed
           * an attribute that is not declared in the Tag Library Descriptor.
           * 
           * @param uri the namespace of the attribute, or null if in the default namespace.//<span style="color:#333333;">就是定义文件的命名空间,如果在默认命名空间,则为null</span>
           * @param localName the name of the attribute being set. // 属性名
           * @param value the value of the attribute //属性值
           * @throws JspException if the tag handler wishes to
           *     signal that it does not accept the given attribute.  The 
           *     container must not call doStartTag() or doTag() for this tag.
           */
          public void setDynamicAttribute(String uri, String localName, Object value ) throws JspException;
          
      }



然后在定义文件中加入
<dynamic-attributes>true</dynamic-attributes>


这样标签才支持动态属性,否则使用动态属性,会抛出org.apache.jasper.JasperException异常.

然后servlet才会循环调用setDynamicAttribute方法处理动态属性,此方法是在属性注入过后执行的.

此致,标签的书写就结束了!

感谢阅读!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: