JSP 1.X编码规范
由于JavaServer Page
(JSP
)技术广泛应用于WEB
应用程序,如同许多Java
程序员一样,许多JSP
程序员和WEB
应用开发者在开发和维护这些应用程序时面临一些难以抉择的问题:“我们该如何编写更易于阅读、编写和维护的JSP
代码”。
在本文中,我们提出了一套JSP
(version 1.1 and 1.2
)标准规范,使用WEB
组件的软件项目都应该遵守这个规范。本文以
Java编码规范为模板来确定应该提及在与JSP技术相关的编码规范中的各个重要方面。特别地,本文提到了文件的命名和组织、缩进排版、注释、指令、声明、Scriptlets、表达式、空格、命名规范,以及编程惯例。因为这是我们第一次提出一套JSP编码规范,我们非常希望获得大家的反馈。请将意见和建议发送至
jsp-codeconv-comments@sun.com
。
1.
为什么需要编码规范
编码规范对于程序员和WEB
内容开发者来说非常重要,原因有数个:
1
)编码规范可以提高软件的可读性
2
)编码规范可以减少培训工作量
3
)编码规范可以推动组织或团队的标准化
2.
文件命名和存放位置
文件命名能够让一些工具和WEB
容器识别文件的类型和采用相应处理方法。下表列出了建议使用的文件后缀名和存放位置。
文件类型
|
文件后缀
|
建议存放位置
|
JSP technology
|
.jsp
|
<context root>/<subsystem path>/
|
JSP fragment
|
.jsp
|
<context root>/<subsystem path>/
|
.jspf
|
<context root>
/WEB-INF/jspf/
<subsystem path>/
| |
cascading style sheet
|
.css
|
<context root>
/css/
|
JavaScript technology
|
.js
|
<context root>
/js/
|
HTML page
|
.html
|
<context root>/<subsystem path>/
|
web resource
|
.gif, .jpg
, etc.
|
<context root>
/images/
|
tag library descriptor
|
.tld
|
<context root>
/WEB-INF/tld/
|
对以上表格需要说明的是:
第一,
<context root>
是
WEB
应用程序上下文的根目录(或
.war
文件里的根目录)。
第二,
<subsystem path>
用于提供动态和静态
WEB
页面内容的准确的逻辑分类,对于小型的
WEB
应用程序,它可能是一个空字符串。
第三,我们将可能包含在其他
JSP
页面中的
JSP
页面称为
JSP fragment
。注意,在
JSP 2.0
中,“
JSP fragment
”被术语“
JSP segment
”
替换。
JSP fragment
文件可以用
.jsp
和
.jspf
作为文件名后缀,而且应该被放在
/WEB-INF/jspf
文件目录中,或者和其它静态内容放在一起。因为不是完整的页面,
JSP fragment
文件应该使用
.jspf
作为文件名后缀,并且应该放在
/WEB-INF/jspf
文件目录中。
第四,尽管
JSP
规范认为
.jspf
和
.jsp
都可以作为
JSP fragment
文件名后缀,但是我们建议使用
.jspf
,就如
.jsf
被
JavaServer Faces
规范所采用。
最后,将标签库描述符文件(tag library descriptor
)和其他非公开的内容放在
/WEB-INF/
中或其下的子目录中通常是好的习惯。在这种情况下,这些内容对于客户端是访问不到的和不可见的,因为
WEB
容器不会对这个目录下的文件提供访问服务。
在部署描述符文件(
web.xml
)中
welcome-file
元素中声明首页文件(
welcome file
),如果是动态内容,其文件名应该是
index.jsp
,如果是静态内容就应该用
index.html
。
一旦我们要将
JSP
文件国际化,我们建议将
JSP
页面分组分别存放在各自的本地目录中。例如,
US English
版的
index.jsp
应该以
/en_US/index.jsp
方式保存,同样日语版的
index.jsp
文件应存为
/ja_JP/index.jsp
。
Java Tutorial
一书提供了通常情况下如何将
Java
代码国际化的方法的信息,
Designing Enterprise Applications with the J2EE Platform
一书也有WEB
应用程序国际化的内容。
3.
文件组织
有良好组织结构的源码文件不仅易读,而且在文件中也能很快就能找到需要的信息。在这一部分,我们将介绍JSP
文件和标签库描述符(tag library descriptor
)文件的组织结构。
3.1
JSP
文件/JSP Fragment
文件
一个JSP
文件包含按列出顺序排列的以下部分:
1)
开头注释
2) JSP
页面指令(JSP Page Directive
)
3)
可选的标签库(tag library
)指令
4)
可选的JSP
声明
5) HTML
和JSP
代码
3.1.1.
开头注释
一个JSP
文件或片断(fragment
)文件以服务端注释开始:
<%--
- Author(s):
- Date:
- Copyright Notice:
- @(#)
- Description:
--%>
由于在JSP
页面翻译转换时,这种注释被删除,所以它仅在服务端可见。注释中有作者、日期、版权声明、标志符和对WEB
开发者的该JSP
页面的描述信息。字符组合“@(#)
”是用来为某种特定的程序识别标志符的开始位置的。虽然这类程序很少被用到,但是这个字符串没有什么坏处。另外,某些时候这个字符串后被一些控制程序自动添加“$Id$
”作为标志信息。描述信息部分提供了JSP
页面的用途的简要信息。这部分不能超过一段。
在某些情况下,为了证实真实性和法律的用途,开头部分注释需要在翻译转换并产生结果后保留并传送到客户端。将开头注释分为两部份就可以实现这个要求;第一部分是客户端注释:
<!--
- Author(s):
- Date:
- Copyright Notice:
-->
第二部分是比较简短的服务端注释:
<%--
- @(#)
- Description:
--%>
3.1.2.
JSP
页面指令(JSP Page Directive
)
JSP
页面指令定义了JSP
页面在翻译转换时需要的一些属性。JSP
规范没有强制约束在一个JSP
页面中可以定义多少个JSP
页面指令。于是下面两个代码例子是等同的(除了第一个例子在结果输出时会多两个空行):
例1
:
<%@ page session="false" %>
<%@ page import="java.util.*" %>
<%@ page errorPage="/common/errorPage.jsp" %>
任何指令的长度超过JSP
页面的通常的宽度,例如JSP
页面指令,那么这个指令应该分成多行:
例2
:
<%@ page session="false"
import="java.util.*"
errorPage="/common/errorPage.jsp"
%>
通常情况下,例2
是比较好的定义JSP
页面指令的方法。一个例外,当需要引入多个java
包到JSP
页面时,导致import
属性很长:
<%@ page session="false"
import="java.util.*,java.text.*,
com.mycorp.myapp.taglib.*,
com.mycorp.myapp.sql.*, ..."
...
%>
这种情况下,首选的做法是将指令做如下拆分:
<%-- all attributes except import ones --%>
<%@ page
...
%>
<%-- import attributes start here --%>
<%@ page import="java.util.*" %>
<%@ page import="java.text.*" %>
注意:通常使用import
命令要遵循Java
编码规范。例如,当同一个包中的少于(含)3
个类被引用,import
应该对每个类逐个引入,而不是引入整个包。超过3
个类时,由开发者来确定是每个类逐个引入还是使用“.*
”符号来引入整个包。前者能够帮助更容易的确定外部类,尤其是当你试图查找一个有bug
的类,或想理解JSP
页面是如何和Java
代码是如何交互的。例如,在不了解如下所示引入的包的情况下,WEB
开发者将不得不将这些包查个遍才能找到某个用户类:
<%@ page import="com.mycorp.bank.savings.*" %>
<%@ page import="com.thirdpartycorp.cashmanagement.*" %>
<%@ page import="com.mycorp.bank.foreignexchange.*" %>
...
在这种情况下,JSP页面虽然简洁,但是就很难定位类。通常情况下,如果一个JSP页面有太多的import指令,那么页面中就很可能包含较多的Java代码。比较好的选择就是尽量使用JSP标签(本文后面会有说明)。
3.1.3.
可选的标签库(Tag Library
)指令
标签库指令声明了JSP
页面中使用的用户标签库。一行使用一个指令声明。在JSP
页面中多个标签库指令应该堆叠在一起放在同一位置:
<%@ taglib uri="URI1" prefix="tagPrefix1" %>
<%@ taglib uri="URI2" prefix="tagPrefix2" %>
...
就如page指令一样,如果标签库指令的长度超过JSP页面的标准长度(80个字符),就应该拆分多行:
<%@ taglib
uri="URI2"
prefix="tagPrefix2"
%>
只有要用到的标签库才能被声明。
从JSP 2.0之后,JSP标准标签库(JSTL)被强烈推荐应用于WEB应用程序中以帮助减少页面中JSP Scriptlets的使用。通常,使用JSTL的页面更易于阅读和维护。
3.1.4.
可选的JSP
声明
“JSP声明”声明了JSP页面拥有的方法和变量。这些方法和变量与Java编程语言中声明的方法和变量没有不同,因此,相关的编码规范也同样被遵循。声明首选被包含在一个<%! ... %>JSP声明块中,并且把这些声明集中在JSP页面中的一个区域。这里有一个例子:
分散的声明块
|
首选的声明块
|
<%! private int hitCount; %>
<%! private Date today; %>
...
<%! public int getHitCount() {
return hitCount;
}
%>
|
<%!
private int hitCount;
private Date today;
public int getHitCount() {
return hitCount;
}
%>
|
3.1.5.
HTML
和JSP
代码
这一部分包括JSP
页面的HTML
体和JSP
代码,如:JSP
表达式、Scriptlets
和JavaBean
命令。
3.2
标签库描述符
标签库描述符(TLD
)文件必须用正确的XML
声明和正确的DTD
命令开始。例如,一个JSP 1.2 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/dtd/web-jsptaglibrary_1_2.dtd">
之后紧跟的是服务端注释,其中列出了作者、日期、版权、标志信息和对于标签库的简短说明:
<!--
- Author(s):
- Date:
- Copyright Notice:
- @(#)
- Description:
-->
注释中各个元素的使用规则和指导方法和JSP/JSP fragment文件中的注释定义的一样。
这个文件的剩余部分以下部分,在文件中的顺序和列出顺序相同:
l
可选
的标签库校验器(
tag library validator
)声明
l
可选的事件侦听器(event listener)声明
l
一个或多个可用的标签的声明
这里还建议在TLD
文件的元素下增加以下可选的子元素。这些子元素给标签的设计者提供了书写关于标签文件的行为和附加信息的文档的地方,并且可以把这些信息提供给WEB
组件开发者。
TLD
元素
|
JSP 1.2
推荐的子元素
|
JSP 1.1
推荐的子元素
|
attribute (JSP 1.2)
|
description
|
|
init-param (JSP 1.2)
|
description
|
|
tag
|
display-name, description, example
|
name, info
|
taglib
|
uri, display-name, description
|
uri, info
|
validator (JSP 1.2)
|
description
|
|
variable (JSP 1.2)
|
description
|
|
4.
缩进排版
缩进的地方应该填入空格符,Tab
符在不同的编辑器中表示不同的字符间隔,因此在JSP
页面中不能用于缩进排版。除非受特定的IDE
工具限制,通常情况下一个缩进单位代表4
个空格符。下面是个例子:
<myTagLib:forEach var="client" items="${clients}">
<myTagLib:mail value="${client}" />
</myTagLib:forEach>
在连续缩进的情况下,后面的行要对齐前面的行的适当的位置。连续缩进是指多个连续的标准缩进(即多个4个空格符):
<%@ page attribute1="value1"
attribute2="value2"
...
attributeN="valueN"
%>
4.1
脚本元素的缩进
当一个JSP
脚本元素(包括声明、Scriptlet
、表达式)超过一行,我们接受的缩进规范将作用于这个元素的体部分。体部分开始于和元素开始标志 <%=
同一行,或开始标志 <%
之后新的一行。体部分结束于单独一行的元素封闭标志(%>
)之前。例如:
<%= (Calendar.getInstance().get(Calendar.DAY_OF_WEEK)
=Calendar.SUNDAY) ?
"Sleep in" :
"Go to work"
%>
不包含脚本元素开始标志和结束标志的元素体部分的行都以一个标准缩进单位打头(如上例
所示部分),这样使脚本元素体能够同JSP
页面的其他部分清晰地区分开来。
4.2
含有JSP
、HTML
、Java
的混合缩进排版
混有Java
脚本代码和模板文本(HTML
)的混合缩进排版,对于降低领会理解JSP
源文件的难度,混合缩进排版是有必要的。这是因为传统的标准缩进排版使阅读JSP
源文件比较困难。作为一般的惯例,对一个元素内部引入的另一个元素加上一个标准缩进单位。注意这种方法将改变最终输出到客户端用于显示的结果的缩进排版格式。然而这些附加的缩进通常会被浏览器忽略,并且不会对显示在浏览器中的输出结果造成任何影响。例如,在 <table>
标签前加更多的空格字符不会改变表格的位置。所以,下面使用了缩进排版的例子:
<table>
<% if ( tableHeaderRequired ) { %>
<tr>
<th>Last Name</th>
<th>First Name</th>
</tr>
<% } %>
<c:forEach var="customer" items="${customers}">
<tr>
<td><c:out value="${customer.lastName}"/></td>
<td><c:out value="${customer.firstName}"/></td>
</tr>
</c:forEach>
</table>
就要比以下的例子:
<table>
<% if ( tableHeaderRequired ) { %>
<tr>
<th>Last Name</th>
<th>First Name</th>
</tr>
<% } %>
<c:forEach var="customer" items="${customers}">
<tr>
<td><c:out value="${customer.lastName}"/></td>
<td><c:out value="${customer.firstName}"/></td>
</tr>
</c:forEach>
</table>
可读性要强一些。
5.
注释
注释被用来简要描述附近代码的附加信息或用途。这里我们将看到JSP文件中的两种注释:JSP(服务端)注释和客户端注释。
5.1
JSP
注释
JSP
注释(也称为服务端注释)只能在服务端可见(也就是说,这类注释不会被发送到客户端)。纯JSP注释要优于带有脚本语言注释的JSP注释,因为前者不依赖于相关的脚本语言,并且易于迁移至JSP 2.0风格的页面。下面的表格对此做了说明:
行数
|
带有脚本语言注释的JSP 脚本
|
纯JSP 注释
|
单行
|
<% /** ... */ %>
<% /* ... */ %>
<% // ... %>
|
<%-- ... --%>
|
多行
|
<%
/*
*
...
*
*/
%>
|
<%--
-
...
-
-- %>
|
<%
//
//
...
//
%>
|
5.2
客户端注释
客户端注释(<!-- ... -->)能够用于对发往客户端的响应信息加以注释。其中不能包含服务端应用程序的行为和内部架构的相关信息和生成响应的代码。
通常客户端注释的使用是不鼓励的,因为客户/用户不需要也不会直接阅读这些注释来解释收到的响应信息。一个例外是为了确认真实性和法律性用途,例如上面所述的标志性信息和版权信息。另外一个例外就是HTML作者使用少量HTML注释来说明HTML文档的结构。例如:
<!-- toolbar section -->
...
<!-- left-hand side navigation bar -->
...
<!-- main body -->
...
<!-- footer -->
...
5.3
多行注释块
多行注释块,无论是JSP注释还是客户端注释,每行前都用“-”修饰。在XML规范中,双破折号“--”不允许在XML注释块中使用。于是,为了兼容这个规范并且保持一致性,在多行注释块中双破折号“--”不能用来修饰其中的注释行。下面的表格以客户端注释来说明这个问题:
首选格式
|
非XML兼容格式
|
<!--
- line 1
- line 2
...
-->
|
<!--
-- line 1
-- line 2
...
-->
|
6.
JSP
声明
按照Java编码规范,相同类型的变量的声明应该放在不同的行中:
不推荐的
|
推荐的
|
<%! private int x, y; %>
|
<%! private int x; %>
<%! private int y; %>
|
JavaBean
组件不应该用JSP声明来声明和生成实例,而应该使用<jsp:userBean>这个JSP动作标签。
通常使用JSP声明来声明变量是不推荐使用的,因为这将导致脚本语言的使用,将业务逻辑和Java代码混入本应设计用于表现用途的JSP页面。
7.
JSP Scriptlets
若可能,一旦标签库提供了相同的功能就尽量避免使用JSP Scriptlets。这样是JSP页面更易于阅读和维护,有助于将业务逻辑从表现逻辑中分离出来,并且使这样的JSP页面更易于迁移至JSP 2.0风格的页面(JSP 2.0支持但不鼓励使用Scriptlets)。在下面的例子中,customers的数据类型不同,那么就一定有不同的Scriptlet的写法:
customers
是Customers数组时
<table>
<% for ( int i=0; i<customers.length; i++ ) { %>
<tr>
<td><%= customers[i].getLastName() %></td>
<td><%= customers[i].getFirstName() %></td>
</tr>
<% } %>
</table>
customers
是Enumeration时
<table>
<% for ( Enumeration e = customers.elements();
e.hasMoreElements(); ) {
Customer customer = (Customer)e.nextElement();
%>
<tr>
<td><%= customer.getLastName() %></td>
<td><%= customer.getFirstName() %></td>
</tr>
<% } %>
</table>
然而,如果使用通用标签库,对使用不同类型的customers更有灵活性。例如:在JSP标准标签库(JSTL)中,下面一段JSP代码支持数组和Enumeration类型的customers:
<table>
<c:forEach var="customer" items="${customers}">
<tr>
<td><c:out value="${customer.lastName}"/></td>
<td><c:out value="${customer.firstName}"/></td>
</tr>
</c:forEach>
</table>
在采用模型-视图-控制器(MVC)设计模式来降低表现层同业务逻辑的耦合的思想中,JSP Scriptlets不应该用来编写业务逻辑。如果有必要的话,JSP Scriptlets可以用来将处理用户请求的返回数据(也称为“值对象(value objects)”)转换成客户端需要的格式。即便如此,用前端控制器或用户自定义标签来处理数据格式转换要更好一些。例如,下面的代码直接从数据库中取出客户的姓名并且显示在客户端:
<%
// NOT RECOMMENDED TO BE DONE AS A SCRIPTLET!
Connection conn = null;
try {
// Get connection
InitialContext ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("customerDS");
conn = ds.getConnection();
// Get customer names
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT name FROM customer");
// Display names
while ( rs.next() ) {
out.println( rs.getString("name") + "<br>");
}
} catch (SQLException e) {
out.println("Could not retrieve customer names:" + e);
} finally {
if ( conn != null )
conn.close();
}
%>
下面一段JSP代码就要优于上面的代码,因为它将和数据库的交互委托给了用户自定义标签myTags:dataSource,这个标签封装并隐藏了与数据库相关的代码:
<myTags:dataSource
name="customerDS"
table="customer"
columns="name"
var="result" />
<c:forEach var="row" items="${result.rows}">
<c:out value="${row.name}" />
<br />
</c:forEach>
result
是自定义标签myTags:dataSource生成的用来保存从客户数据库取出的客户姓名的变量。JSP代码能够根据客户的需要动态的生成不同的输出信息(HTML、XML、WML),而不会影响后端的代码(例如:dataSource标签)。比较好的选择是将从数据库提取数据并且将结果通过request范围(request-scoped)的属性(attribute)提供给JSP页面这些工作委托给前端控制器Servlet。相关的例子请参看Java BluePrints的企业部分。
小结:
l
理想情况下,在JSP页面中JSP Scriptlet应该是不存在的,因此,JSP页面是与脚本语言无关的,要避免的业务逻辑在JSP页面中实现。
l
上面如果不可能做到,要使用值对象(JavaBean组件)在服务端传递数据,用JSP Scriptlet来转换值对象的格式输出到客户端。
l
若有可能在服务端尽量使用自定义标签(标签处理器)来处理数据。
8.
JSP
表达式
同JSP Scriptlet一样,JSP表达式应该谨慎使用。让我们来看看以下完成相同任务的三个例子:
例1 (直接使用Java代码):
<%= myBean.getName() %>
例2 (使用JSP标签):
<jsp:getProperty name="myBean" property="name" />
例3 (使用JSTL标签):
<c:out value="${myBean.name}" />
例1假设脚本变量myBean已经被声明。另外两个例子假设myBean是个在有效范围内的属性(attribute),能够被PageContext.findAttribute找得到。第二个例子也假设myBean由页面中的<jsp:useBean>引入。
在以上三个例子中,JSTL的例子是首选的。这种方式的代码同使用的JSP表达式的几乎一样简短,而且易于阅读更易于维护,它不依赖Java Scriptlet(Java Scriptlet需要WEB开发者对此语言和API调用熟悉)。此外,这样也使得将这些页面更易于迁移至JSP 2.0风格编程,在JSP 2.0风格下简单地在模板文本中输入${myBean.name}就可以实现以上相同的功能。应该注意的是JSTL的例子在从页面上下文PageContext取得myBean的值同从本地的Java脚本变量中取值确实略微有些不同。
最后,JSP表达式要优于同样作用的JSP Scriptlet,后者依赖于相关脚本语言的语法。例如:
<%= x %>
就要优于
<% out.print( x ); %>
9.
空白(White Space
)
空
白
产生的缩进排版美化的JSP代码可以减少理解和维护的工作量。特别是在JSP文件的多个位置需要插入空行和空格。
9.1
空行(Blank Lines)
如果不会对输出造成不想要的影响,空行可以谨慎地用来提高JSP的易读性。下面的例子,在HTML标签<pre>块内部两个JSP表达式之间插入了一个空行,将导致输出的HTML在客户端浏览器上有多余的一行。然而如果加在<pre>块外部,在浏览器上就不会有这种效果。
JSP
语句
|
输出到客户端浏览器上的HTML效果
|
<pre>
<%= customer.getFirstName() %>
<%= customer.getLastName() %>
</pre>
|
Joe
Block
|
<pre>
<%= customer.getFirstName() %>
<%= customer.getLastName() %>
</pre>
|
Joe
Block
|
<%= customer.getFirstName() %>
<%= customer.getLastName() %>
|
Joe Block
|
9.2
空格(Blank Spaces)
在JSP标签和它的内容之间应该加入一个空格(如)。例如:下面
<%= customer.getName() %>
就要优于
<%=customer.getName()%>
在JSP注释标签和注释内容之间应该有空格符分隔:
<%--
- a multi-line comment broken into pieces, each of which
- occupying a single line.
--%>
<%-- a short comment --%>
10.
命名规范
使用命名规范使项目中WEB组件元素更容易识别、分类和整理。在这一节我们将看到JSP的命名规范。
10.1
JSP
文件命名(JSP Names
)
JSP
文件名应该用小写字母开头。名字可以包含多个单词,之后每个单词的首字母大写。JSP文件名可以是简单的一个名词或一个短语。应该避免仅有一个动词的JSP文件名,因为这样的文件名不能为开发者提供充足的信息。例如:
perform.jsp
就不如
performLogin.jsp
清晰。
在动词作为JSP文件名的情况下,应该使用动词的一般现在时形式,因为它暗示了作为后端处理的动作:
showAccountDetails.jsp
要优于
showingAccountDetails.jsp
10.2
标签名(Tag Names)
下面显示了标签处理器和相关的类的命名规范:
描述
|
类名
|
XXX tag extra info (extending from
javax.servlet.jsp.tagext.TagExtraInfo
)
|
XXXTEI
|
XXX tag library validator (extending from
javax.servlet.jsp.tagext.TagLibraryValidator
)
|
XXXTLV
|
XXX tag handler interface (extending from
javax.servlet.jsp.tagext.Tag/IterationTag/BodyTag
)
|
XXXTag
|
XXX tag handler implementation
|
XXXTag
|
另外,标签命名不能违反Java编码规范中类和接口的命名规范。
为了将标签相关的类与其他的类区分开来,这些类所属包的名称后缀应使用tags或taglib。例如:
com.mycorp.myapp.tags.XXXTag
10.3
标签前缀名(Tag Prefix Names
)
标签前缀名应该是简短而有意义的标题形式的名词,并且首字母小写。标签前缀名不应该包含非字母字符。以下是一些示例:
例
|
OK?
|
mytaglib
|
no
|
myTagLib
|
yes
|
MyTagLib
|
no
|
MyTagLib1
|
no
|
My_Tag_Lib
|
no
|
My$Tag$Lib
|
no
|
11.
XML
语法下的JSP
页面(JSP Pages in XML Syntax
)
JSP
提供了两种不同的语法:一种是写JSP页面的“标准语法”,一种是写作为XML文档的JSP页面的“XML语法”。用标准语法写的JSP页面被称为“JSP 页面(JSP pages)”。用XML语法写的JSP页面被称为“JSP文档(JSP documents)”本文主要讲述的是JSP页面,但是也有许多应用于JSP文档的概念。因为XML被普遍采用,JSP文档的使用也被期望不断增加,因此JSP 2.0规范将引入更友好的XML语法。
应该注意到使用XML语法编写JSP页面和JSP页面的XML视图是有区别的而且经常也容易混淆。页面编写者可以使用标准语法或XML语法来编写JSP页面。然后容器将JSP页面翻译成暴露在标签库校验器下的XML视图。
11.1
JSP
文档结构
JSP
文档有以下基本结构:
<? xml version="1.0" ?>
<!--
- Author(s):
- Date:
- Copyright Notice:
- @(#)
- Description:
-->
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:prefix1="URI-for-taglib1"
xmlns:prefix2="URI-for-taglib2"
version="1.2">
JSP Document ...
</jsp:root>
第一行是可选的定义该页面是一个XML文档的XML序言。序言之后是该文档的注释。<jsp:root>元素定义了这是一个JSP文档,并且作为root元素出现。必须引入jsp这个名称空间,所有的标签库也必须使用这个root元素引入。属性version是必要的,用来指明使用的JSP规范的版本。实际的内容作为<jsp:root>的子元素出现。标准的XML缩进排版规则应该在文档中继续沿用,用4个空格作为一个缩进单位。
JSP
文档必须是一个具有良好格式的XML文档,因此例如<% %>之类的元素就必须使用等效的XML元素,例如<jsp:scriptlet />。细节请查看JSP规范。
11.2
XML
注释(XML Comments)
JSP
规范在规定XML风格(XML-style)的注释输出时是否会被去掉的问题上不太明确,于是如果要求注释输出到客户端,为了保证这一点,注释内容就要如以下所示包在<jsp:text>节点里面:
...
<jsp:text><![CDATA[
<!--
- Multiline comment
- to be sent to client.
-->
]]></jsp:text>
...
11.3
JSP
文档中的Java代码
当要在声明、Scriptlet和表达式中写Java代码时,若有必要确保这些Java代码不会破坏文档结构,就应该使用CDATA元素。
...
<jsp:scriptlet>
for( int level = 0; level < 3; level++ ) {
</jsp:scriptlet>
<tr>
<td>
<jsp:expression><![CDATA[
"<h" + level + ">Text</h" + level + ">"
]]></jsp:expression>
</td>
</tr>
<jsp:scriptlet>
}
</jsp:scriptlet>
...
跟JSP标准语法中的缩进方式不一样,XML缩进应该被遵守,而不管每个元素的内容是什么。
12.
编程惯例
一般来说,由于以下原因,避免在JSP页面中写Java代码(声明、Scriptlet和表达式):
l
直到JSP页面部署,其中的Java代码的语法错误是检测不出来的。另一方面,在标签库和Servlet中的错误在部署前就能发现。
l
JSP
页面中的Java代码比较难于调试。
l
JSP
页面中的Java代码比较难于维护,尤其是哪些不是Java专业者的页面作者。
l
不要将复杂的业务逻辑和表现逻辑混在一起已广为接受。JSP页面主要用于表现逻辑。
l
包含有Java代码、HTML和其他脚本指令的代码可能难于阅读。
l
JSP 2.0
倾向于使用更简单的表达式语言而不推荐使用Scriptlet。如果在你的JSP页面中没有使用Java代码,就很容易将你的页面迁移至JSP 2.0方式编程。
更多指导思想和细节请参看Java BluePrints的企业部分。
12.1
JavaBean
组件的初始化(JavaBeans Component Initialization)
JSP
提供了一个方便的元素来初始化属性描述符确定的JavaBean组件的所有属性。例如:
<jsp:setProperty name="bankClient" property="*"/>
但是,这种方法要谨慎使用。首先,如果bean有一个属性,比如说amout,在当前的ServletRequest对象中却没有这个参数或者参数的值是"",于是以上动作没有起任何作用:JSP页面甚至不会用null值去设置bean的这个属性。于是,无论之前对bankClient的amount属性赋了任何值,这个bean都不会受任何影响。其次,没有定义属性编辑器(PropertyEditors)的非简单的属性可能不能由ServletRequest对象的一个字符串值隐式地初始化,需要显式地数据转换。第三,如果应用程序没有仔细地设计的话,恶意的用户能够在请求中添加另外的参数来设置bean的本来不想设置的属性。
如果你仍然喜欢在<jsp:setProperty>使用property="*"来达到获得比较简洁的代码,那么我们建议你在<jsp:setProperty>标签前加上注释来说明bean的初始化需要在ServletRequest对象中有哪些参数。在下面的例子中,通过注释我们知道初始化bankClient这个bean,firstName和lastName是必需的:
<%--
- requires firstName and lastName from the ServletRequest
--%>
<jsp:setProperty name="bankClient" property="*" />
12.2
JSP
隐含对象(JSP Implicit Objects)
直接使用JSP的隐含对象来获取对这些对象的引用要优于API调用。因此,不必使用
getServletConfig().getServletContext().getInitParameter("param")
来访问SerletContext实例提供的初始化参数,我们可以如下简单地使用隐含对象:
application.getInitParameter("param")
在仅仅需要输出初始化参数的值的情况下,最好使用JSTL标签来访问初始化参数:
<c:out value="${initParam['param']}" />
12.3
引号的使用(Quoting)
应该使用统一形式的引号。被引用的内容应该包含在两个双引号(“"”)之间而不是两个单引号(“'”)。
不统一的引号使用
|
优选的引号使用
|
<%@ page import=
'
javabeans.*
'
%>
<%@ page import="java.util.*" %>
|
<%@ page import="javabeans.*" %>
<%@ page import="java.util.*" %>
|
一个需要使用单引号的例外是,当脚本语言中需要使用双引号时:
<jsp:include page='<%= getFoodMenuBar("Monday") %>' />
12.4
使用自定义标签(Using Custom Tags)
如果自定义标签没有体内容(body content),应该显式地声明content为empty(而不应默认为“JSP”),例如以下标签库描述符中的一段:
<tag>
<name>hello</name>
<tag-class>com.mycorp.util.taglib.HelloTagSupport</tag-class>
<body-content>empty</body-content>
...
</tag>
以上配置告诉JSP容器体内容(body content)必须是空的,而不包含任何需要解析的JSP语法。这样能消除或避免为解析空的体内容(body content)而分配的不必要的资源。
空的标签应该是短XML元素,而不使用打开-关闭样式的XML元素对来提高可读性。于是,
<myTag:hello />
要优于
<myTag:hello></myTag:hello>
12.5
使用标签库附加信息和标签库校验器(Use of TagExtraInfo and TagLibraryValidator)
有些时候,仅用TLD不能够表达使用标签库的有效方法。因此这时就应该编写TagExtraInfo类和 TagLibraryValidator类,并在TLD中注册,于是在翻译的时候就能够捕捉到标签库的错误。
12.6
使用JavaScript(Use of JavaScript Technology)
为了使JavaScript运行正常,JavaScript不应该依赖于不同浏览器类型的某种特征。
将JavaScript代码放在独立的文件中同JSP文件分开是个好的方法,使用如下的一个语句就可以将JavaScript代码引入到JSP页面中:
<script language=javascript src="/js/main.js">
12.7
嵌套式样式表(Cascading Style Sheets)
使用嵌套式样式表来集中对标题、表格等等共同特征的控制。这样提高了表现给用户的一致性,减少了维护的工作量和JSP页面的代码长度。因此,替代以下在HTML标签中嵌入样式信息的做法:
<H1><FONT color="blue">Chapter 1</FONT></H1>
...
<H1><FONT color="blue">Chapter 2</FONT></H1>
...
将样式信息定义在一个样式文件myJspStyle.css中,其内容包含:
H1 { color: blue }
在JSP页面使用该样式表:
<link rel="stylesheet" href="css/myJspStyle.css" type="text/css">
...
<H1>Chapter 1</H1>
...
<H1>Chapter 2</H1>
...
12.8
使用组合视图模式(Use of Composite View Patterns)
当JSP页面需要特定的合成的结构,这种结构也可能在其他JSP页面重复出现,有一种办法是将JSP页面分成若干部分,使用组合视图模式(参加Java BluePrints的模式部分)。例如,一个JSP页面在其呈现的结构上有时有以下的逻辑布局:
header
| |
menu bar
|
main body
|
footnote
| |
footer
|
在这种样式下,JSP页面可以分为不同模块,每一部分都作为JSP fragment。被组合的JSP fragment可以用翻译时(translation-time)或请求时(request-time)包含(include)的JSP标签来放在组合JSP页面的适当位置。通常,当使用静态包含指令包含一个不会单独请求的页面时,记得使用.jspf的后缀名,并且把这个文件放在这个WEB应用文档(war)的/WEB-INF/jspf/目录中。例如:
<%@ include file="/WEB-INF/jspf/header.jspf" %>
...
<%@ include file="/WEB-INF/jspf/menuBar.jspf" %>
...
<jsp:include page="<%= currentBody %>" />
...
<%@ include file="/WEB-INF/jspf/footnote.jspf" %>
...
<%@ include file="/WEB-INF/jspf/footer.jspf" %>
...
12.9
其他的建议
在本文中,我们提出了一套用来编写更可维护的和稳定的JSP代码和WEB组件的推荐编码规范。这里还有其他很好的编程惯例,如果你要想深入研究这些主题的话。例如:JSP 1.2规范建议:
l
定义新的隐式对象
l
访问提供者指定信息
l
自定义标签库
此外,Java BluePrints也在更广的范围提供了很好的编程惯例,例如Model-View-Controller模式的使用(在模式部分)。
我们非常高兴你对本文提出的编程规范的关注,并且我们希望你能够共享你在JSP编码规范上的其他建议。请将你的反馈信息发送至 jsp-codeconv-comments@sun.com。
下面我们列出了一个完整的演示以上编码规范的WEB应用程序的源码。你可以在这里下载这个应用程序的WAR文件
13.
示例代码
这里列出了一个范例WEB应用程序来演示本文提出的编码规范。它包含包含在一个.war文件中的以下源码文件和目录结构:
/index.jsp
/WEB-INF/classes/codeconv/GetResultsTag.class
/WEB-INF/jspf/footer.jspf
/WEB-INF/lib/jstl.jar
/WEB-INF/lib/standard.jar
/WEB-INF/tld/lotterylib.tld
/WEB-INF/web.xml
index.jsp
页面使用了一个自定义标签库(由lotterylib.tld确定)查询当月每天(直到并包含当天)的假的抽奖结果。然后使用JSTL进行文本格式化,循环产生HTML的输出结果。
这个范例需要JSP 1.2和JSTL 1.0。
14.
参考资料
5.
Alur D., Crupi J., Malks D., Core J2EE Patterns: Best Practices and Design Strategies, Sun Microsystems Press, Prentice Hall Inc., 2001.
8.
Gamma E., Helm R., Johnson R., Vlissides J., Design Patterns: Elements of Reusable Software, Addison-Wesley, 1995.
10.
Java BluePrints.