传统标记处理器
也许我们很幸运,我们的公司采用了JSP 2.0。但也可能很糟糕,用的是2.0以前的版本。
Tag处理器API
我们只要扩展其中一个类就行了。
一个非常小传统标记处理器
这个例子太极除了,它与SimpleTag处理器的doTag()方法几乎没有什么区别。
有两个方法的传统标记处理器
标记有体时:简单标记和传统标记的比较
这里思考一个问题:如果我们要循环一个体呢?这里是return,我们该如何做呢?后面有答案!
传统标记的生命周期是不同的
这样的生命周期很有意思。
传统标记生命周期取决于返回值
doStartTag()和doEndTag()方法返回一个int。这个int告诉容器下一步要做什么。
对于doStartTag(),容器的问题是“我该计算体吗?”。
对于doEndTag(),容器的问题是“我要继续计算调用页面的余下部分吗?”。
如图:
IterationTag允许体重复执行
TagSupport的UML:
通过重写doAfterBody()方法达到重复执行体的功能。
例子:
简单标记实现:
传统标记实现:
TagSupport的默认返回值
如果没有覆盖返回一个整数的TagSupport生命周期方法,要当心TagSupport方法实现返回的默认值。
需要注意的点,给你一个例子
要注意,doAfterBody()方法在已经处理一次体后才开始运行,看看上面的调用“流程图”。
这里还要注意:容器可以重用传统标记处理器,也就是说,我们要注意全局变量。
实例
还记得第三章的啤酒案例吗?现在来实现动态获取!
开发环境:
初始化页面(init.jsp):
<%@ page import="java.util.*" %>>
<html>
<body>
<% List<String> optionsList = new ArrayList<String>();
optionsList.add("light");
optionsList.add("amber");
optionsList.add("brown");
optionsList.add("dark");
getServletContext().setAttribute("colorList", optionsList);
%>
<jsp:forward page="beer.jsp"/>
</body>
</html>
我们的表单页面(beer.jsp):
<%@ taglib prefix="formTags" uri="http://example.com/tags/forms" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<body>
<form method="post" action="SelectBeer.do">
<p>Select beer characteristics:</p>
Color:
<c:catch>
<formTags:select name="color" size="1"
optionsList="${applicationScope.colorList}"/>
<br><br>
</c:catch>
<input type="submit" value="submit">
</form>
</body>
</html>
我们的简单标记处理器类(SelectTagHandler.java):
package com.example.taglib;
import java.io.IOException;
import java.util.List;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class SelectTagHandler extends SimpleTagSupport{
private List<String> optionsList;
private String name;
private int size;
public void setOptionsList(List<String> optionsList) {
this.optionsList = optionsList;
}
public void setName(String name) {
this.name = name;
}
public void setSize(int size) {
this.size = size;
}
@Override
public void doTag() throws JspException, IOException {
PageContext pageContext = (PageContext) getJspContext();
JspWriter out = pageContext.getOut();
out.println("<select name='"+name+"' size='"+size+"'>");
for(int i=0;i<optionsList.size();i++) {
String s = optionsList.get(i);
out.println("<option value='"+s+"'> "+s+" </option>");
}
out.println("</select>");
}
}
我们的标记文件(demo.tld):
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlib-version>1.2</tlib-version>
<jsp-version>1.2</jsp-version>
<short-name>Forms Taglib</short-name>
<uri>http://example.com/tags/forms</uri>
<description>
An example tab library of replacements for the HTML form tags.
</description>
<tag>
<name>select</name>
<tag-class>com.example.taglib.SelectTagHandler</tag-class>
<body-content>empty</body-content>
<attribute>
<name>optionsList</name>
<type>java.util.List</type>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>size</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
我们的部署文件(DD):
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<welcome-file-list>
<welcome-file>init.jsp</welcome-file>
</welcome-file-list>
</web-app>
实验结果:
select标记还不完善
我们HTML的<select>
标记有更多的属性。
我们需要在标记处理器类添加些能够设置它们的方法:
看完之后…呵呵,tld也要配置这些属性。太恐怖了!!
幸好!JSP规范专门针对这个目的提供了一个API,那就是我们的DynamicAttributes接口。
标记处理器代码使用DynamicAttribute接口
使用3张图搞定这个部分:
首先看看setXxx()该如何解决(因为这里是键/值对,所以用HashMap是最好的选择,当然也可以用别的):
接下来是doTag()方法:
最后是TLD中的配置:
最后就完成了。这简直是神器!
这里指出:这个接口对传统标记也是适用的。
标记文件的dynamic-attributes
这里要注意,tagAttrs是一个页面作用域变量。
一个实例:
首先是.tag文件:
<%@ tag body-content="empty" dynamic-attributes="tagAttrs"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:forEach var="attr" items="${tagAttrs}">
${attr.key} = ${attr.value}
</c:forEach>
其次是测试的.jsp文件:
<%@ taglib prefix="myTags" tagdir="/WEB-INF/tags" %>
<html>
<body>
<myTags:header name="123" value="111"/>
</body>
</html>
实验结果:
帅呆了!!
DynamicAttributes接口的要点
- DynamicAttributes接口允许标记处理器类接收任意数目的标记属性。
- TLD中的标记声明必须包含
<dynamic-attribute>
元素。 - 显式标记属性必须有一个设置方法。
- 一般会使用setDynamicAttribute()方法利用一个hashmap存储动态属性名/值对。
- 标记文件也可以使用动态属性。
- 标记要使用tag指令的dynamic-attributes属性。
- dynamic-attributes的值包含动态属性的一个hashmap。
- 一般地,会使用JSTL forEach定制动作迭代处理这个映射。
如果要访问体内容!
如果我们能直接访问具体的体内容,就可以做很多事情了,比如在表达式中使用体内容,或者以某种方式过滤或修改。为此可以扩展BodyTagSupport而不是TagSupport,这样你就能访问BodyTag接口方法。
基于BodyTag,你会获得两个新方法
注意:默认值改变为EVAL_BODY_BUFFERED。
还有一个需要注意的地方(如果没有体,需要return SKIP_BODY):
传统标记方法的生命周期返回值
标记可以调用其父标记
SimpleTag和Tag都有一个getParent()方法(注意返回值存在区别)。
简单标记的父标记可以是传统标记
简单标记的父标记可以是传统标记的原因,是因为Tag is-like-a JspTag。因此,简单标记不能是传统标记的父标记。
父标记从子标记获取信息
我们知道,只有getParent()方法而没有getChild()方法,也就是说,只能获取到父标记不能得到子标记。那我们如何获取到信息呢?
一个例子:
这样就很巧妙的让父标记知道子标记的信息。下面是测试:
结果与我们想象的一样。
得到一个任意的祖先
使用findAncestorWithClass()方法向上查找到一个特定的祖先,返回第一个找到的祖先(也就是如果这颗树有多个我们要查找的节点,返回深度最深的节点并且这个节点是this的祖先)。