一、简介
原理:用户自定义的 jsp 标记。当一个含有自定义标签的 jsp 页面被 jsp 引擎编译成 servlet 时,tag 标签被转化成了对一个标签处理器类的对象的操作。
标签库API:定义在 javax.servlet.jsp.tagext 中
二、实现SimpleTag接口的标签处理器类的生命周期
1)setJspContext:Jsp 引擎将代表 JSP 页面的 pageContext 对象传递给标签处理器对象
2)setParent:Jsp 引擎将父标签处理器对象传递给当前标签处理器对象。只有存在父标签时,Jsp 引擎才会调用才方法
3)setXxx:设置标签属性。只有定义属性才调用该方法。
4)setJspBody:若存在标签体,Jsp 引擎将把标签体封装成一个 JspFragment 对象,调用 setJspBody 方法将 JSPFragment 对象传递给标签处理器对象。若标签体为空,这 setJspBody 将不会被 Jsp 引擎调用。
5)doTag:容器调用标签处理器对象的 doTag 方法执行标签逻辑
三、自定义标签开发与应用步骤
- 用Java类来实现标签功能(类中的字段和属性就是使用标签时的属性。就像 a 标签的 href 属性)
- 用TLD文件描述标签的名字、类型以及该标签所关联的java类等
1. 编写完成标签功能的 Java 类(标签处理器)
HelloSimpleTag(实现SimpleTag接口):把 value 输出 count 遍
public class HelloSimpleTag implements SimpleTag { private String value; private String count; public void setValue(String value) { this.value = value; } public void setCount(String count) { this.count = count; } //标签体逻辑实际应该编写到该方法中. @Override public void doTag() throws JspException, IOException { // System.out.println("value: " + value + ", count: " + count); // // HttpServletRequest request = (HttpServletRequest) pageContext.getRequest(); // pageContext.getOut().print("Hello: " + request.getParameter("name")); JspWriter out = pageContext.getOut(); int c = 0; c = Integer.parseInt(count); for(int i = 0; i < c; i++){ out.print((i + 1) + ": " + value); out.print("<br>"); } } @Override public JspTag getParent() { System.out.println("getParent"); return null; } @Override public void setJspBody(JspFragment arg0) { System.out.println("setJspBody"); } private PageContext pageContext; //JSP 引擎调用, 把代表 JSP 页面的 PageContext 对象传入 //PageContext 可以获取 JSP 页面的其他 8 个隐含对象. //所以凡是 JSP 页面可以做的标签处理器都可以完成. @Override public void setJspContext(JspContext arg0) { System.out.println(arg0 instanceof PageContext); this.pageContext = (PageContext) arg0; } @Override public void setParent(JspTag arg0) { System.out.println("setParent"); } }
ReadFileTag(继承SimpleTagSupport类):给一个文本路径,输出该文本里的内容
public class ReadFileTag extends SimpleTagSupport{ //相对于当前 WEB 应用的根路径的文件名 private String src; public void setSrc(String src) { this.src = src; } @Override public void doTag() throws JspException, IOException { PageContext pageContext = (PageContext) getJspContext(); InputStream in = pageContext.getServletContext().getResourceAsStream(src); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String str = null; while((str = reader.readLine()) != null){ str = Pattern.compile("<").matcher(str).replaceAll("<"); str = Pattern.compile(">").matcher(str).replaceAll(">"); pageContext.getOut().println(str); pageContext.getOut().println("<br>"); } } }
2. 编写标签库描述(tld)文件,在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 http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd" version="2.0"> <!-- 描述 TLD 文件 --> <description>MyTag 1.0 core library</description> <display-name>MyTag core</display-name> <tlib-version>1.0</tlib-version> <!-- 建议在 JSP 页面上使用的标签的前缀 --> <short-name>mytag</short-name> <!-- 作为 tld 文件的 id, 用来唯一标识当前的 TLD 文件, 多个 tld 文件的 URI 不能重复. 通过 JSP 页面的 taglib 标签的 uri 属性来引用. --> <uri>http://www.xxx.com/mytag/core</uri> <!-- 描述自定义的 HelloSimpleTag 标签 --> <tag> <!-- 标签的名字: 在 JSP 页面上使用标签时的名字 --> <name>hello</name> <!-- 标签所在的全类名 --> <tag-class>com.zhang.javaweb.tag.HelloSimpleTag</tag-class> <!-- 标签体的类型 --> <body-content>empty</body-content> <!-- 描述当前标签的属性 --> <attribute> <!-- 属性名 --> <name>value</name> <!-- 该属性是否被必须 --> <required>true</required> <!-- rtexprvalue: runtime expression value 当前属性是否可以接受运行时表达式的动态值 --> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>count</name> <required>false</required> <rtexprvalue>false</rtexprvalue> </attribute> </tag>
<tag> <name>readerFile</name> <tag-class>com.zhang.javaweb.tag.ReadFileTag</tag-class> <body-content>empty</body-content> <attribute> <name>src</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag> </taglib>
3. 在JSP页面中导入和使用自定义标签
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!-- 导入标签库(描述文件) --> <%@taglib uri="http://www.xxx.com/mytag/core" prefix="mytag" %> <!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> <mytag:readerFile src="/WEB-INF/note.txt" />
<!-- count不能接收运行时表达式的值,所以不能用el表达式 --> <mytag:hello value="${param.name }" count="10" /> </body> </html>
三、使用方法总结
1. 带标签体的自定义标签
1). 若一个标签有标签体
<mytag:testJspFragment>abcdefg</mytag:testJspFragment>
在自定义标签的标签处理器中使用 JspFragment 对象封装标签体信息。
2). 若配置了标签含有标签体
则 JSP 引擎会调用 setJspBody() 方法把 JspFragment 传递给标签处理器类
在 SimpleTagSupport 中还定义了一个 getJspBody() 方法, 用于返回 JspFragment 对象。
3). JspFragment 的 invoke(Writer) 方法
把标签体内容从 Writer 中输出,若为 null,
则等同于 invoke(getJspContext().getOut()), 即直接把标签体内容输出到页面上。
有时, 可以 借助于 StringWriter, 可以在标签处理器类中先得到标签体的内容:
//1. 利用 StringWriter 得到标签体的内容.
StringWriter sw = new StringWriter();
bodyContent.invoke(sw);
//2. 把标签体的内容都变为大写
String content = sw.toString().toUpperCase();
4). 在 tld 文件中, 使用 body-content 节点来描述标签体的类型
<body-content>: 指定标签体的类型,大部分情况下, 取值为 scriptless。可能取值有 3 种:
- empty: 没有标签体
- scriptless: 标签体可以包含 el 表达式和 JSP 动作元素,但不能包含 JSP 的脚本元素
- tagdependent: 表示标签体交由标签本身去解析处理。若指定 tagdependent,在标签体中的所有代码都会原封不动的交给标签处理器,而不是将执行结果传递给标签处理器
<body-content>tagdependent</body-content>
5). 定义一个自定义标签
<mytag:printUpper time="10">abcdefg</mytag> 把标签体内容转换为大写, 并输出 time 次到浏览器上。
6). 实现 forEach 标签
> 两个属性: items(集合类型, Collection),var(String 类型)
> doTag:
* 遍历 items 对应的集合
* 把正在遍历的对象放入到 pageContext 中,键: var,值: 正在遍历的对象.
* 把标签体的内容直接输出到页面上。
<c:forEach items="${requestScope.customers }" var="cust2">
${pageScope.cust2.id } -- ${cust2.name } <br>
</c:forEach>
2. 开发有父标签的标签
@Override public void doTag() throws JspException, IOException { //1. 得到父标签的引用 JspTag parent = getParent(); //2. 获取父标签的 name 属性 ParentTag parentTag = (ParentTag) parent; String name = parentTag.getName(); //3. 把 name 值打印到 JSP 页面上. getJspContext().getOut().print("子标签输出name: " + name); }
1). 父标签无法获取子标签的引用
父标签仅把子标签作为标签体来使用
2). 子标签可以通过 getParent() 方法来获取父标签的引用(需继承 SimpleTagSupport 或自实现 SimpleTag 接口的该方法)
若子标签的确有父标签,JSP 引擎会把代表父标签的引用通过 setParent(JspTag parent) 赋给标签处理器
3). 父标签的类型是 JspTag 类型
该接口是一个空接口,但是来统一 SimpleTag 和 Tag 的。实际使用需要进行类型的强制转换。
4). 父标签的 <body-content></body-content>需设置为 scriptless
在 tld 配置文件中,无需为父标签有额外的配置。但,子标签是是以标签体的形式存在的,所以父标签的 <body-content></body-content>需设置为 scriptless。
5). 实现
<c:choose>
<c:when test="${param.age > 24}">大学毕业</c:when>
<c:when test="${param.age > 20}">高中毕业</c:when>
<c:otherwise>高中以下...</c:otherwise>
</c:choose>
> 开发 3 个标签: choose,when,otherwise
> 其中 when 标签有一个 boolean 类型的属性: test
> choose 是 when 和 otherwise 的父标签
> when 在 otherwise 之前使用
> 在父标签 choose 中定义一个 "全局" 的 boolean 类型的 flag:用于判断子标签在满足条件的情况下是否执行。
* 若 when 的 test 为 true,且 when 的父标签的 flag 也为 true,则执行 when 的标签体(正常输出标签体的内容),
同时把 flag 设置为 false
* 若 when 的 test 为 true,且 when 的父标签的 flag 为 false,则不执行标签体。
* 若 flag 为 true,otherwise 执行标签体。