自定义标签

Posted shi_zi_183

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义标签相关的知识,希望对你有一定的参考价值。

自定义标签

在JSP开发中,为了处理某些逻辑功能,难免会在JSP页面书写大量的Java代码,从而导致JSP页面难以维护,可用性较低,为此,JSP从版本1.1开始,支持用户开发自己的标签,即自定义标签。

自定义标签入门

什么是自定义标签

自定义标签可以有效地将html代码与Java代码分离,从而使不懂Java编程地HTML设计人员也可以编写出功能强大地JSP页面,JSP规范中定义了多个用于开发自定义标签地接口和类,它们都位于javax.servlet.jsp.tagext包中。

所有地标签处理器都需要实现Tjavax.servlet.jsp.tagext.JSPTag接口,这个接口是在JSP2.0中新增地一个标识接口,它没有任何方法,主要是作为javax.servlet.jsp.tagext.Tag和javax.servlet.jsp.tagext.SimpleTag地共同基类。在JSP2.0之前,所有的标签类都需要实现Tag接口,这样的标签称为传统标签。后来为了简化标签的开发,JSP2.0规范又定义了一种新类型的标签,称为简单标签,简单标签的处理器类要实现SimpleTag接口。

自定义标签的开发步骤

开发一个自定义标签至少需要三个步骤
1、编写标签处理器
开发自定义标签的核心任务就是要编写作为标签处理器的Java类。
1)传统标签开发,需要实现javax.servlet.jsp.tagext.Tag接口
2)简单标签开发,需要实现javax.servlet.jsp.tagext.SimpleTag接口
Tag接口和SimpleTag接口定义了JSP页面与标签处理器类之间的通信规则。如果JSP引擎在编译JSP页面时会遇到自定义标签,传统标签将会调用标签处理器类的doStartTag()方法,简单标签将会调用标签处理器类的doTag()方法。
2、编写标签库描述符文件
要想让JSP引擎在遇到自定义标签时,能找到其所对应的标签处理器类,还必须编写一个标签库描述符(Tag Library Descriptor)文件,简称TLD文件。TLD文件与标签处理器之间的关系就如同web.xml文件与Servlet之间的关系,一个标签处理器类想要被JSP容器找到并调用,必须在TLD文件中进行注册,一个TLD文件中可以注册多个标签处理器类,每个自定义标签的注册名称不能相同,同一个TLD文件中注册的多个标签处理器类就形成了一个自定义标签库。TLD文件是基于XML文件的,其内容的编写需要遵循XML语法规范。
mytag.tld

<?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>2.0</jsp-version>
  <short-name>SimpleTag</short-name>
  <uri>http://www.mytld.cn</uri>
  <tag>
    <name>ipTag</name>
    <tag-class>tld.IpTag</tag-class>
    <body-content>empty</body-content>
  </tag>
</taglib>

<body-content>元素用于指定标签体的类型,其值共有4个
1)empty:表示在使用自定义标签时不能设置标签体,否则JSP容器会报错
2)JSP:表示自定义标签的标签体可以为任意的JSP元素,需要注意的是JSP必须大写
3)scriptless:表示自定义标签的标签体可以包含除JSP脚本元素之外的任意JSP元素
4)tagdependent:表示JSP容器对标签体内容不进行解析处理,而是将标签体内容原封不动输出给客户端或交给标签处理器自己去处理。例如,标签体中的EL表达式,标签<>,JSP脚本<%%>,都会被当作普通字符文本处理。
注:编写TLD文件后,需要把它放置到WEB-INF目录或者其子目录下,但WEB-INF\\classes目录和WEB-INF\\lib目录除外。
3、在JSP页面导入和使用自定义标签
TLD文件编写完成后,就可以在JSP文件中使用自定义标签。在使用自定义标签之前,首先需要使用taglib指令来引入TLD文件

<%@ taglib uri="" prefix=""%>

在上述语法中,uri用于指定引用的是哪一个TLD文件,它应该和要引入的TLD文件中<uri>元素的值保持一致。prefix属性用于为引入的TLD文件指定一个"引用代号",在使用这个标签库中注册的自定义标签时都需要加上这个"引用代号"作为前缀。prefix属性的值可以是任意的,但不能和其他taglib指令中的prefix属性值重复,而且需要遵循XML名称空间的命名约定。
自定义标签的格式有很多种
1)空标签
空标签是指不包括标签体的标签,它有两种语法

<prefix:tagname/>
<prefix:tagname></prefix:tagname>

2)带标签体的标签
在自定义标签的开始标签和结束标签之间可以包括标签体

<prefix:tagname>body</prefix:tagname>

3)带属性的标签
标签的属性是对标签元素的补充说明,它一般定义在开始标签中,以键/值对的形式出现

<prefix:tagname attrname1="attrvalue1" [attrname2="attrvalue2"...]>
	[body]
</prefix:tagname>

4)嵌套标签
嵌套标签是指一个标签的标签体中包含另外的标签,外层的标签称为父标签,内层嵌套的标签称为子标签

<prefix:tagname>
	<prefix:nestedtagname>
		[body]
	</prefix:nestedtagname>
</prefix:tagname>

传统标签

Tag接口

Tag接口是所有传统标签的父接口,它定义了4个int类型的静态常量和6个抽象方法。
下表中的常量都是处理器方法的返回值,服务器根据方法的返回值来决定标签体和JSP页面是否执行。

静态常量功能描述
EVAL_BODY_INCLUDEdoStartTag()方法的返回值,表示标签体会执行
SKIP_BODYdoStartTag()方法的返回值,表示标签体不被执行
EVAL_PAGEdoEndTag()方法的返回值,表示标签后面余下的JSP页面继续执行
SKIP_PAGEdoEndTag()方法的返回值,表示标签后面余下的JSP页面不被执行

下表中是Tag接口定义的抽象方法

方法声明功能描述
void setPageContext(PageContext pc)JSP容器实例化标签处理器后,调用setPageContext()方法将JSP页面的内置对象pageContext对象传递给标签处理器,标签处理器可以通过pageContext对象与JSP页面进行通信。
void setParent(Tag t)调用setPageContext()方法后,JSP容器会调用setParent()方法将当前标签的父标签处理器对象传递给当前标签处理器,如果当前标签没有父标签,则传递给setParent()方法的参数为null
Tag getParent()返回当前标签的父标签处理器对象,如果当前标签没有父标签则返回null
int doStartTag()当JSP容器解析到自定义标签的开始标签时,会调用doStartTag()方法,该方法可以返回EVAL_BODY_INCLUDE和SKIP_BODY两个常量,如果使用Tag的子接口BodyTag,还可以使用BodyTag.EVAL_BODY_BUFFERED常量
int doEndTag()当JSP容器解析到自定义标签的结束标签时,会调用doEndTag()方法,该方法可以返回EVAL_PAGE和SKIP_PAGE两个常量
void release()JSP容器在标签处理器对象被作为垃圾回收之前调用release()方法,以便释放标签处理器所占用的资源

Tag接口定义了JSP页面与标签处理器之间的通信规则,当JSP容器将JSP页面翻译成Servlet源文件时,如果遇到JSP标签,会创建标签处理器类的实例对象,然后依次调用标签处理器的setPageContext()方法,setParent()方法,doStartTag()方法,doEndTag()方法,release()方法,因此,在实现Tag接口时,需要对这些抽象方法进行实现。

IterationTag接口

在自定义标签的开发过程中,有时需要对标签体的内容进行重复处理,这时,可以使用IterationTag接口,它继承自Tag接口,在Tag接口基础上新增了一个EVAL_BODY_AGAIN常量和一个doAfterBody()方法
1、EVAL_BODY_AGAIN常量
EVAL_BODY_AGAIN常量是doAfterBody()方法的返回值,如果doAfterBody()方法该常量,JSP容器会把标签体的内容重复执行一次。
2、int doAfterBody()方法
JSP容器在每次执行完标签体后会调用doAfterBody()方法,该方法可以返回常量SKIP_BODY和EVAL_BODY_AGAIN。如果方法返回SKIP_BODY常量,JSP容器会去执行代表结束标签的doEndTag()方法,如果返回EVAL_BODY_AGAIN,则重复执行标签体。
1)编写标签处理器类
在Eclipse中新建工程chapter09,并在工程下编写标签处理器类Iteratr.java。
由于TagSupport类实现了IterationTag接口,为了简化程序的编写,我们定义的标签处理器类只需继承TagSupport类即可。

package Tag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.*;
public class Iteratr extends TagSupport{
	private int num;
	public void setNum(int num){
		this.num=num;
	}
	public int doStartTag() throws JspException{
		return Tag.EVAL_BODY_INCLUDE;
	}
	public int doAfterBody()throws JspException{
		num--;
		if(num>0){
			return EVAL_BODY_AGAIN;
		}else{
			return SKIP_BODY;
		}
	}
}

2)注册标签处理器类
mytag.tld

<?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>2.0</jsp-version>
  <short-name>SimpleTag</short-name>
  <uri>http://www.mytld.cn</uri>
  <tag>
    <name>iterate</name>
    <tag-class>Tag.lteratr</tag-class>
    <body-content>JSP</body-content>
    <attribute>
    	<name>num</name>
    	<required>true</required>
    </attribute>
  </tag>
</taglib>

3)编写JSP页面iterate.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.mytld.cn" prefix="my"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<my:iterate num="5">
		hello,world!<br/>
	</my:iterate>
</body>
</html>

BodyTag接口

在实现自定义标签时,有时需要对标签体的内容进行处理以后再向浏览器输出,比如将小写英文字母转化为大写,将HTML标签进行转义。为了实现这样的功能,JSP规范中定义了一个BodyTag接口,它继承自IterationTag接口,并在IterationTag接口基础上新增了两个方法和一个静态常量
1、EVAL_BODY_BUFFERED常量
如果标签处理器类实现了BodyTag接口,它的doStartTag()方法除了可以返回SKIP_BODY和EVAL_BODY_INCLUDE常量之外,还可以返回EVAL_BODY_BUFFERED常量。当doStartTag()方法返回EVAL_BODY_BUFFERED常量时,JSP容器将会创建一个javax.servlet.jsp.tagext.BodyContent对象,使用该对象来执行标签体。
2、setBodyContent(BodyContent b)方法
当且仅当doStartTag()方法返回EVAL_BODY_BUFFERED常量时,JSP容器才会调用setBodyContent()方法,通过该方法将BodyContent对象传递给标签处理器类使用。
3、doInitBody()方法
JSP容器在调用setBodyContent()方法后会调用doInitBody()方法来完成一些初始化的工作,该方法的调用在标签体执行之前。
BodyContent类时JspWriter类的子类,它在JspWriter的基础上增加一个用于存储数据的缓冲区(确切地说缓冲区是在BodyContent地子类org.apache.jasper.runtime.BodyContentImple中定义的),当调用BodyContent对象的方法写数据时,数据将被写入到BodyContent内部的缓冲区中。
当标签处理器类的doStartTag()方法返回EVAL_BODY_BUFFERED常数时,JSP容器会创建一个BodyContent对象,然后调用该对象的write()方法将标签体的内容写入BodyContent对象的缓冲区中,开发者只要能够访问BodyContent缓冲区的内容,就能对标签体的内容进行处理。

方法声明功能描述
String getString()以字符串的形式返回BodyContent对象缓冲区中保存的数据
Reader getReader()返回一个关联BodyContent对象缓冲区中数据的Reader对象,通过Reader对象可以读取缓冲区中的数据。
void clearBody()用于清空BodyContent对象缓冲区中的内容
JspWriter getEnclosingWriter()用于返回BodyContent对象中关联的JspWriter对象。当JSP容器创建BodyContent对象后,PageContent对象中的"out"属性不再指向JSP的隐式对象,而是指向新创建的BodyContent对象。同时,在BodyContent对象中会用一个JspWriter类型的成员变量enclosingWriter记住原来的隐式对象,getEnclosingWriter()方法返回的就是原始的JSP隐式对象。
writerOut()用于将BodyContent对象中的内容写入到指定的输出流。

注:传统标签的处理器是单例的,只会被创建和销毁一次。
1)编写标签处理器类ToUpperCase.java

package Tag;

import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.*;

public class ToUpperCase extends BodyTagSupport{
	public int doEndTag() throws JspException{
		String content=getBodyContent().getString();
		content=content.toUpperCase();
		try{
			bodyContent.getEnclosingWriter().write(content);
		}catch(IOException e){
			e.printStackTrace();
		}
		return super.doEndTag();
	}
}

由于BodyTagSupport类中的doStartTag()方法默认返回EVAL_BODY_BUFFERED常量,JSP容器会在执行标签体之前创建BodyContent对象,然后将标签体内容通过setBodyContent()方法设置给BodyContent对象。因此在例中,直接使用getBodyContent()方法的getString()方法获得写入到BodyContent缓冲区的内容,然后将其转换为大写,通过调用getEnclosingWriter()方法获取到out对象,将内容输出到浏览器。
注:这里不能使用doStartTag()方法,因为执行doStartTag()方法时,标签体中的内容还没有缓存到BodyContent对象中,因此不能通过getBodyContent()方法获取内容。
2)注册标签处理器
在mytag.tld中增加一个Tag元素,对标签处理器类进行注册。

  <tag>
    <name>toUpperCase</name>
    <tag-class>Tag.ToUpperCase</tag-class>
    <body-content>JSP</body-content>
  </tag>

3)编写JSP页面toUpperCase.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <%@ taglib uri="http://www.mytld.cn" prefix="my"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<my:toUpperCase>
	abcdefg
</my:toUpperCase>
</body>
</html>

简单标签

由于传统标签在使用三个标签接口来完成不同的功能时,显得过于繁琐,不利于技术推广,为此,Sun公司为了降低标签技术的学习难度,在JSP2.0中定义了一个更为简单、便于编写和调用的SimpleTag接口来实现标签功能。

简单标签API

SimpleTag接口与传统接口最大的区别在于:simpleTag接口只定义了一个用于处理标签逻辑的doTag()方法,该方法用于取代传统标签接口中定义的doStartTag(),doEndTag()和doAfterBody()等方法。doTag()方法在JSP引擎执行自定义标签时调用,并且之被调用一次,那些使用传统标签接口所能完成的功能都在doTag()方法体内完成。
1、SimpleTag接口

方法声明功能描述
void setJspContext(JspContext pc)用于将JSP页面的内置对象pageContext对象传递给标签处理器,标签可以通过pageContext对象与JSP页面进行通信。JSPContext类是PageContext类的父类,其中定义了一些不依赖于Servlet运行环境的方法,setJspContext()方法接受的参数类型为JspContext,是为了便于将简单标签拓展应用到非Servlet运行环境中
void setParent(JspTag parent)用于将当前的标签的父标签传递给标签处理器,如果没有父标签则不执行这个函数。
JspTag getParant()返回当前标签的父标签处理器对象,如果当前标签没有父标签则返回null
void setJspBody(JspFragment jspBody)用于把代表标签体的JspFragment对象传递给标签处理器对象
void doTag()用于完成所有标签逻辑,包括输出、迭代、修改标签体内容等。在方法中可以抛出javax.servlet.jsp.SkipPageException异常,用于通知JSP容器不再执行JSP页面中位于结束标签后面的内容,这等效于在传统标签的doEnd()方法中返回SKIP_PAGE常量

注:JSP规范要求JSP容器在每次处理JSP页面中的简单标签时,都需要创建一个独立的简单标签处理器实例对象,而不会像传统标签那样对标签处理器进行缓存,因此简单标签也是线程安全。
JspFragment类
javax.servlet.jsp.tagext.JspFragment类是在JSP2.0中定义的,它的实例对象代表JSP页面中的一段JSP片段,但是这段JSP中不能包含JSP脚本元素。
JSP容器在处理简单标签的标签体时,会把标签体内容用一个JspFragment对象表示,并调用标签处理器对象的setJspBody()方法将JspFragment对象传递给标签处理器对象,标签开发者可以根据需要调用对象的方法来决定是否输出标签体、或者循环多次输出标签体等。

方法声明功能描述
JspContext getJapContext()用于返回代表调用页面的JspContext对象
void invoke(Writer out)用于将标签体内容写入到指定的输出流对象out中,如果调用该方法时传入的参数为null,JSP容器会将标签内容写入JspContext,getOut()方法返回的输出流对象中。

JspFragment的invoke()方法是简单标签开发中最重要的一个方法,它用于控制如何执行标签体的内容。如果在doTag()方法中执行一次invoke(),那么标签体内容输出一次,执行多次,则输出多次。与BodyContent对象不同,在JspFragment中没有提供容器缓存标签体内容,也没有定义getString()之类的方法取出标签体内容,如果想对标签体内容进行修改,只需在调用invoke()方法时传入一个可取出结果数据的输出流对象,例如StringWriter、CharArrayWriter,让标签体的执行结果输出到该输出流对象中,然后取出数据进行修改后再输出给浏览器。
3、SimpleTagSupport
JSP规范中定义了一个类SimpleTagSupport,该类实现了SimpleTag接口,它内部使用成员变量jspContext和jspBody引用了JSP容器传入的JspContext对象和JspFragment对象,并且提供了两个方法来返回这两个对象的引用。

方法声明功能描述
JspContext getJspContext()用于返回代表调用页面的JspContext对象
JspFragment getJspBody()用于返回代表标签体的JspFragment对象

1)编写标签处理器类
SimpleIterate.java

package Tag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.*;
public class SimpleIterate extends SimpleTagSupport{
	private int num;
	public void setNum(int num){
		this.num=num;
	}
	public void doTag()throws JspException,IOException{
		JspFragment jf=this.getJspBody();
		for(int i=0;i<num;i++){
			jf.invoke(null);
		}
	}
}

2)编写简单标签库描述符文件
simpletag.tld

  <tag>
  	<name>simpleIterate</name>
  	<tag-class>
  		Tag.SimpleIterate
  	</tag-class>
  	<body-content>
  		scriptless
  	</body-content>
  	<attribute>
  		<name>num</name>
  		<required>true</required>
  	</attribute>
  </tag>

3)编写simpleIterate.jsp页面

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <%@ taglib uri="http://www.mytld.cn/SimpleTag" prefix="my"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<my:simpleIterate num="5">
		simpleTag!<br>
	</my:simpleIterate>
</body>
</html>

以上是关于自定义标签的主要内容,如果未能解决你的问题,请参考以下文章

Android TabLayout ViewPager 不会在 backstack 上膨胀标签片段

VSCode自定义代码片段——CSS选择器

vscode自定义问题

VSCode自定义代码片段6——CSS选择器

VSCode自定义代码片段(vue主模板)

VSCode自定义代码片段——声明函数