技巧欢迎阅读本期的 Enterprise Java Technologies Tech Tips。下面你将获得使用企业 Java 技术和 API 的技巧,如 Java 2 Platform 和 Enterprise Edition (J2EE) 中的 Java 技术和技巧。
本文讨论:
自定义标记文件
和 JSP Pages 一同使用 Enterprise Beans
这些技巧是使用 Java 2, Enterprise Edition, v 1.4 SDK 开发的。您可以下载 SDK,网址为 http://java.sun.com/j2ee/1.4/download-dr.html。
Tech Tips 中的问题由 Mark Johnson 编写,他是 elucify technical communications 的总裁 ,也是 Designing Enterprise Applications with the J2EE Platform, 2nd Edition 的作者之一。Mark Johnson 主管一个开放论坛讨论这些技巧。
您可以下载这些技巧的示例存档。应用程序的上下文根是 ttfeb2004,index.html 欢迎文件指出如何使用该示例代码。使用下面的代码和/或信息的应遵守许可条款。
自定义标记文件
JSP 页中的自定义标记看上去像 HTML 标记。而自定义标记在运行时会替换为文本,这些文本是由一个与该标记相关联的处理程序类输出的。上一个 Enterprise Tech Tip Using Custom Tag 说明了如何创建一个这样的类。本月的第一技巧将介绍一个新的更容易的方法来实现自定义标记。
在 JSP 2.0 之前,创建自定义标记的惟一方法是将其作为 Java 标记处理程序类来实现。处理程序类开发人员还需要创建一个 TLD 文件,该文件负责为 web 容器描述标记。尽管自定义标记很强大,它也需要编程技巧和深入了解 JSP 页是如何翻译成 HTML 的。
JSP 2.0 标记文件的新特点能够让非编程人员编写可重用的标记。同时也使编程人员的编程生活变得轻松自如。JSP 2.0 语法与 JavaServer Pages Standard Tag Library (JSTL)及其表达式语言(EL)一起使用,不用书写任何 Java 代码就能创建自定义标记。
标记文件是 JSP 页的可重复使用组件。它有诸多优点:
可用来隐藏或删除 scriptlet。
通过引用而不是通过剪切和粘贴来提高代码的重用性。
可使 JSP 页更易书写、视觉更加一致且更易于维护。
可由非编程人员书写。
其语法比 Java 更接近于 HTML,所以 JSP 页看上去更像是用一种语言书写而成的。
可组成高级组件以提高工作效率和开发速度。
自定义标记所使用的 TLD 文件通常可自动生成。
可用来重构现有页。代码的公共部分可以合并到一个标记文件,该文件可在应用程序视图之间共享。
标记文件并不完全替代自定义标记句柄类。当对有相关布局和表示的可复用内容进行封装时,标记文件更为可取。而在 JSP 页中重复使用应用程序逻辑(application logic)时,自定义标记要更胜一筹。例如,页眉和页脚是标记文件的拿手好戏。相比之下,JSTL 中的自定义标记是作为 Java 语言处理程序类实现的。
标记文件细节
实际上,标记文件会被翻译和编译成标记句柄类。标记处理程序和标记处理程序类之间的惟一区别是:标记处理程序是用 JSP 语法写的,而标记处理程序类是用 Java 语言写的。
JSP 2.0 兼容的容器在 Web 存档路径 WEB-INF/tags 中查找标记文件。标记文件也可在 WEB-INF/lib 下打包成 JAR 文件。当 Web 容器为 JSP 页提供服务时,每当该容器遇到与标记文件相关的标记时,该标记文件中 JSP 内容的输出都将被分析并包含在响应流中。 标记文件可定义属性,并可完全访问 JSP 2.0 的表达式语言(EL)。标记文件也可创建在其执行完毕后仍能继续存在的 EL 变量。
标记文件用属性指令声明其属性。下面是选自本技巧所附带的示例代码中的一个例子。标记文件以一个属性指令开始:
<% attribute name="format" required="false" %>
本标记(用于格式化日期)用这行代码告诉容器预计出现一个可能的“format”属性。指令中的“required”属性为强制属性设置为“true”。这些指令允许 web 容器在部署时生成自己的 TLD 文件。
标记通过其属性接受输入。除标记文件生成的输出文本外,标记也可以通过创建 EL 变量“输出”数据。标记文件可以将一个值返回到调用它的页面,如下所示:
<% variable name-given="filesincluded" scope="AT_END" %>
“name-given”可为标记完成后将要在页面中设置的变量提供名称。“AT_END”指定该变量是在标记文件结束设置的。
标记文件示例
本技巧中的示例代码包括一个标记文件,该文件重新实现了为 2002 年 9 月的 Tech Tip“Using Custom Tags”而开发的自定义标记。原始标记可用三种方法对服务器的日期进行格式化:
如果格式未设置或为空,则标记用默认格式显示日期。
如果格式参数以美元符号开头,则标记使用该字符串的其余部分作为一个环境项的名称。然后标记查找这个已命名的环境项并将它的值作为格式。
如果“format”参数包含一个格式字符串(与 java.text.SimpleDateFormat 兼容),那么该字符串就用来格式化日期。
标记文件指定的标记功能略有不同。如果格式参数以一个美元符号开头,则标记查找一个 servlet 上下文初始化参数,而不是查找一个环境项。(表达式语言 JSTL 1.0 并不为访问环境项提供内在的支持。)
本示例标记文件 date.tag 以用于定义该文件中预期属性的指令开始。它也为该标记使用到的其他标记库指定了命名空间。
<% attribute name="format" required="false" %>
<% taglib uri=
"http://java.sun.com/jsp/jstl/core" prefix="c" %>
<% taglib uri=
"http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<% taglib uri=
"http://java.sun.com/jsp/jstl/functions"
prefix="fn" %>
日期标记文件的下一代码块使用 <c:choose> 标记确定该格式字符串。<c:choose> 是 JSP 页中实现 if/then/else 功能所选用的标记。如果格式未设置或为空,则第一个“when”语句会将 EL 变量“format”设置为默认值。代码如下:
<c:choose>
<%-- If format is blank, set default --%>
<c:when test="${format == null or format == ''}">
<c:set var="format"
value="EEEE, d MMMM yyyy 'at' kk:mm:ss z"/>
</c:when>
...
如果“format”非空,那么它将以一个“$”或不以一个“$”开头。在前一种情况下,<choose> 标记的 <otherwise> 语句将去掉打头的“$”,并用有给定名称的上下文参数的内容替代“format”变量。
<c:otherwise>
<%-- Else if format starts with "$",
look up in context param,
and set "format" to its value. --%>
<c:if test="${fn:substring(format,0,1) == '$'}">
<c:set var="format_name"
value="${fn:substringAfter(format,'$')}"/>
<c:set var="format"
value="${initParam[format_name]}"/>
</c:if>
<%-- Otherwise leave it as it is --%>
</c:otherwise>
</c:choose>
注释中指出,如果“format”不以一个“$”开头,它的值保持不变。
至此,EL 变量“format”的值被赋给一个字符串,该字符串将用来格式化日期。 useBean 代码行创建了一个指示当前时间的 Date 对象。fmt:formatDate 方法用给定的“format”格式化该日期:
<%-- Now actually create and format the date --%>
<jsp:useBean id="now" class="java.util.Date"/>
<fmt:formatDate value="${now}" pattern="${format}"/>
至此就完成了标记文件。
使用标记文件更容易。示例页 DatePage.jsp(可链接到本月示例存档中 index.jsp 之外)顶部的一个指令说明存档目录(/WEB-INF/tags)下的所有标记都可以使用“mytags”前缀来访问。该指令如下:
<% taglib tagdir="/WEB-INF/tags" prefix="mytags" %>
现在,就可以很简单的使用 JSP 标记文件了,就好像是在使用任何的自定义标记一样。下面代码节选自示例页 DatePage.jsp:
默认格式下的服务器时间和日期
are <b><mytags:date/></b>.
服务器的时区是
<b><mytags:date format="zzzz"/></b>.
服务器日期是
<b><mytags:date format="M/d/yyyy"/></b>.
服务器时间是
<b><mytags:date format="hh:mm:ss a"/></b>.
<mytags:date> 的每次出现时都会调用该标记文件,并且在输出中该标记会被服务器对应的时间和日期所替代。
这个例子只是抛砖引玉。JSP 标记文件还有许多这里没有提到的选项。要了解更多有关 JSP 标记文件的信息,请阅读 J2EE 1.4 Tutorial 中“Custom Tag in JSP Pages”一章节。
back to top
和 Jsp Pages 一同使用 Enterprise Bean
2004 年 1 月 26 日版的 Tech Tips 中描述了 JSP 2.0 Expression Language (EL)。该技巧中的示例之一介绍了如何使用 EL 变量通过名称对 JavaBean 进行访问。JavaBean 的属性也可以用简单的 EL 表达式访问。这一特点使得 EJB 组件在 JSP 页中非常易于使用。只需将应用程序视图所需的 Enterprise JavaBeans 组件(enterprise beans) 放入已命名的属性中,并让 JSP 页通过 EL 表达式中的名称访问该属性即可。阐明此技术的最好方法就是举例了。
示例代码
本技巧中的示例代码是一个信用卡验证应用程序。信用卡公司有一系列规定可用来验证一个信用卡号的有效性。一个信用卡号可能有效并被透支、已过期或已取消。有效性检查的目的是在数据授权传送之前捕捉数据输入错误。
信用卡号在下面几种情况下有效:
有信用卡类型的正确前缀。
信用卡号有正确的数字位数。
信用卡号通过了 checksum 测试。
信用卡没有过期。
本示例程序使用了三个组件:
一个有状态的会话 bean CreditCardLocal,它代表信用卡数据,并执行验证。
一个 servlet Feb2004Servlet,它在会话范围中创建该 bean 的一个实例,这样每个用户都有一个实例。
一个 JSP 页 ValidateCard.jsp,它收集数据并显示验证结果。
该代码是这样工作的:index.jsp 窗体上的一个链接指向 servlet,该 servlet 创建会话 bean 句柄并将其置于会话上下文中。然后 servlet 将请求转发给 URL jsp/ValidateCard.jsp,由它处理卡号输入和验证。用户完成数据输入后该窗体就回递到它自己。在一个真实的应用程序中,当信用卡正确验证后,窗体会自动转发到采购流程中的下一步。
servlet 中的代码很简单明了。它只是创建了一个 CreditCardLocal 类型的有状态会话 bean,并以“creditCard”之名把它置于一个 HttpSession 属性之中。当该 servlet 被调用时,如果这样一个变量已经存在,那么该 servlet 就会删除前一个实例。
CreditCardLocal creditCard = (CreditCardLocal)
req.getSession().getAttribute("creditCard");
// Remove it if it exists--we're starting over.
if (creditCard != null) {
try {
creditCard.remove();
} catch (Exception e) {
System.err.println(
"Exception removing credit card: " + e);
}
System.err.println("INFO: Removed previous card.");
}
// Create new enterprise bean reference in session scope
try {
InitialContext ctx = new InitialContext();
CreditCardLocalHome cclh =
(CreditCardLocalHome)ctx.lookup(
"java:comp/env/ejb/CreditCard");
String cardName = req.getParameter("cardName");
String cardType = req.getParameter("cardType");
String cardNumber = req.getParameter("cardNumber");
String cardDate = req.getParameter("cardDate");
creditCard = cclh.create(
cardName, cardNumber, cardType, cardDate);
req.getSession().setAttribute(
"creditCard", creditCard);
} catch (Exception e) {
throw new ServletException(e);
}
RequestDispatcher rd = req.getRequestDispatcher(
"/jsp/ValidateCard.jsp");
rd.forward(req, res);
注意上面代码的最后两行。在创建完 enterprise bean 引用之后,servlet 就将请求转发给 ValidateCard.jsp 页,该页处理输入和验证。
该页的第一部分会将可能已被投递到该页的任何参数拷贝到该 enterprise bean 的对应属性中。注意 <c:set> 标记是如何使用“target”和“property”来访问给定名称的 enterprise bean 的属性的。
<!-- Copy parameters to creditCard properties -->
<c:set target="${creditCard}" property="name"
value="${param['cardName']}"/>
<c:set target="${creditCard}" property="type"
value="${param['cardType']}"/>
<c:if test="${param.cardNumber != null and
param.cardNumber != ''}">
<c:set target="${creditCard}" property="number"
value="${param.cardNumber}"/>
</c:if>
<c:set target="${creditCard}"
property="expirationDateStr"
value="${param['cardDate']}"/>
如果请求参数未设置(页面第一次显示时,没有设置请求参数),表达式如 ${param['cardName']} 得到的值为空,那么对应的 bean 属性也被设置为空。
ValidateCard.jsp 页的下一部分从用户那里收集数据。当用户单击提交按钮时,这些数据被回递同一个页面。这部分的代码有一些有趣的特点。窗体总是回递到它自己,于是下面定义的每个输入都包括一个属性,该属性将输入值设置为信用卡的当前值。
窗体的第一部分使用了一个表格来控制布局和本地背景颜色,并且为窗体属性“cardName”定义了一个输入元素。
<input type="text" name="cardName" size="32"
value="${creditCard.name}">
cardName 的值被赋给 EL 表达式 ${creditCard.name},该表达式等同于以下代码:
<%= ((CreditCardLocal)request.getSession().
getAttribute("creditCard")).getName() %>
如果窗体之前已被提交过,那么该域输入过的任何名称都将显示在 cardName 输入框中。
下一个输入是一组单选按钮,一个按钮对应一种信用卡类型。信用卡对象有一个类型为 CCInfo 的 “info”属性。该对象是一个含有所有有效信用卡类型信息的 java.util.HashMap。该 map 的核心是每个信用卡的符号名称,例如,“mc”代表万事达信用卡(MasterCard)。值为 CCDesc 类型,是用于描述卡的对象。(要了解详情,请参阅代码示例中的格式化源代码)。<c:forEach> 循环会在接收到的信用卡类型中来回循环,并为每个类型创建一个单选按钮。
<p>
<b>Type of card:</b>
<c:forEach var="item" items="${creditCard.info}">
<input type="radio" name="cardType"
value="${item.key}"
${(item.key eq creditCard.type) ? 'checked' : ''}>
${item.value.description}
</c:forEach>
如果在 creditCard 对象中正在创建的单选按钮是当前选中按钮,那么表达式 ${(item.key eq creditCard.type) ? 'checked' : ''} 就得到字符串“checked”。该循环以可扩展的方式提供了一个隐藏的好处。如需添加一个新的信用卡类型,只需向 CreditCard 的 CCInfo 中添加一个该类型的 CCdesc 描述即可。在下次 ValidatePage.jsp 执行时,该循环就会接收这个新的信用卡类型并将其加入到列表中。
下面两个输入包括信用卡号和过期日期:
<b>Card number:</b>
<input type="text" name="cardNumber" size="24"
value="${creditCard.number}"><p>
<b>Expiration date (mm/yyyy):</b>
<input type="text" name="cardDate" size="10"
value="${creditCard.expirationDateStr}">
再次注意:该值被赋给了相应的 enterprise bean 属性值。
如果合适的话,窗体的第三部分会显示一个错误信息,或者一个说明该卡有效的信息。这部分含有两个 <c:when> 标记和一个 <c:otherwise> 标记。当信用卡号为空时,第一个标记用来防止显示错误信息。
<c:choose>
<c:when
test=
"${creditCard.number == null || creditCard.number == ''}">
<!-- No number, so no need to complain
that it's not valid -->
</c:when>
当信用卡号有效时,第二个 <c:when> 标记显示一个成功信息。
<c:when test="${creditCard.valid}">
The following card is valid:
<table border="0">
<tr><th align="left">Name:</th>
<td>${creditCard.name}</td>
</tr>
<!-- and so on... -->
</c:when>
此时,一个真正的应用程序就可能将用户的浏览器转递到一个窗体,该窗体收集记账地址信息(billing address infomation)。这里要注意:对 <c:if> 的测试是一个布尔值。所以表达式 ${creditCard.valid} 必须是布尔型的。本属性的存取器(accessor)是 CreditCardLocal.isValid(),这是因为布尔值的 get 属性存取器的命名习惯是使用“is”而不是“get”。
最后一个标记 <c:otherwise> 是针对的是信用卡号无效的情况。
<c:otherwise>
<hr>
<font color="red">This number is not valid.</font>
<i>Problem</i>: ${creditCard.validityMessage}
Please correct the problem and try again.
</c:otherwise>
</c:choose>
CreditCardLocal.getValidityMessage 返回一条用户友好的错误信息解释信用卡无效的原因。
本示例中有趣和有用的地方是 enterprise bean 界面可存储在应用程序的会话(或其他)上下文中,且可以通过 JSP EL 表达式中的名称引用。因为在 scriptlet 中并没有正在实现应用程序逻辑,所以使用此技术可以改进封装。它也使得 JSP 页更易创建、理解和维护。
运行示例代码
下载这些技巧的示例存档。应用程序的上下文根(context root)是 ttfeb2004。下载的 ear 文件也含有示例的全部源代码。
可以使用部署工具程序(deploytool program)或管理控制台(admin console)在 J2EE 1.4 Application Server 上部署此应用程序存档(ttfeb2004.ear)。也可以通过运行 asadmin 命令进行部署:
asadmin deploy install_dir/ttfeb2004.ear
用安装 war 文件的路径替代 install_dir。
要访问该应用程序,请链接:http://localhost:8000/ttfeb2004。
对于 J2EE 1.4 Application Server 之外的一个 J2EE 1.4 相容的实现,可使用自己 J2EE 产品的部署工具在自己的平台上部署该应用程序。
当启动该应用程序时,将会看到如下一个页面(只显示了该页面的一部分):
企业Java技术开发技巧2则
80酷酷网 80kuku.com