Tomcat与JavaWeb 8.2 自定义标签的创建和使用(二)重复执行标签主体、访问标签主体内容
1. 创建和使用iterate标签(重复执行标签主体)
JSP网页中经常需要显示集合中的批量数据。我们曾使用"<%%>"形式的Java程序片段的for循环等来完成这种功能。
下面将创建一个iterate标签,它能完成和Java程序片段的循环类似的功能。下面例程的iterate.jsp中就通过iterate标签来遍历books集合中的所有元素。使用iterate标签可以使JSP代码更加简洁,提高可读性。
iterate.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="mm" uri="/mytaglib" %>
<%@ page import="java.util.*" %>
<%@ page import="mypack.BookDetails" %>
<%
BookDetails book1 = new BookDetails("Name1","Title1",100,"Description1");
BookDetails book2 = new BookDetails("Name2","Title2",200,"Description2");
BookDetails book3 = new BookDetails("Name3","Title3",300,"Description3");
BookDetails book4 = new BookDetails("Name4","Title4",400,"Description4");
List books = new ArrayList();
books.add(book1);
books.add(book2);
books.add(book3);
books.add(book4);
%>
<html>
<head>
<title>Title</title>
</head>
<body>
<table border="1">
<caption><b>书的信息</b></caption>
<tr>
<th>作者</th>
<th>书名</th>
<th>价格</th>
<th>读者评价</th>
</tr>
<mm:iterate var="book" items="<%=books%>">
<tr>
<td>${book.name}</td>
<td>${book.title}</td>
<td>${book.price}</td>
<td>${book.description}</td>
</tr>
</mm:iterate>
</table>
</body>
</html>
iterate标签有两个属性:
- var:指定存放在页面范围内的属性名,此处为“book”,意味着iterate标签的处理类每次从集合中取出一个元素后,会把这个元素存放在页面范围内,属性名为“book”。
- items:表示待遍历访问的集合,此处为books集合。
iterate标签的处理类是IterateTag:
mypack/IterateTag.java
package mypack;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.TagSupport;
import java.util.Collection;
import java.util.Iterator;
public class IterateTag extends TagSupport {
private Iterator items;
private String var;
private Object item;
public void setItems(Collection items) {
if (items.size()>0){
this.items = items.iterator();
}
}
public void setVar(String var) {
this.var = var;
}
@Override
public int doStartTag() throws JspException {
if (items.hasNext()) {
item = items.next();
saveItem();
System.out.println("save +1 次");
return EVAL_BODY_INCLUDE;
} else {
return SKIP_BODY;
}
}
@Override
public int doAfterBody() throws JspException {
if (items.hasNext()) {
item = items.next();
saveItem();
System.out.println("save +1 次");
return EVAL_BODY_AGAIN;
} else {
return SKIP_BODY;
}
}
private void saveItem() {
if (item == null) {
pageContext.removeAttribute(var, PageContext.PAGE_SCOPE);
} else {
pageContext.setAttribute(var,item);
}
}
}
当Servlet容器调用iterate.jsp的iterate标签时,运行IterateTag对象的处理流程如下:- 调用setPageContext()、setParent()、setVar()、setItems()方法。
- 调用doStartTag()方法,由于items枚举对象不为空,因此取出一个元素,把它存放在页面范围内,属性名为变量var的值“book”。
- 执行标签主体,打印页面范围内的名为“book”的BookDetails对象的name等属性。
- 执行doAfterBody()方法,由于tems枚举对象不为空,因此取出一个元素,把它存放在页面范围内,属性名为变量var的值“book”。
- 重复执行标签主体和doAfterBody()方法,直到items枚举对象为空,此时doStartTag()会返回一个SKIP_BODY,结束循环。
BookDetails.java(mypack目录下)
package mypack;
public class BookDetails {
private String name;
private String title;
private float price;
private String description;
public BookDetails(String name,String title,float price,String description){
this.name = name;
this.title = title;
this.price = price;
this.description = description;
}
public String getName() {
return name;
}
public float getPrice() {
return price;
}
public String getDescription() {
return description;
}
public String getTitle() {
return title;
}
}
在mytaglib.tld文件中对iterate标签进行定义:
<tag>
<name>iterate</name>
<tag-class>mypack.IterateTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>var</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>items</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
由于在iterate标签的主体中包含如${book.name}等JSP代码,因此把content-type元素设置为“JSP”。由于iterate标签的items属性是一个基于<%=%>形式的Java表达式,因此它的相应<rtexprvalue>子元素的值为true。
OK,现在运行应用,访问localhost:8080/iterate.jsp:
,可以看到,实现了重复执行标签主体内容的功能。
2. 创建和使用greet标签(访问标签主体内容)
greet.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="mm" uri="/mytaglib" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%int size = 3;%>
<mm:greet count="3">
<font size="<%=size++%>">
Hi,${param.username} <br>
</font>
</mm:greet>
</body>
</html>
greet标签有一个count属性,它决定了重复执行标签主体的次数。greet标签的处理类为GreetTag类,该类继承BodyTagSupport类,具有重复执行标签主体和访问缓存在BodyContent对象中的标签主体的执行结果的作用。
mypack/GreetTag.java
package mypack;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.BodyTagSupport;
import java.io.IOException;
public class GreetTag extends BodyTagSupport {
private int count;
private String username;
public void setCount(int count) {
this.count = count;
}
@Override
public int doStartTag() throws JspException {
System.out.println("Call doStartTag()");
if (count > 0) {
return EVAL_BODY_BUFFERED;
} else {
return SKIP_BODY;
}
}
@Override
public void setBodyContent(BodyContent b) {
System.out.println("Call setBodyContent()");
super.setBodyContent(b);
}
@Override
public void doInitBody() throws JspException {
System.out.println("Call doInitBody()");
username = pageContext.getRequest().getParameter("username");
}
@Override
public int doAfterBody() throws JspException {
System.out.println("Call doAfterBody()");
if (count > 1) {
count--;
return EVAL_BODY_AGAIN;
} else {
return SKIP_BODY;
}
}
@Override
public int doEndTag() throws JspException {
System.out.println("Call doEndTag()");
JspWriter out = bodyContent.getEnclosingWriter();
try {
String content = bodyContent.getString(); //得到标签主体的执行结果
System.out.println(bodyContent.getString());
//修改标签主体的执行结果
if (username != null && username.equals("Monster")) {
content = "Go away.Monster!";
}
out.println(content);
} catch (IOException e) {
e.printStackTrace();
}
return EVAL_PAGE;
}
}
当Servlet容器处理greet.jsp中的greet标签时,运行GreetTag对象的主要流程如下:- 调用setPageContext()、setParent()、setCount()方法。
- 调用doStartTag()方法,由于count属性大于0,因此返回EVAL_BODY_BUFFERED。
- 调用setBodyContent()和doInitBody()方法。
- 执行标签主体,把标签主体的执行结果缓存在BodyContent对象中。
- 执行doAfterBody()方法,由于count属性大于1,返回EVAL_BODY_AGAIN。
- 重复执行标签主体和doAfterBody()方法,直到count属性不再大于1.
- 调用doEndTag()方法,读取username请求参数,如果username为“Monster”,就向客户端输出“Go away,Monster!”,否则就输出在BodyContent对象的缓存中的数据。
在mytaglib.tld文件中对greet标签的定义:
<tag>
<name>greet</name>
<tag-class>mypack.GreetTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>count</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
由于在greet标签主体中包含如${paran.username}等JSP代码,因此把<body-content>元素设置为“JSP”。
配置完毕后,运行Web应用,访问localhost:8080/greet.jsp?username=Tomcat,页面显示:
观察IDEA控制台的输出:
以上打印结果显示了Servlet容器调用GreetTag对象的各个方法的流程,其中doAfterBody()方法调用了3次。上图下半部分的结果为3次重复执行标签主体的结果,这些结果都先缓存在了BodyContent对象中,最后由doEndTag()方法一次性地把BodyContent对象中的数据全部输出到客户端。
再访问localhost:8080/greet.jsp?username=Monster:
,
控制台输出:
可以看到,输入Monster也会有对应的3个主体标签缓存在BodyContent对象中,但是它们最后没有被打印到网页。
对于第2小节,如果理不清逻辑,应该回到 8.1 JSP Tag API ,重新温习IterationTag和BodyTag的各个方法的执行逻辑。