简介
JSTL是一个JSP标准标签库,可以解决大部分问题,但是如果我们需要一些更特殊的功能,就需要自定义类似JSTL中标签的标签。如果EL表达式无法满足我们的需求,我们也可以自定义EL函数。
tld后缀的文件为标签库描述符,它是一个XML格式的文件,顾名思义,就是用来描述标签库的文件,编写自定义标签和EL函数时都需要用到。
tld文件
<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<!--标签库描述-->
<description>测试标签库</description>
<!--标签版版本-->
<tlib-version>1.0</tlib-version>
<!--标签库名称-->
<short-name>testTagLibrary</short-name>
<!--定义一个唯一标识当前版本标签库的公开的URI,taglib指令的uri属性必须等于该值。
如果没有将标签处理器和tld文件打成jar包,那么taglib指令的uri属性的值也可以本文件的相对路径,如/WEB-INF/test.tld-->
<uri>http://test/tag-library.com</uri>
<tag>
<!--标签描述-->
<description>经典标签库</description>
<!--标签的名称,引用标签时使用-->
<name>classic</name>
<!--标签处理器的完全限定类名,不能放到默认包下-->
<tag-class>com.ClassicCustomTag</tag-class>
<!--
指定标签有效的body类型。
JSP容器使用此值来验证标签body类型是否正确,并通过页面组合工具帮助页面作者提供有效的标签body。
tagdependent:标签的body由标签处理器解析,可以是另外一种语言,如SQL。
JSP:标签body可以包含jsp的语法,如jsp脚本元素,使用简单标签处理器不能设置为该值。
empty:标签body必须为空。
scriptless:标签body可以是静态HTML元素,EL表达式,jsp动作元素,但不允许出现jsp脚本元素
-->
<body-content>JSP</body-content>
<!--标签的属性-->
<attribute>
<!--属性描述-->
<description>权限</description>
<!--属性的名称-->
<name>permission</name>
<!--true/yes表示必须,false/no表示可选-->
<required>true</required>
<!--true/yes表示属性值支持jsp脚本元素和EL表达式,false/no表示不支持-->
<rtexprvalue>true</rtexprvalue>
<!--属性值的类型,如果rtexprvalue为false/no,则该值永远为java.lang.String-->
<type>java.lang.String</type>
</attribute>
</tag>
<!--用于提供标签库中要暴露给EL的每个函数的信息-->
<function>
<!--函数的唯一名称-->
<name>customPrint</name>
<!--函数的完全限定类名,不能放到默认包下,该类包含实现该函数的静态方法-->
<function-class>com.CustomElFunction</function-class>
<!--实现该函数的静态方法的签名,可以使用基本数据类型以及void。
如java.lang.String nickName(java.lang.String,int)-->
<function-signature>java.lang.String print(java.lang.String)</function-signature>
</function>
</taglib>
自定义标签
创建自定义标签分为两个步骤,编写标签处理器和注册标签。标签处理器在JSP2.0之后新增了一种实现方式。注册标签就是在tld文件中描述标签,并且把tld文件放到WEB-INF目录下。如果把标签处理器和tld文件打成jar包,那么tld文件应该放在jar包的META-INF目录下。
JSP2.0之前
JSP2.0之前的标签处理器要实现Tag、IterationTag、BodyTag等接口,称为典型标签处理器。
TagSupport
标签处理器继承TagSupport类,然后重写doStartTag、doAfterBody、doEndTag方法既可。
如果标签有属性,那么需要在标签处理器中定义该属性。
doStartTag方法是Tag接口定义的,在处理开始标签时调用,返回Tag.EVAL_BODY_INCLUDE表示执行标签body,返回Tag.SKIP_BODY表示不执行标签body。
doEndTag方法是Tag接口定义的,在处理结束标签时调用,返回Tag.EVAL_PAGE表示执行页面的剩余部分,返回Tag.SKIP_PAGE表示不执行页面的剩余部分。
doAfterBody是IterationTag接口定义的,在处理完标签body后调用,返回IterationTag.EVAL_BODY_AGAIN表示再次执行标签body,返回Tag.SKIP_BODY表示不再执行标签body。
执行顺序为,doStartTag->body->doAfterBody->doEndTag。
下面为一个简单的例子。
ClassicCustomTag.java
package com;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
public class ClassicCustomTag extends TagSupport {
private String permission;
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
@Override
public int doStartTag() throws JspException {
if ("query".equals(permission) || "add".equals(permission)
|| "delete".equals(permission) || "edit".equals(permission)) {
return SKIP_BODY;
} else {
return EVAL_BODY_INCLUDE;
}
}
@Override
public int doAfterBody() throws JspException {
return super.doAfterBody();
}
@Override
public int doEndTag() throws JspException {
return super.doEndTag();
}
}
test.tld
<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<description>测试标签库</description>
<tlib-version>1.0</tlib-version>
<short-name>testTagLibrary</short-name>
<uri>http://test/tag-library.com</uri>
<tag>
<description>经典标签库</description>
<name>classic</name>
<tag-class>com.ClassicCustomTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<description>权限</description>
<name>permission</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
</taglib>
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib uri="http://test/tag-library.com" prefix="test" %>
<html>
<head>
<title>index</title>
</head>
<body>
<div>
<button onclick="alert(this.innerHTML);" <test:classic permission="query">disabled title="没有权限" </test:classic>>查询
</button>
</div>
<div>
<button onclick="alert(this.innerHTML);" <test:classic permission="edit">disabled title="没有权限" </test:classic>>编辑
</button>
</div>
<div>
<button onclick="alert(this.innerHTML);" <test:classic permission="export">disabled title="没有权限" </test:classic>>导出
</button>
</div>
</body>
</html>
BodyTagSupport
如果标签处理类需要和标签body交互如读取、重写标签body,那么需要继承BodyTagSupport类,BodyTagSupport继承了TagSupport,因此可以实现TagSupport的全部功能,因此可以重写doStartTag、doAfterBody、doEndTag,还可以重写getBodyContent、doInitBody。
此时doStartTag应该返回BodyTag.EVAL_BODY_BUFFERED,表示申请缓冲区,由setBodyContent方法得到的BodyContent对象来处理标签的body,否则getBodyContent返回null。body.getEnclosingWriter可以返回一个JspWriter对象,用于将响应发送到客户端。
doInitBody方法表示准备执行标签body时调用。
执行顺序为,doStartTag->doInitBody->body->doAfterBody->doEndTag。
下面为一个简单的例子。
ClassicCustomBodyTag.java
package com;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.BodyTagSupport;
public class ClassicCustomBodyTag extends BodyTagSupport {
private String permission;
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
@Override
public int doStartTag() throws JspException {
if ("query".equals(permission) || "add".equals(permission)
|| "delete".equals(permission) || "edit".equals(permission)) {
return EVAL_BODY_BUFFERED;
} else {
return SKIP_BODY;
}
}
@Override
public void doInitBody() throws JspException {
}
@Override
public int doAfterBody() throws JspException {
try {
BodyContent body = getBodyContent();
System.out.println("body:" + body.getString());
if ("query".equals(permission)) {
body.getEnclosingWriter().print("查询");
} else if ("add".equals(permission)) {
body.getEnclosingWriter().print("新增");
} else if ("delete".equals(permission)) {
body.getEnclosingWriter().print("删除");
} else {
body.getEnclosingWriter().print("无");
}
} catch (Exception e) {
e.printStackTrace();
}
return super.doAfterBody();
}
@Override
public int doEndTag() throws JspException {
return super.doEndTag();
}
}
test.tld
<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<description>测试标签库</description>
<tlib-version>1.0</tlib-version>
<short-name>testTagLibrary</short-name>
<uri>http://test/tag-library.com</uri>
<tag>
<description>经典标签库</description>
<name>classic</name>
<tag-class>com.ClassicCustomBodyTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<description>权限</description>
<name>permission</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
</taglib>
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib uri="http://test/tag-library.com" prefix="test" %>
<html>
<head>
<title>index</title>
</head>
<body>
<div>
<button onclick="alert(this.innerHTML);"><test:classic permission="query">***</test:classic>
</button>
</div>
<div>
<button onclick="alert(this.innerHTML);"><test:classic permission="edit">***</test:classic>
</button>
</div>
</body>
</html>
JSP2.0之后
JSP2.0之后新增了SimpleTag接口,实现该接口的标签处理器称为简单标签处理器。
简单标签处理器必须有无参构造函数。JSP容器调用setJspContext方法传递JspContext对象,从JspContext对象中可以获取JspWriter,用于将响应发送到客户端。如果标签有body,JSP容器调用setJspBody方法传递JspFragment对象,如果没有body,就不会调用这个方法。最后JSP容器在标签执行时调用doTag方法,并且只调用一次。
JspFragment有两个方法,一个是获取JspContext对象,一个是执行标签body并且输出到指定的Writer。
SimpleTagSupport
该类实现了SimpleTag接口,可以方便开发。
下面是一个简单的例子。
SimpleCustomTag.java
package com;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class SimpleCustomTag extends SimpleTagSupport {
private String permission;
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
@Override
public void doTag() throws JspException, IOException {
try {
JspContext jspContext = getJspContext();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
OutputStreamWriter outputStreamWriter=new OutputStreamWriter(byteArrayOutputStream);
getJspBody().invoke(outputStreamWriter);
outputStreamWriter.close();
System.out.println("body:" + byteArrayOutputStream.toString());
if ("query".equals(permission)) {
jspContext.getOut().print("查询");
} else if ("add".equals(permission)) {
jspContext.getOut().print("新增");
} else if ("delete".equals(permission)) {
jspContext.getOut().print("删除");
} else {
jspContext.getOut().print("无");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
test.tld
<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<description>测试标签库</description>
<tlib-version>1.0</tlib-version>
<short-name>testTagLibrary</short-name>
<uri>http://test/tag-library.com</uri>
<tag>
<description>简单标签库</description>
<name>simple</name>
<tag-class>com.SimpleCustomTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<description>权限</description>
<name>permission</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
</taglib>
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib uri="http://test/tag-library.com" prefix="test" %>
<html>
<head>
<title>index</title>
</head>
<body>
<div>
<button onclick="alert(this.innerHTML);"><test:simple permission="query">***</test:simple>
</button>
</div>
<div>
<button onclick="alert(this.innerHTML);"><test:simple permission="edit">***</test:simple>
</button>
</div>
</body>
</html>
自定义EL函数
函数必须是静态方法。
下面为一个简单的例子。
CustomElFunction.java
package com;
public class CustomElFunction {
public static String print(String message) {
return "自定义打印:" + message;
}
}
test.tld
<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<description>测试标签库</description>
<tlib-version>1.0</tlib-version>
<short-name>testTagLibrary</short-name>
<uri>http://test/tag-library.com</uri>
<function>
<name>customPrint</name>
<function-class>com.CustomElFunction</function-class>
<function-signature>java.lang.String print(java.lang.String)</function-signature>
</function>
</taglib>
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib uri="http://test/tag-library.com" prefix="test" %>
<html>
<head>
<title>${test:customPrint("index.jsp")}</title>
</head>
<body>
</body>
</html>