user:getUserShoppingList
标记会执行,并将其自己存储为名为 userShoppingList
的 pageContext
属性。product:getProductData
标记将检索属性值并从 getUserShoppingList
对象调用方法 getProductIds()
。
清单 4. getUserShoppingList
<user:getUserShoppingList userId="13" id="userShoppingList"/>
...
<products:getProductData productData="userShoppingList">
<!-- Some formatting template -->
...
</products:getProductData>
|
标记 getUserShoppingList
的标记库描述符可能类似清单 5:
清单 5. getUserShoppingList 库描述符
<tag>
<name>getUserShoppingList</name>
<tagclass>com.taglib.UserShoppingListTag</tagclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>userId</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>id</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
|
一旦 getShoppingList
标记(可能是在 doEndTag()
方法中)完成了其所有的处理,它的标记处理程序将含有下面这行:
pageContext.setAttribute(getId(),this);
|
getId()
检索标记(userShoppingList
)中设置的标识,this
引用整个类本身。
一旦在 pageContext
中存储了 userShoppingList
标记,getProductData
标记处理程序类就可以使用用 productData
属性传递给 userShoppingList
标记的标识来访问它。getProductData
标记的处理程序类将使用该标识(用方法 getProductData()
检索的)以找到 pageContext
中的属性并将其类型强制转换成 UserShoppingListTag
对象。清单 6 中的代码声明一个称为 productList
的 List
并调用用户类方法 getProductIds()
来抽取产品列表。
清单 6. 抽取产品列表
UserShoppingListTag userShoppingList =
(UserShoppingListTag)pageContext.getAttribute(getProductData());
List productList = (List)userShoppingList.getProductIds();
|
在页面和会话上下文中引用标记
存储整个标记对象对于让其它标记自由支配该对象内的属性和方法是有用的。但是,您也可以选择只暴露对象的部分。实际上,前面的示例的局限在于产品标记期望产品列表来自 UserShoppingListTag
对象。
或许一个较好的方法是让用户标记 getShoppingList
只将产品列表导出到 pageContext
供任何其它标记使用。这样做的好处是其它标记不需要预先知道有关返回产品列表的方法,甚至也不需要知道产生列表的对象。如果存储在 pageContext 中的数据的标签由属性 productData
提供,那么产品标记的标记处理程序将如下所示:
List productList = (List)pageContext.getAttribute(getProductData());
|
这一技术将适用于购物列表标记以及其它类似于从给定的类别调用销售项目或产品的标记。
如果数据存储在会话中,那么它将类似于下面的样子:
HttpSession session = pageContext.getSession();
List productList = (List)session.getAttribute(getProductData());
|
遍历示例定制标记
在下面的参考资料部分中,您会发现一个到一组示例定制标记的链接。请下载该代码软件包并查看一下。在本文的最后部分,我将使用这些示例向您演示使用定制标记所带来的好处。
示例标记概述
样本标记背后的想法十分简单、直接。总共有七个定制标记、一个 JSP 文件以及一个标记库描述符(.tld 文件)。
这些标记相互协作以显示三项内容之一:一个欢迎屏幕、一个登录屏幕或者(如果出错的话)一条登录错误消息。(实际的登录过程并不是由标记处理的 — 为了简单起见,我们将通过设置会话和请求变量来模仿该过程,否则需要用 servlet 或 JavaBean 组件来进行设置)。
如清单 7 所示,有两个主要的标记:getUserData
和 nestedLogin
。第一个标记获取用户,第二个标记根据用户 John Q. Citizen 是否登录来显示适当的 HTML。
这两个标记代表了一种方法,即一个标记 nestedLogin
可以访问另一个标记 getUserData
,后者存储在 pageContext
中。
nestedLogin
标记还演示了在一个标记内嵌套其它标记的过程;它允许其它标记访问它的方法。三个其它的标记 isLoggedInHTML
、notLoggedInHTML
和 logInFailureHTML
代表了三个可能的显示。这三个标记提供对 nestedLogin
标记中的特性的访问;nestedLogin
将确定并显示适当的代码块。
剩下的两个标记 getUserName
和 getLoginError
演示了两种使用嵌套标记的方法:作为简单的主体内容和作为访问祖先标记中方法的方式。它们都不覆盖它们的祖先标记;它们只是从其祖先标记获取数据 — 也就是用户的名称和登录错误(如果设置了这两者的话)。
清单 7. 示例 JSP 代码
<HTML>
<HEAD>
<TITLE>Custom Tag Communication</TITLE>
</HEAD>
<BODY bgcolor="#ffffff">
<!-- LOAD TAG LIBRARY -->
<%@ taglib uri="goforit.tld" prefix="goforit" %>
<!-- SET THE USER -->
<goforit:getUserData id="user"/>
<!-- SET THE LOGIN HTML BASED ON WHETHER OR NOT THE USER IS LOGGED IN -->
<!-- ONE OF THE NESTED NODES WILL BE DISPLAYED ACCORDINGLY -->
<goforit:nestedLogin userDataID="user">
<goforit:isLoggedIn>
<!-- THE HTML IN THIS NODE IS DISPLAYED IF THE USER IS LOGGED IN -->
</goforit:isLoggedIn>
<goforit:notLoggedIn>
<!-- THE HTML IN THIS NODE IS DISPLAYED IF THE USER IS NOT LOGGED IN -->
</goforit:notLoggedIn>
<goforit:loginFailure>
<!-- THE HTML IN THIS NODE IS DISPLAYED IF THERE WAS A LOGIN ERROR -->
</goforit:loginFailure>
</goforit:nestedLogin>
</BODY>
</HTML>
|
标记库描述符
清单 8 演示了两个主要标记的描述符。请注意必需的 getUserData
的 id
属性和 nestedLogin
的 userDataID
属性。它们用于在 pageContext
中注册用户对象并在其它类中检索该对象。
清单 8. 标记描述符
<tag>
<name>getUserData</name>
<tagclass>com.taglibrarycommunication.taglib.GetUserDataTag</tagclass>
<info></info>
<bodycontent>JSP</bodycontent>
<attribute>
<name>id</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
<tag>
<name>nestedLogin</name>
<tagclass>com.taglibrarycommunication.taglib.NestedLoginTag</tagclass>
<info></info>
<bodycontent>JSP</bodycontent>
<attribute>
<name>userDataID</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
|
标记处理程序
下面的章节演示了类之间的通信的一些重要方面。
GetUserData
这个标记处理程序类检索其它标记使用的用户数据。
注:因为我们的标记不处理登录过程,所以构造了这些标记来查找存储在会话中的登录用户以及存储在请求中的错误。因为我们实际上没有让人们登录,所以这个标记的开始部分通过将某人设置到对话或将错误设置到请求来模拟三种可能的方案。清单 9 中所示的代码控制这三种方案 — 如果将下面的两行都注释掉,那么就认为用户未登录:
清单 9. 控制示例方案的代码
// UNCOMMENT TO MIMIC A LOGGED IN USER STORED IN SESSION
//session.setAttribute("user","John Q. Citizen");
// UNCOMMENT TO MIMIC LOGIN ERROR STORED IN REQUEST
//pageContext.getRequest().setAttribute("loginError","Password incorrect");
|
注册用户数据的关键在 doStartTag()
方法中:
清单 10. doStartTag()
public int doStartTag() {
session = pageContext.getSession();
... SET VARIOUS PROPERTIES BASED ON THE USER ...
// THIS IS THE LINE THAT SAVES THIS CLASS TO pageContext
pageContext.setAttribute(id,this);
return SKIP_BODY;
}
|
NestedLoginTag
这个类显示在清单 11 中,它根据用户是否登录或者是否出错来确定将三个特性中的哪一个返回给客户机。嵌套在 JSP 页面中的类内的其它标记确定这三个特性的值。NestedLoginTag
通过首先获取在前一个标记中注册到 pageContext
中的用户来确定显示哪个标记。然后它试图检索用户名和任何可能出现的错误。如果这两项都是空白,它就假定用户没有登录。如果有错误消息,无疑出错了。如果设置了用户的名称,那么用户就成功地登录了。
清单 11. NestedLoginTag
// PULL THE userData OUT OF THE pageContext
// WITH THE userDataID SUPPLIED THROUGH THE CUSTOM TAG
GetUserDataTag userData =
(GetUserDataTag) pageContext.getAttribute(getUserDataID());
// SET userName AND loginError FROM VALUES IN userData OBJECT
setUserName(userData.getUserName());
SetLoginError(userData.getLoginError());
...
if (getUserName()!="" &&
getLoginError()==""){
// IF userName IS SET PERSON IS LOGGED IN
pageContext.getOut().print(getIsLoggedInHTML());
} else {
if (getLoginError()=="")
// IF NO userName SET BUT NO loginError SHOW LOGIN
pageContext.getOut().print(getNotLoggedInHTML());
else
// IF loginError SHOW LOGIN AND ERROR
pageContext.getOut().print(getLogInFailureHTML());
}
|
IsLoggedInTag、NotLoggedInTag 和 LogInFailureTag
清单 12 中的三个标记嵌套在 nestedLogin
内。它们的功能都相似,但却设置不同的 nestedLogin
特性。其主体内容允许 JSP 开发人员访问并设置 nestedLogin
的 isLoggedInHTML
、notLoggedInHTML
和 logInFailureHTML
特性。
清单 12. 访问父标记
// THIS LINE ACCESSES THE PARENT CLASS NestedLoginTag
NestedLoginTag parent = (NestedLoginTag) getParent();
if (parent != null){
BodyContent bc = getBodyContent();
String body = bc.getString();
// SET THE isLoggedInHTML PROPERTY OF THE PARENT CLASS
// WITH THE BODY SUPPLIED THROUGH THE CUSTOM TAG
parent.setIsLoggedInHTML(body);
}
|
GetUserNameTag 和 GetLoginErrorTag
清单 13 中的标记仅仅从 pageContext
检索 userData
对象并获取它的一个特性。
清单 13. 访问存储在 pageContext 中的对象
GetUserDataTag userData =
(GetUserDataTag) pageContext.getAttribute(getUserDataID());
...
if (userData.getUserName() !=null){
pageContext.getOut().print(userData.getUserName());
}
|
结束语
JSP 页面将客户端显示与服务器端逻辑分开,并让可能不是 Java 程序员的 Web 设计人员能够充分利用 Java 技术的功能。通过使用定制标记,您可以给予从事 Web 应用程序各层开发的开发人员更多选择,并为 Web 开发人员提供一种面向对象的方法,以鼓励重用代码模块和标记。一旦您检验了本文所包含的样本标记库,您应该可以开始在您自己的应用程序内使用定制标记了。