星期六, 十二月 02, 2006

Using JSTL 使用

此文中,我们将更详细地讲述不同标记描述文件(Tag Library Descriptor,TLD)里的各种标记。我们会通过例子去说明条件、迭代、URL、SQL及XML等标记的使用。文章的目的就是展示JSTL的主要功能,说明如何使用JSTL以及说明如何使用JSTL去改善JSP的开发。

简单来说,JSTL就是一套可以用在多个方面的自定义功能,JAVA标准组织(Java Community Process,JCP)在JSR-52里面定义到,JSTL提供表达式语言(expression language),流程控制及数据检校等功能,JSTL规范的最后版本可以在JCP的网站上找得到。

JSTL要求运行在支持JSP 1.2版的服务器上,JSTL的主旨就是简化JSP的开发以及提供访问、操作数据的简便方式,使用JSTL可以使用大家的工作更轻松。

JSTL包含了多个TLD和JAR文件,这些TLD覆盖很多功能,我们将一个个说明。首先,我们要重点说一下JSTL最突出的功能,表达式语言。

实际上,表达式语言是由JSR-152专家组所提供的,也就是定义JSP 2.0的专家组。JSR-152和JSR-52很多时是在一起工作的,更甚的是,表达式语言已经成为JSP 2.0的一部分。表达式语言提供访问数据的一种简便方法,它支持运算,支持bean,支持集合,同时也支持类型转换和属性的缺省值。

使用表达式语言

表达式语言是通过 ${expression} 这样的形式来调用的,并且它只能用在属性里面,例如:

<c:if test="${product.price >= customer.limit}">
...
</c:if>


在上面的例子,我们使用表达式语言做一个比较运算,它也可以混合一些静态文本,就象下面的例子:

<c:forEach var="current">
<c:out value="Product-${current}"/>
</c:forEach>


在例子里面,我们枚举出一个集合里面的元素,之后将它加在一个字符串后面输出,结果就象:

Product-1
Product-2
Product-3...

从例子里可以看出,相对于以前的代码,使用JSTL之后,在整个过程变得简单清晰。在以前,你需要定义一个对象,要知道对象的类型,并且要掌握一些脚本语言,而这只是去完成一个简单的操作。

现在,有了JSTL,我们可以使用简单的语法去访问数据,表达式语言是非常适合做这样的工作的。

再举一个例子,我们可以将:

<jsp:useBean id="customer" type="sample.Customer" scope="request"/> ...
Customer Name: <%=customer.getName()%>
...
<% if (customer.getState().equals("CO")){ %>
...
<%}%>


转换成:

Customer Name: ${ customer. name}
<c:if test="${customer. state == param. state}">
...
</c:if>


表达式语言允许直接访问JSP中任何作用域的变量,比如可以用${foo}来代替pageContext.findAttributes("foo")。还有可以能过点或索引来访问bean以及它们的属性,如:

${user.address.city}
${products.[product.id]}
${products.["Part-010"]}


表达式语言提供所有你使用到的运算:==, !=, <,>, <=, >=, &&, ||, ! 。这表达式语言中,它们表示为lt, gt, le, ge, eq, ne,这样可以避免与XML的语法重复。表达式语言也有普通的算术运算同布尔运算。另外,表达式语言还具有自动类型转换的功能,如int value = "${request.myValue}"会自动转换。

在表达式语言是可以提供属性的缺省值的,使用缺省值是为了避免空指针异常,下面是一个例子,

<c:set var="city" value="${user.address.city}" default="N/A" />


现在,我们已经对表达式语言有所了解,下面,让我们看一下与EL相关的操作。

核心操作

在核心标记库里使用到表达式语言。<c:out>标记将表达式语言所计算出的值输出到当前的JSP输出流。这个与JSP中的<%= scripting exp %>相似。比如:

<c:out value="${customer.name}"  default="N/A" />


核心标记库里还可以设置和清除变量。变量的缺省作用域是PAGE。例如我们可以用<c:set var="customer" value=${customer}" />设置一个页面变量customer,再用一个<c:remove var="customer" />去清除它。

现在,能过JSTL,我们可以使用一个标记去捕捉java.lang.Throwable,例如:<c:catch var="myError" />。使用这个标记,可以统一页面的异常处理,但这不意味着代替JSP的错误页面机制。通过使用<c:catch>标记,就可以在页面上控制特定的异常,而不用转到错误页面,其实并不是所有异常都需要转到错误页面。通过使用<c:catch>标记,与用户的交互也变得更友好。

条件操作

在条件操作中使用EL是简化JSP的一种强有力的手段。<c:if>标记,它可以构造简单的条件表达式。下面的例子,访问了一个对象的属性:

<c:if test="${user.visitCount == 1}"
Welcome back!
</c:if>


当然,有IF就一定会有ELSE。如果是"if/then/else"结构的,则使用<c:choose>, <c:when>和<c:otherwise>标记。

看一看下面的例子,我们对一个查询结果集做过了一些处理,之后使用标记去显示正确的信息。

<c:choose>
<c:when test="${count == 0}">
No records matched your selection.
</c:when>
<c:otherwise>
<c:out value="${count}"/> records matched your selection.
</c:otherwise>
</c:choose>


迭代操作

在整个JSTL里面,可能最有用的操作就是迭代操作了。迭代操作的标记有<c:forEach>,<c:forToken>和<x:forEach>。最后一个是XML的迭代操作。下面我们会说到XML的操作,但现在我们还是继续核心操作。

迭代操作支持所有的J2SE集合类型,包括List, LinkedList, ArrayList, Vector, Stack, 和Set。还有java.util.Map对象,如HashMap, Hashtable, Properties, Provider, 和Attributes。还有数组。当使用基本数据类型的数组时,它会包装成对应的基本类。在迭代操作中,会输出两个东西,当前的数据项和迭代的状态。看看下面的例子:

<table>
<c:forEach var="product"
items="${products}"
varStatus="status">
<tr>
<td><c:out value="${status.count}"/></td>
<td><c:out value="${product.name}"/></td>
</tr>
</c:forEach>
</table>


例子中,products是一个集合,当前的数据项入在变量product里面,变量status是当前的迭代状态。是不是很简单。

URL操作

除了迭找操作外,核心标记库里也提供了URL相关的操作,它包括超链接,引入和重定向。可以使用<c:url>去设定一个URL。假如我们想指定一个带参数的URL,并在链接中使用,那就象下面的例子:

<c:url=http://mysite.com/register var="myUrl">
<c:param name="name" value="${param.name}"/>
</c:url>
<a href='<c:out value="${myUrl}"/>'>Register</a>


JSTL中有很强大的资源引入机制,它可以指定绝对的URL,在同一应用的相对的URL,不同应用的相对的URL,还有FTP资源。下面给出一些例子:

绝对URL:<c:import url="http://sample.com/Welcome.html"/>
同一应用中的相对的URL:<c:import url="/copyright.html"/>
不同应用中的相对的URL:<c:import url="/myLogo.html" context="/common"/>
FTP资源:<c:import url="ftp://ftp.sample.com/myFile"/>

通过上面的说明可以看出,<c:import>比<jsp:include>强大得多,但是,也有其它理由去使用<jsp:include>的。在JSTL中,对于引入的资源使用了缓存,但有时候缓存会是没用的。如果你使用<jsp:include>,资源的内容会被读入并写到当前的JspWriter,而且是每访问一次就重读一次。而使用<c:import>,资源的内容则只会读取一次。

本地化操作

JSTL的另一样重要功能就是本地化操作,通过当前请求的,或者环境配置中的参数,就可以简便地实现本地化操作。这个操作是使用J2SE中的ResourceBundle机制去存放各种译文的。JSTL通过设定的区域,去找到并使用相应的ResourceBundle。用<fmt:setLocale>去设置区域,如<fmt:setLocale value="es_Es"/>,value属性为语言代码和国家代码。也可以直接指定一个ResourceBundle:<fmt:bundle basename="ApplicationResource_fr"/>。

一旦设置了区域或者绑定特定的信息,那么<fmt:message>标记就会自动地选择正确的ResourceBundle,使用以下的形式就可以输出正确的信息:

<fmt:message key="welcome">
<fmt:param value="${visitCount}" />
<fmt:message/>


你也可以直接使用< fmt:requestEncoding/>标记去设置当前请求的字符集。

获取和显示文字信息只是本地化操作的一半,而另一半就是格式化同解释日期和数字,不同的地域会有不同的日期和数字格式的。使用<fmt:formatNumber>和<fmt:parseNumber>去格式化数字,金额,百分比数,而且还可以指定格式,就如<fmt:formatNumber value="12.3" pattern=".00"/>会输出"12.30"。

日期和时间的处理使用<fmt:formatDate>, <fmt:timeZone>, <fmt:setTimeZone>, 和 <fmt:parseDate>。

SQL操作

SQL操作允许你直接操作数据源,在MVC模式里面是不提倡这样做的,我更是彻底反对在一个正式产品里这样做。它们只适用于快速开发,原型开发或者是一些小的应用里面,它们是不应该用在一些大型的应用上面的。不过也有许多开发人员想使用它,所以它保留在标准里面。下面我们来看一下SQL操作。

SQL标记可以用来设置数据源,执行查询,访问查询结果,执行更新等。所有的SQL操作都是基于某一数据源的。有几种方式可以设置数据源:在配置文件里设置
sql.datasource参数,在程序中直接设置,或者使用<sql:setDataSource>标记。如下面就设置了一个MySql的数据源:

<sql:setDataSource var="datasource"  driver="org.gjt.mm.mysql.driver" url="jdbc:mysql://localhost/db" />


与JDBC中的DriverManager相似,<sql:setDataSource>也只是一个包装。数据源的属性,它可以是一个JNDI资源,也可以是一个JDBC参数。用<sql:query datasource="${datasource}" ... />这样的方式来访问数据。

我们把这些东西都放在一起,它是会是这样的:

<sql:query var="customer" datasource="${datasource}"
SELECT * FROM customers WHERE state = 'CO' ORDER BY city
</sql:query>
<table>
<c:forEach var="row" items="${customers.row}">
<tr>
<td><c:out value="${row.custName}" /></td>
<td><c:out value="${row.address}" /></td>
</tr>
</c:forEach>
</table>


使用事务和执行更新一样也很简单。例如,我们建立一个事务,并执行几个更新,那就是下面代码:

<sql:transaction dataSource="${dataSource}">
<sql:update>
UPDATE account SET Balance =Balance -? WHERE accountNo = ?
<sql:param value="${transferAmount}"/>
<sql:param value="${accountFrom}"/>
</sql:update>
</sql:transaction>


用<sql:dateParam>标记来设定SQL语句中的类型为日期型的参数的值。

XML操作

JSTL中最后一类操作是XML操作,XML操作也可细分成核心操作,流程控制操作和转换操作。JSTL中的XML操作是基于Xpath的,Xpath是XML操作专用的表达式语言。JSTL所有的XML操作中,用"select"属性去指定XPath表达式,这些信息将由XPath引擎解释。

XML核心操作与JSTL的核心操作相似,它包含<x:out>, <x:set>, 和<x:parse>标记。<x:parse>标记提供将XML文档转换成结构化数据的功能,之后,这些数据就能被XPath引擎解释了。例如,有一个关于书籍的XML文档,我们就可以解释它,并打印出来:

<c:import url="http://oreilly.com/book?id=1234" var="xml"/>
<x:parse source="${xml}" var="bookInfo"/>
<x:out select="$bookInfo/title"/>
<x:out select="$bookInfo/author"/>


XML类流程控制操作与核心类的流程控制操作一样,包括if, choose, when, otherwise, 和 forEach 这些标记,区别只是它们使用XPath表达式语言。当表达式计算出来后,根据一些规则,结果将转换成一个布尔值。这些规则如下:

A number is true if and only if it is neither positive or negative zero nor NaN.
一个数字当且仅当它不是一个正数,也不是一个负零,更不是一个NaN(非数字)时,表达式的值为真。
A node-set is true if and only if it is non-empty.
一个节点当且仅当它不是空节点时,表达式的值为真。
A string is true if and only if its length is non-zero.
一个字符串当且仅当它的长度不为0是,表达式的值为真。

XML转换操作,是通过XSL样式表来转换XML文档,转换的结果输出到当前页面,另一方面也可以其中的结果保存到变量中。完成一个转换就是导入一个XML文档和XSL样式表,之后做一个转换那么简单:

<c:import url="/books" var="xml"/>
<c:import url="/WEB-INF/xslt/bookDisplay.xsl" var="xslt"/>
<x:transform source="${xml}" xslt="${xslt}"/>


如果你在转换中使用到参数,你可以用<x:param>指定参数名称及参数值。

小结

现在,你已经学会了使用JSTL中的标记,这样,开发JSP会更容易更快捷。留意JSTL的发展,当JSTL的正式版本发布以后,JSP服务器的提供厂商就会对此优化。你可以在http://jakarta.apache.org/taglibs/doc/standard-doc/intro.html中得到JSTL的最后实现版本。

没有评论: