我们希望为 mapDefine
标签指定三个属性,如下所示:
id |
新 scriptlet 变量的名字。 |
scope |
新 scriptlet 变量所在的范围。 |
type |
新 scriptlet 变量的类型 (HashMap 、FastHashMap 、TreeMap 或者 FastTreeMap )。 如果 type 设置为 hash ,那么就会创建一个 HashMap 。如果 type 设置为 fasthash ,那么将创建 FastHashMap 。 |
在 JSP 页中使用这个标签时,它看起来将像下面这样:
<map:mapdefine type="hash" scope="session" id="editParams"> ... </map:mapdefine>
这个标签将在会话范围内创建一个名为 editParams
的 HashMap
。
为了在标签处理程序中创建属性,需要定义相应的 JavaBean 属性。 因此,每一个属性在标签处理程序中都有对应的 setter 方法,如下所示:
public class MapDefineTag extends TagSupport { ... private String type = FASTTREE; private String id; private String scope; public void setType(String string) { type = string; } public void setId(String string) { id = string; } public void setScope(String string) { scope = string; }
转换引擎将用硬编码的配置数据或者运行时表达式设置这个标签的属性。 我们将在 第 4 步:在 TLD 文件中定义属性 中对此做更详细的讨论。
在 第 5 步:实现 doStartTag() 方法 中,我们将在标签处理程序的 doStartTag()
方法中使用这些属性。
第 4 步:在 TLD 文件中定义属性
就 像上一小节中所做的那样,通过声明 JavaBean 属性定义自定义属性,然后在 TLD 文件中声明这些属性。 每一个 JavaBean 属性都必须与相应的自定义标签属性相匹配。 在 TLD 中定义的属性必须匹配 JavaBean 属性,不过却可以有与标签属性不匹配的 JavaBean 属性。
下面是 MapDefineTag
的属性声明:
<tag> <name>mapDefine</name> <tag-class>trivera.tags.map.MapDefineTag</tag-class> <body-content>JSP</body-content> ... <attribute> <name>id</name> <required>true</required> <rtexprvalue>false</rtexprvalue> <description>The id attribute</description> </attribute> <attribute> <name>scope</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description>The scope attribute</description> </attribute> <attribute> <name>type</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description> Specifies the type of map valid values are fasttree, fasthash, hash, tree </description> </attribute> </tag>
name
元素指定属性的名字。required
元素指定属性是否是必需的(默认值是 false
)。rtexprvalue
元素表明属性是硬编码了转换时的值还是允许使用运行时 scriptlet 表达式。
记住,MapDefineTag
类必须为前面描述的每一个属性定义一个 JavaBean 属性,我们在 第 3 步:在标签处理程序 Java 类中创建属性 中完成这个任务。
第 5 步:实现 doStartTag() 方法
标签开始时调用 doStartTag()
方法 —— 从开发人员的角度看,这是当引擎遇到 <map:mapdefine></map:mapdefine>
时发生的。如果 doStartTag()
返回 SKIP_BODY
,那么将不处理标签正文。 如果它返回一个 EVAL_BODY_INCLUDE
,那么将处理正文。
MapDefine
类的 doStartTag()
方法完成以下工作:
type
属性确定要创建的 map 的属性。 scope
属性确定新的 map 对象放在什么范围内。 id
属性确定新 map 对象要放入的范围的名字。 让我们更详细地分析这个过程。MapDefine
类检查 type
属性是设置为 FASTTREE
、HASH
、TREE
还是 FASTHASH
。然后创建相应的 map,如下所示:
/* String constants for the different types of maps we support */ public static final String FASTHASH = "FASTHASH"; public static final String FASTTREE = "FASTTREE"; public static final String HASH = "HASH"; public static final String TREE = "TREE"; /** The map we are going to create */ private Map map = null; /** The member variable that holds the type attribute */ private String type = FASTTREE; ... public int doStartTag() throws JspException { /** Based on the type attribute, determines which type of Map to create */ if (type.equalsIgnoreCase(FASTTREE)) { map = new FastTreeMap(); } else if (type.equalsIgnoreCase(HASH)) { map = new HashMap(); } else if (type.equalsIgnoreCase(TREE)) { map = new TreeMap(); } else if (type.equalsIgnoreCase(FASTHASH)) { map = new FastHashMap(); }
然后,用 id
和 scope
属性将 hashmap 以一个给定的名字设置到一个给定范围中:
private String id; private String scope; public int doStartTag() throws JspException { ... if (scope == null){ pageContext.setAttribute(id, map); }else if("page".equalsIgnoreCase(scope)){ pageContext.setAttribute(id, map); }else if("request".equalsIgnoreCase(scope)){ pageContext.getRequest().setAttribute(id, map); }else if("session".equalsIgnoreCase(scope)){ pageContext.getSession().setAttribute(id, map); }else if("application".equalsIgnoreCase(scope)){ pageContext.getServletContext().setAttribute(id, map); } return EVAL_BODY_INCLUDE; }
如果范围属性是 null
,那么 map 将放入页范围。否则,参数将放入通过 scope
属性传递的范围名所对应的范围中。
到目前为止,我们已经有一个非常简单的标签,它有三个属性:id
、scope
和 type
。 我们将用给定的名字将 map 放到一个范围中。但是,我们还有一件事没做,就是声明 scriptlet 变量。 为了做到这一点,需要向 TLD 再添加一项,这就是我们在下一小节中所要做的事。
要 理解 scriptlet 变量,必须理解 TLD 文件的作用。这个文件基本上是元数据的一个储存库,当 JSP 页转换为 servlet 时,标签会使用这些元数据。 在生成的 servlet 中,scriptlet 变量成了本地变量。 要让 JSP 转换引擎知道这些变量应当声明的类型,需要像下面这样在 TLD 文件中增加项:
<variable> <name-from-attribute>id</name-from-attribute> <variable-class>java.util.Map</variable-class> <scope>AT_BEGIN</scope> </variable>
将上面的代码片断放在 TLD 文件中 body-content
元素之后、attribute
元素之前。 在 variable
元素下面,我们声明了三个子元素:name-from-attribute
、variable-class
和 scope
。 name-from-attribute
指定 id
属性的值是转换引擎将要定义的 scriptlet 变量的名字。variable-class
是转换将要定义的变量的类类型。scope
指定变量什么时候可用:它可以嵌套到标签的正文中 (NESTED
)、在标签结束后 (AT_END
)、或者在标签的开始时 (AT_BEGIN
)。我们使用 AT_BEGIN
这一范围,它意味着变量的范围将是从标签的开始到当前 JSP 页的结束。
现在了解了如何构建简单自定义标签。在下一小节中,我们将分析标签的生存周期方法,以了解 JSP 页实际运行时所发生的情况。