JSP标签
JSP 中支持自定义标签。自定义标签是为了简化代码,使用自定义标签替换一个 Java 代码片断,完成相同的功能。
基本用法
下面以“在 JSP 页面中获取并输出客户端 IP”为例,来讲解自定义标签的实现步骤。
# (1) 需求
若使用 Java 代码块来完成这个功能,则 JSP 页面定义如下:
<%
// 获取客户端IP
String clientIP = request.getRemoteAddr();
out.write(clientIP);
%>
下面要定义一个标签,替换掉这段代码并实现相同功能。
# (2) 定义标签处理器
一个标签实际上对应着一个类中的一段代码。若要定义一个类来完成自定义标签的功能,则需要该类实现一个接口:javax.servlet.jsp.tagext.SimpleTag。这个类称为标签处理器类。该接口中具有五个需要实现的方法:
doTag():当 JSP 页面中执行到标签时,由服务器自动调用执行的方法。自定义标签功能的实现都在这个方法中。
getParent():获取到当前标签的父标签的引用。
setJspBody():由服务器自动调用。服务器会将 JSP 代码片断传入给当前的 Java 类
setJspContext():由服务器自动调用。服务器会将 PageContext 对象传入给当前的 Java类。注意,PageContext 是 JspContext 的子类。
setParent():由服务器自动调用。服务器会将当前标签的父标签引用传入给当前的 Java类。不过,javax.servlet.jsp.tagext.SimpleTagSupport 类实现了 SimpleTag 接口,并且,该类还提供了获取 JSP 片断、获取 PageContext 的方法。
所以,我们就无需再实现 SimpleTag 接口了,只需要继承 SimpleTagSupport 类即可。
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
public class ClilentSimpleTag extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
// 获取PageContext对象
PageContext pa = (PageContext)getJspContext();
// 获取客户端IP
String remoteAddr = pa.getRequest().getRemoteAddr();
// 输出
pa.getOut().write(remoteAddr);
}
}
(3) 注册标签处理器
在项目的 WEB-INF 目录下,新建一个扩展名为.tld 的 XML 文件。该文件的约束,同样来自于 Tomcat 的 webapps 中的/examples/WEB-INF/jsp2/jsp2-example-taglib.tld 文件。在其中添加相应的子标签。
`
<tlib-version>1.0</tlib-version>
<short-name>mytage</short-name>
<uri>http://www.xxx.com/jsp/mytage</uri>
<tag>
<name>clientIp</name>
<tag-class>com.jsp.tag.ClilentSimpleTag</tag-class>
<body-content>empty</body-content>
</tag>
` 以上配置就定义了一个前辍为 mytag,标签为 clientIp 的标签,该标签没有标签体。
(4) 使用自定义标签
在 JSP 页面中,只需通过 taglib 指令,将相应的标签库导入即可。
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<%--引入标签库--%>
<%@taglib uri="http://www.xxx.com/jsp/mytage" prefix="mytag"%>
<html>
<body>
<%-- 自定义标签库的使用 --%>
<mytag:clientIp/>
</body>
</html>
(5) 标签处理器实例的生命周期
标签处理器是多例的。每执行一次自定义标签,都会创建一个标签处理器的实例。该标签执行完毕,则标签处理器实例被销毁。可以在标签处理器中添加一个无参构造器。会发现,每刷新一次页面,均会创建一个标签处理器实例。
public class ClilentSimpleTag extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
// 获取PageContext对象
PageContext pa = (PageContext)getJspContext();
// 获取客户端IP
String remoteAddr = pa.getRequest().getRemoteAddr();
// 输出
pa.getOut().write(remoteAddr);
}
public ClilentSimpleTag() {
System.out.println("实例化标签无参构造!");
}
}
定义带标签体的标签
若要定义带标签体的标签,则需要注意以下几点:
使用 getJspBody()方法可以获取到标签的文本对象,此文本对象以 JspFragment 对象形式出现。
JspFragment 对象具有一个 invoke(Writer)方法,该方法可以将标签文本对象写入到输出流中。
若要获取字符串类型的标签文本数据,则需要将 JspFragment 对象写入到一个具有缓存功能的输出流中,且该流数据还可以被截取到。一般这个输出流使用 StringWriter。
## (1)需求
<%--自定义的标签使用 --%>
<mytag:toUpperCase>dfdfg</mytag:toUpperCase>
<%-- 存在于四大域值 --%>
<%
session.setAttribute("sum","jhjgh");
%>
<mytag:toUpperCase>${sessionScope.sum}</mytag:toUpperCase>
定义一个标签,实现的功能是,将标签体文本中小写字母转换为大写字母。若标签体文本来自于表达式的计算结果,则先计算表达式,再将结果转换为大写。在前面项目中直接实现。
(2) 定义标签处理器
package com.jsp.tag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
import java.io.StringWriter;
public class UpperCaseTage extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
// 创建缓存输出流
StringWriter stringWriter = new StringWriter();
// 获取JSP片断 即标签文本对象
JspFragment jspBody = this.getJspBody();
// 将标签写入到输出流中 ,
jspBody.invoke(stringWriter);
// 获取标准输出流对象
JspWriter out = this.getJspContext().getOut();
// 将所有标签文本放到缓存流中
String s = stringWriter.toString();
//转为大写
String s1 = s.toUpperCase();
// 输出到游览器
out.write(s1);
}
}
(3) 注册标签处理器修改 WEB-INF 下的 customTagLib.tld 文件。在其中添加如下配置。
<tag>
<name>toUpperCase</name>
<tag-class>com.jsp.tag.UpperCaseTage</tag-class>
<body-content>scriptless</body-content>
</tag>
对于标签,其值有四个选项:
empty:表示自定义的标签没有标签体
scriptless:表示自定义的标签具有标签体,且标签体文本中不能包含 Java 脚本,即不能包含 Java 代码段与 Java 表达式。但可以包含 EL 表达式,且会对 EL 表达式进行计算。
JSP:在 JSP2.0 之前,定义标签处理器类需要实现 Tag 接口,或继承自 TagSupport 类。
那时候该属性值有用,表示会将标签体的文本内容原样显示在浏览器。该属性值对于继承自 SimpleTagSupport 类的标签处理器是不能使用的。使用会报错。
tagdependent:表示会将标签体的文本内容原样显示在浏览器。若其中包含 EL 表达式,也会将其作为普通字符串,而不对其进行计算。
(4) 运行结果
通过前面需求中 JSP 页面中的内容,其运行结果如下:
(5) 其它情况
若将 customTagLib.tld 中该中的值设置为 tagdependent
<tag>
<name>toUpperCase</name>
<tag-class>com.jsp.tag.UpperCaseTage</tag-class>
<body-content>tagdependent</body-content>
</tag>
则以上 JSP 页面的运算结果会是,将所有内容全部作为普通字符串,即使 EL 表达式,也不会进行计算。
定义带属性的标签
若要定义带属性的标签,则需要注意以下几点:
标签中的属性,反映到标签处理器中,就是一个具有 set 方法的属性。
对于 JspFragment 的 invoke(Writer)方法需要注意,若指定了输出流,则会将标签文本对象写入到指定的输出流中。若没有指定输出流,即指定输出流为 null,则默认将标签文本对象写入到 JSP 标准的输出流 JspWriter 中。
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<%--引入标签库--%>
<%@taglib uri="http://www.xxx.com/jsp/mytage" prefix="mytag"%>
<html>
<body>
<%
session.setAttribute("testTrue", true);
%>
<mytag:if test="${sessionScope.testTrue}">
男
</mytag:if>
</body>
</html>
定义一个标签,实现的功能是:若 test 属性的值为 true,则显示标签体的文本。否则不显示。其中 test 的值可以来自于表达式的计算结果。
(2) 定义标签
处理器标签处理器中声明的具有 set 方法的属性,其属性名即为标签的属性名。
package com.jsp.tag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
public class IfSimpleTag extends SimpleTagSupport {
private Boolean test;
public void setTest(Boolean test) {
this.test = test;
}
@Override
public void doTag() throws JspException, IOException {
if (test) {
// 获取PageContext对象
PageContext pageContext = (PageContext) this.getJspContext();
// 获取输出流
JspWriter out = pageContext.getOut();
// 将文本写出到输出流
this.getJspBody().invoke(out);
}
}
}
以上代码完全等价于下面的代码:
package com.jsp.tag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
public class IfSimpleTag extends SimpleTagSupport {
private Boolean test;
public void setTest(Boolean test) {
this.test = test;
}
@Override
public void doTag() throws JspException, IOException {
if (test) {
this.getJspBody().invoke(null);
}
}
// @Override
// public void doTag() throws JspException, IOException {
// if (test) {
// // 获取PageContext对象
// PageContext pageContext = (PageContext) this.getJspContext();
// // 获取输出流
// JspWriter out = pageContext.getOut();
// // 将文本写出到输出流
// this.getJspBody().invoke(out);
// }
// }
}
(3) 注册标签处理器
修改 WEB-INF 下的 customTagLib.tld 文件。在其中添加如下配置。
<tag>
<name>if</name>
<tag-class>com.jsp.tag.IfSimpleTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>test</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
若自定义标签具有属性,则需要在中添加子标签。
:指定属性名。要保证与标签处理器中的属性名相同。
:指定该属性在标签中是否是必须的,为 true 则为必须的,为 false 则可有可无。
:指定该属性值是否可以来自于运行时表达式的值,为 true 表示属性值可以来自表达式,为 false 则表示该值只能为常量。这里的表达式指的是 EL 表达式与 JSP中的表达式块。rtexprvalue,是 Runtime Expression Value 的简写。
定义 forEachList 标签
(1) 需求
<%@ page import="java.util.ArrayList" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<%--引入标签库--%>
<%@taglib uri="http://www.xxx.com/jsp/mytage" prefix="mytag" %>
<html>
<body>
<%
ArrayList<String> names = new ArrayList<>();
names.add("小草");
names.add("小化");
names.add("小奴役");
names.add("小密集");
pageContext.setAttribute("names", names);
%>
<mytag:forEachList items="${names}" var="name">
${name} <br>
</mytag:forEachList>
</body>
</html>
定义一个标签,实现的功能是:循环遍历属性 items 所指定的 list,当前遍历的元素名称为 var 属性所指定的名称,使用 EL 在标签体中输出当前元素的值。
(2) 定义标签处理器
public class ForEachListSimpleTag extends SimpleTagSupport {
private List items;
private String var;
@Override
public void doTag() throws JspException, IOException {
PageContext pageContext = (PageContext)this.getJspContext();
for (Object obj : items ) {
// 循环将数据写到pageContext域空间中
pageContext.setAttribute(var,obj);
// 获取当前标签文本对象 将其写到标准输出流中
this.getJspBody().invoke(null);
}
}
public List getItems() {
return items;
}
public void setItems(List items) {
this.items = items;
}
public String getVar() {
return var;
}
public void setVar(String var) {
this.var = var;
}
}
需要注意以下几点:
var 定义为 String,而非 Object。因为我们需要的是将 var 作为域属性名称放入到域属性空间,而具体的元素,是作为域属性空间中的值出现的。
在循环遍历时,需要将当前循环元素存放到域属性空间中。因为 JSP 页面中是通过 EL读取每一个 List 的元素的。所以每一个 List 的元素,均需要存放到域属性空间中。
每循环遍历一次,都需要将当前的标签文本对象放入到 JSP 的标准输出流中,以显示于JSP 页面中。
(3) 注册标签处理器
<tag>
<name>forEachList</name>
<tag-class>com.jsp.tag.ForEachListSimpleTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>items</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>var</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
注册标签处理器时需要注意:
属性 items 的值允许来自表达式,所以的值为 true。
属性 var 的值不允许是动态的,所以的值为 false。
需求
前面定义的 forEachList 标签,只能循环遍历 List 集合。但对于 Set、Map 集合,及数组均不能进行遍历。这里要定义一个标签 forEach,可以用来遍历 Set、Map 集合,及 Object数组。
自行完成
将自定义标签库打包发行
将前面的自定义标签库打为 Jar 包后,就可以在今后的项目中使用了。具体操作百度。