2010年8月17日 星期二

JSP & Servlet JSTL使用簡介

EL和標準動作並不是萬能
有些邏輯處理的部分,例如迴圈等,依然無法靠他們達成
在不撰寫Sciptlet的條件下,這時候就會讓人想要自訂標籤來訂製自己需要的動作
當然自訂標籤也不是那麼容易的事,所以我們可以先考慮使用JSTL

JSTL提供了更多的標準動作擴充
因為它並不屬於JSP 2.0的規格,所以還需要先去下載JSTL的jar檔案才行

JSTL的版本選擇必須依照所使用的container版本來挑選才行
1.0 對應 Tomcat 4.x  (Servlet 2.3)
1.1 對應 Tomcat 5.x  (Servlet 2.4)
1.2 對應 Tomcat 6.x  (Servlet 2.5)

目前所使用的是Tomcat 6,所以就先到http://download.java.net/maven/1/jstl/jars/ 下載
覺得難找的話就只能在網路上搜尋看看了
將下載的 jstl-1.2.jar 放入應用程式的/WEB-INF下的 lib 目錄下
如果使用的 IDE 是Eclipse,還需要在專案資料夾的設定做調整
右鍵 -> property -> Java Build Path -> Libraries -> Add JARs
將lib裡的JSTL的 jar 檔加入Build Path
另外,在網路上或 Servlet & JSP教學手冊這本書上被提到的 jstl-impl-1.2.jar
是在 https://jstl.dev.java.net/ 下載的
需要api文件的話,可以下載網址裡的api檔案來看
但是這個實作檔少包了些class檔,執行上可能會讓我們碰到障礙
找 jstl-1.2.jar 比較實在

JSTL提供的標籤庫可以分成五個大類
1.核心標籤庫
   提供條件判斷、屬性存取、URL 處理及錯誤處理等標籤
2.格式標籤庫
   提供對應日期、時間、時區等標準格式的處理與轉換
3.SQL 標籤庫
   提供基本的資料庫查詢、更新、設定資料源(DataSource)等功能
4.XML 標籤庫
   提供XML 剖析、流程控制、轉換等功能之標籤
5.函式標籤庫
   提供常用字串處理的自訂EL 函式標籤庫

使用前須先用 taglib 定義文件名稱及 uri 參考
如:<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
上面的 uri 參考引入了JSTL的核心標籤庫,命名為c為習慣的命名方式
當然這篇筆記只會紀錄核心標籤庫為主的動作標籤
其餘的若有需求,還需要查詢API來使用

<c:out>標籤可以輔助輸出的工作,使用範例如下:
<c:out value="${pageContext.source }" escapeXml="true" />

這段程式碼的前提是我們已經在pageContext裡存了以 XML 寫成的source特性
將要輸出的內容放在 value 裡
escapeXml可以選擇是否將輸出的內容完整展示出來,而不讓"<等符號被 html 解讀而脫離
實際上這個特性的預設值是 true ,所以範例裡不加這個也跟我們原本目的相同
拜這個屬性所賜,使用<c:out>標籤成為了較為安全的選擇
假如有人想發動指令搞攻擊(Injection attack),其所輸入的JavaScript也不會被解譯

<c:out>標籤還提供了預設值的設置,如下程式碼:
 <c:out value="${pageContext.source }" default="Hi~" />

當範例裡的輸出的表達式是 null 值時,會展示 default 屬性裡的值
相較於不會顯示 null 值的EL,<c:out>標籤提供更大的彈性

網頁裡常見以來源資料的陣列, 用迴圈方式來寫出展示用的資料表格
JSTL裡提供了處理迴圈的<c:forEach>標籤動作來應付這類需求
程式範例:
test.java=========

//測試用的Servlet程式碼
.....
String[] testList = { "Baker sucks", "a", "b" };
request.setAttribute("testList ", testList );
.....

show.jsp=======

.....
<c:forEach var="tester" items="${testList }" varStatus="loopCounter" >
  <tr>
      <td>${tester }</td>
      <td>${loopCounter.count }</td>
  </tr>
</c:forEach>
.....


items裡放入所要繞行的陣列,var 則是每一次繞行時所取出的值的代稱
因為<c:forEach>標籤動作會做迴圈直到繞行完陣列所有的值,所以每次var的值也不相同
varStatus是選用的屬性,範例裡我們用它來取得計數器
搭配使用EL輸出來源的值,這樣就完成表格的製作了
注意包含var在內等標籤設定的變數都只在<c:forEach>標籤內有效,無法在標籤外部使用
除了陣列外,Collection、Map也是常見用來繞行的物件
<c:forEach>標籤裡也有可以設定每次迴圈跳過的元素間距等屬性,詳細使用要查詢API
如果陣列裡面還包著陣列,<c:forEach>標籤也能夠以巢狀的方式來使用
例如:
<c:forEach var="testList" items="${source }" >
     <c:forEach var="tester" items="${testList }" > //外層迴圈取出的testList成為內層迴圈的來源
          <tr>
               <td>${tester }</td>
          </tr>
     </c:forEach>
</c:forEach>

假如只是要以指定的數字做迴圈,不需要或沒有提供來源陣列
則改以如下方式撰寫:
<c:forEach var="i" begin="1" end="${requestScope.rollNumber}" step="1">
    <c:out value="這是第${i}個迴圈" />
</c:forEach>

begin 是迴圈的起始數值,end是迴圈的中止數值,step是每次回圈的增加數值
var 指定的變數就是當次迴圈的數值

<c:if>標籤能做到條件式的篩選,範例如下
 <c:if test="${ user == 'vencs' }">
    <c:out value="Hi~" />
</c:if>

test屬性裡放入要進行判斷的式子,如果為 true 則執行主體內的程式
當然這樣只能做到 if 判斷,沒有做到 else 的功能
比起寫一大堆<c:if>標籤,JSP還是有提供比較好的做法
我們該使用的是<c:choose><c:when><c:otherwise>標籤
範例:
<c:choose>
  <c:when test="${ user == 'John' }">
    Hi~John
  </c:when>
 
  <c:when test="${ user == 'Tesa' }">
    Love Tesa
  </c:when>
 
  <c:otherwise>                   //如果上述的when條件都沒有成立,則會執行otherwise裡的程式
    Please Enter your name
  </c:otherwise>
</c:choose>

這個範例就比較接近一般程式中所看見的 if、else 判斷式
<c:choose>標籤裡僅會執行最早符合條件的程式區段
而<c:otherwise>標籤在<c:choose>標籤裡並不是必要的

過去學過的<jsp:setProperty>標籤只能拿來設定 bean 的性質
<c:set> 標籤則可以做到更多的事,例如增加作用域裡的屬性等
<c:set>標籤有兩種版本,分別是 var 與 target
var 版本用來設定屬性質,以下範例分成有無主體的版本來看
無主體:
<c:set var="Song" scope="application" value="Kashmir" />

有主體:
<c:set var="Song" scope="Context">
  Kashmir
</c:set>

這裡有無主體的差別只在於換個放入值的方法
如果裡面是複雜的表達式或是很長的字串,會比較容易閱讀
var 屬性是必要的,若var 不存在;只要 value 不是null,它就會被建立
<c:set>的scope 屬性則是選用,預設值是page
可選擇的作用域有 page、request、session、application
注意,假如使用<c:set>標籤的屬性的值被設定成 null,該屬性會被移除!
即使原本該屬性有正確的設定值

set 版本的設定針對bean的特性或是 Map的鍵 / 值
無主體:
//target 裡的值必須指向真實的物件
<c:set target="${tw.vencs.song }" porperty="title" value="Kashmir" />


有主體:
<c:set target="${tw.vencs.song }" porperty="title" >
  Kashmir
</c:set>

var 和 target 兩屬性並不能被同時使用
若 target 指向的結果是 null 或不是bean或Map這兩種型別之一,container會丟出例外
當然如果指向的bean物件的特性與 property 指定的值並不相符,也是會被丟出例外
另外,target 必須以EL表達式、scriptlet來指向實際的物件參考
輸入字串在這邊是不行的

<c:remove>標籤與<c:set>相對
用它來做刪除是比較直覺且合理的做法
使用範例:
<c:remove var="source" scope="request" />

var必須要是字串,不能用表達式
scope屬性雖然是選用的,但忽略它會讓該屬性從所有領域移除

<c:import>標籤則提供了我們嵌入內容的新方式
它不同於靜態的include指令,比較偏向動態的<jsp:include>標準動作
語法: <c:import  url="a.jsp" charEncoding="UTF-8" />
處理方式是在請求期間將 url 連結的內容增加到目前的頁面上
不同於先前的兩種引入方式
<c:import>的 url 可以嵌入container外的資源,例如來自不同Server的檔案
charEncoding 能調整引入檔案的編碼
動態引入可以做到的客製化,<c:import>也可以做到
就像是我們會在<jsp:include>裡使用的<jsp:param>
<c:param>標籤能做到同樣的效果
程式範例:
<c:import url="a.jsp" >
  <c:param name="title" value="hello world" />
</c:import>

被嵌入的檔案使用EL呼叫即可,例如${param.title}
基本上跟標準動作的include差不多

之前的筆記有記錄著關於遇上Client端停用Cookie時,針對session使用的處理法
<c:url>標籤能讓我們做到重寫URL或重新編碼來應對
在JSP上做到之前寫在Servlet裡的事
範例:
<c:url value="/response.jsp" var="encodedURL" >
  <c:param name="id" value="${id }"></c:param>
  <c:param name="pw" value="${pw }"></c:param>
</c:url>

<a href="${encodedURL }">click me</a>

範例裡同時做了重寫URL與重新編碼
重寫時會將 jsessionid 增加到 "value" 的相對URL後
var的設定可以讓這個被重寫過的URL能夠被拿來使用
需要重新編碼的原因在於必須將不安全或保留的字元替換成其他字元
例如空白必須被替換成 "+"
編碼的工作就讓<c:param>來做,就像範例一樣

JSTL也提供了在JSP中處理例外的動作標籤
<c:catch>標籤的名稱看起來就讓人聯想到Java的 try / catch
但其處理方式可以被視為不需要 catch 或 finally 的try區塊
將可能有問題的EL或其他程式碼包在<c:catch>標籤的主體裡
例外發生時就會被捕捉起來
 範例如下:
<c:catch var="except">
  <% int x = 10/0; %>>
</c:catch>
這行程式碼在標籤外<br />

<c:if test="${except != null }">
  We got ${except.message}
</c:if>

範例中位於<c:catch>主體內的程式碼會引發 exception,並被<c:catch>捕捉
之後會不管主體內還有哪些程式碼還沒被執行,程式碼會直接跳到標籤結尾再繼續
預設的錯誤處理機制(錯誤頁面)在整個流程中都不會介入
<c:catch>標籤的 var 屬性能用來存取 exception 物件
因為 exception 隱含物件只有在錯誤頁面中才能被存取,其他頁面中沒有這個隱含物件
設定完選用的 var 屬性後,可以在page作用域以指定的 var 值來取得例外物件
範例中以例外類別的 message 特性取得更詳細的資訊

沒有留言:

張貼留言