JSP 1.X編碼規範
由於JavaServer Page(JSP)技術廣泛應用於WEB應用程序,如同許多Java程序員一樣,許多JSP程序員和WEB應用開發者在開發和維護這些應用程序時面臨一些難以抉擇的問題:“我們該如何編寫更易於閱讀、編寫和維護的JSP代碼”。
在本文中,我們提出了一套JSP(version 1.1 and 1.2)標準規範,使用WEB組件的軟件項目都應該遵守這個規範。本文以Java編碼規範爲模板來確定應該提及在與JSP技術相關的編碼規範中的各個重要方面。特別地,本文提到了文件的命名和組織、縮進排版、註釋、指令、聲明、Scriptlets、表達式、空格、命名規範,以及編程慣例。因爲這是我們第一次提出一套JSP編碼規範,我們非常希望獲得大家的反饋。請將意見和建議發送至[email protected]。
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文件目錄中。
最後,將標籤庫描述符文件(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編碼規範上的其他建議。請將你的反饋信息發送至 [email protected]。
下面我們列出了一個完整的演示以上編碼規範的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.