JSP之自定义标签

接上次说,在 JSP 中一般是不允许出现 Java 代码的,但有的时候必须要用 Java 代码来做一些输出,这时候可以使用自定义标签来实现,自定义标签对应一个 Java 类,JSP 引擎解析到标签的时候会执行对应类中相应的方法

传统标签

先来说说在 JSP2.0 之前的做法,虽然可能已经没人用了,但是了解一点没坏处啊,又不是很难,看框架的时候也许会用到呢

自定义标签需要实现 Tag 接口,这个接口非常简单,当然一般都是继承它的实现类 TagSupport 然后复写需要的方法
API 参考:https://tomcat.apache.org/tomcat-5.5-doc/jspapi/
下面是一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package web.tag;
public class ViewIPTag extends TagSupport {

// 一般写在 Start 方法里
@Override
public int doStartTag() throws JspException {
// 获取 pageContext 对象,也就得到了所有的隐式对象
HttpServletRequest request = (HttpServletRequest) this.pageContext.getRequest();
JspWriter out = this.pageContext.getOut();

// 获取 IP 并输出
String remoteAddr = request.getRemoteAddr();
try {
out.write(remoteAddr);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
return super.doStartTag();
}
}

这里就简单写了个输出 IP 的例子,剩下的就是让这个类和标签关联起来,一般我们在 WEB-INF 目录下新建一个 tld 文件来指定,可以是多级目录下,但必须在 WEB-INF 下,如果用的是 IDEA 的话直接有现成的模板,实在不行可去抄 Tomcat 里的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="ISO-8859-1"?>
<taglib xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
version="2.1">

<tlib-version>1.0</tlib-version>
<short-name>bfchengnuo</short-name>
<uri>http://bfchengnuo.com</uri>

<tag>
<name>ViewIP</name>
<tag-class>web.tag.ViewIPTag</tag-class>
<body-content>empty</body-content>
</tag>

</taglib>

主要是配置 uri 和 tag 标签,下面在 JSP 中会用到 body-content 属性可以认为标签是否含有标签体,单标签双标签的区别,然后下面就是在 JSP 中进行引用了

1
2
3
4
5
6
7
8
9
10
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://bfchengnuo.com" prefix="custTag" %>
<html>
<head>
<title>自定义标签测试</title>
</head>
<body>
您的 IP 是:<custTag:ViewIP/>
</body>
</html>

prefix 一般写我们定义的那个 tld 的文件名,有利于查找

还有一点需要注意,自定义标签处理完后一般不会立即释放资源,也就是一般不会执行 release() 方法,和 Servlet 类似,也会被缓存,等待下一次访问,等 Web 服务器关闭的时候会释放资源

自定义标签扩展

自定义标签除了上面的用法,还有几种比较常用的方式,比如控制 JSP 的 某一部分/全部内容 是否执行,重复输出某些内容、修改某些内容等,从 JSP 的用法上看它们都是一样的,就是把要控制的内容放进自定义标签体里。

控制是否执行

JSP 文件就不多说了,把要控制的内容包进去就可以了,然后我们在 Java 代码中进行控制,至于怎么控制,那就是用 Start/End 方法的返回值了!在 API 文档中说的也比较详细了,Tag 接口中定义了几个常量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public int doStartTag() throws JspException {
// 判断是否有权限
if(flag)
return Tag.EVAL_BODY_INCLUDE;
else
return Tag.SKIP_BODY;
}

// 如果是控制整个 JSP 的内容,可以在 JSP 开始处加一个结束标签,然后...
// 这样就不需要把所有的内容包进去,只是在头部加个结束标签
@Override
public int doEndTag() throws JspException {
// 判断是否有权限
if(flag)
return Tag.EVAL_PAGE;
else
return Tag.SKIP_PAGE;
}

对了,还有,不要忘记在 WEB-INF 文件夹下的 tld 文件里修改 body-content 为 JSP

1
2
3
4
5
<tag>
<name>ViewIP</name>
<tag-class>web.tag.ViewIPTag</tag-class>
<body-content>JSP</body-content>
</tag>

重复执行

重复执行某段内容,Tag 接口就无能为力了,所以就有了 IterationTag 接口,是的,它是专门为了重复执行而设计的,在 Tag 的基础上加了一个常量和方法
TagSupport 类已经实现了这个接口,所以说,用到直接复写 doAfterBody() 方法就可以了
它的调用时机是在标签体执行完,doEndTag 方法执行前
如果返回 EVAL_BODY_AGAIN 继续重复执行( doAfterBody 方法也会重复执行);
返回的是 SKIP_BODY 继续向下执行,但是 doStartTag 方法只会执行一次,毕竟配对的 doEndTag 不会执行嘛

1
2
3
4
5
6
7
8
9
private int i = 5;
// 循环输出 6 次,是在标签体执行完后才执行 doAfterBody
@Override
public int doAfterBody() throws JspException {
if (--i >= 0)
return IterationTag.EVAL_BODY_AGAIN;
else
return IterationTag.SKIP_BODY;
}

修改内容

如果想要修改内容的话,上面的两个接口都无能为力,只能靠 BodyTag 接口了,类似的我们一般是继承 BodyTagSupport 类然后复写需要的方法其实还是是 doStartTag() 只不过返回值不同了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class UpdateTag extends BodyTagSupport {

@Override
public int doStartTag() throws JspException {
// 返回值为 EVAL_BODY_BUFFERED 会自动调用 setBodyContent(BodyContent b)
// 然后就可以在 End 标签里获取到内容了
return BodyTag.EVAL_BODY_BUFFERED;
}

@Override
public int doEndTag() throws JspException {
BodyContent bodyContent = this.getBodyContent();
String content = bodyContent.getString();
// TODO...
return Tag.EVAL_PAGE;
}
}

简单标签

正是因为传统标签做不同的操作需要实现不同的接口太麻烦,所以在 JSP2.0+ 加入了SimpleTag 接口来统一,所有的功能实现这一个就可以了,不过我们还是习惯继承 SimpleTagSupport 类啦~

在简单标签中,解析时会先执行 setJspBody 方法把标签体的内容存起来 (当然还会自动调用 setParent [没有就会传 null ]、setJspContext 方法),然后执行 doTag() 方法
要注意的是:在简单标签中没有 Start/End 方法了,开始结束全都是执行 doTag;从 API 文档中可以知道,如果不想执行后面的内容,抛一个 SkipPageException 异常即可,相当于是传统标签的 SKIP_PAGE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SimpleTag extends SimpleTagSupport {

@Override
public void doTag() throws JspException, IOException {
// 获取标签体
JspFragment jf = this.getJspBody();
// 执行标签体的内容,如果不手动调用就不会执行
jf.invoke(this.getJspContext().getOut());
// 上面那句等价于 jf.invoke(null);


// 获取标签体内容. 将内容写入到事先准备的缓冲区内,然后获取
StringWriter sw = new StringWriter();
jf.invoke(sw);
String content = sw.toString();

// 如果不执行后面的内容,抛一个 SkipPageException 异常
throw new SkipPageException();
}
}

还要记得修改 body-content ,在 2.0+ 后要使用 scriptless,从名字也可以看得出,是不建议再在 JSP 中嵌入java 代码的

1
2
3
4
5
<tag>
<name>SimpleTag</name>
<tag-class>web.tag.SimpleTag</tag-class>
<body-content>scriptless</body-content>
</tag>

基本上就是这样了..

自定义标签的属性

都知道 HTML 标签都有属性,自定义的应该也有嘛~,想要设置自定义属性一般需要两步

  • 在对应的 Java 文件中编写对应的 Set 方法,程序会自动调用的,注意名称一致
  • 配置 TLD 文件

下面就举个简单栗子,循环输出指定次数的标签体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SimpleAttributes extends SimpleTagSupport {
private int cont;

// 可以自动转换八大基本数据类型
public void setCont(int cont) {
this.cont = cont;
}

@Override
public void doTag() throws JspException, IOException {
JspFragment jf = this.getJspBody();
for (int i = 0; i < cont; i++) {
jf.invoke(null);
}
}
}

只设置一个 Set 方法就行了,Get 没什么必要,下面就是配置 tld 文件了:

1
2
3
4
5
6
7
8
9
10
11
12
<tag>
<name>custAttributes</name>
<tag-class>web.tag.SimpleAttributes</tag-class>
<body-content>scriptless</body-content>

<attribute>
<name>cont</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<!--<type></type>-->
</attribute>
</tag>

required :表示是否是必须的
rtexprvalue:表示是否允许使用表达式(比如 ${name} )
type:指定类型,一般没必要设置

然后就可以在 JSP 中进行使用了,可以实现 防盗链、if判断、迭代、处理转义等功能,就如 EL 表达式一样,迭代功能写起来可能复杂点,不过利用反射技术挺方便的
PS:转义的实现在 \webapps\examples\WEB-INF\classes\util 下有现成的

最后,可以把这些自定义标签打成 jar 包,以后用到了直接丢进 lib 就行了,打包的时候建一个普通的 Java 工程就行,只要保证在 web 项目中没问题,拷贝到 Java 项目中后出现的错误不用管,是 J2EE 依赖的问题,然后把 tld 文件放在根目录下的 META-INF 目录就行,参考 Tomcat 中自带的 Jar 包结构。

喜欢就请我吃包辣条吧!

评论框加载失败,无法访问 Disqus

你可能需要魔法上网~~