2010年8月25日 星期三

網頁筆記(2)

CSS樣式表的使用有幾個好處
除了比起寫在頁面的<style>元素中,有維護方便以及重複利用的優點外
PDA、移動設備等等,越來越多的設備也支援CSS樣式表的使用
以下列的CSS為例:
<link rel="stylesheet" href="css/lightbox.css" type="text/css" media="screen" />

<link>標籤裡的media屬性指定樣式表要套用的設備類型
設定成"screen"指定樣式表要套用在電腦螢幕
如果要套用在其他設備(如印表機、電視、投影機等)的話再修改media選項即可

2010年8月23日 星期一

JSP & Servlet 自訂標籤設計(2)

這篇筆記介紹 Tag File 的使用,為之前的自訂標籤的第二篇

Tag File 是自訂標籤的一部份
可以用來呼叫重複利用的內容,做到類似嵌入的效果 
它提供非 Java 開發人員不需要撰寫Java類別以及TLD就建立自訂標籤的選擇

Tag File 與其他的方法上比較有所差異的部分在觀念上
以概念來說,Web Application中不應該出現在請求之外的傳遞參數
所以之前提過的頁面客製化的作法在概念上並不是那麼的合理

使用方法為以下步驟:
  1.將被嵌入的檔案的副檔名改成.tag
  2.將Tag File (副檔名是.tag的檔案)放在WEB-INF下名為 "tags" 的目錄或其子目錄裡
  3.在JSP頁面加上 taglib 指令並使用 tagdir 屬性,接著如下範例呼叫標籤:
     <%@taglib prefix="fragment" tagdir="/WEB-INF/tags" %>>

     <html><body>
    <fragment:Header />
    </body></html>

範例會在頁面中呼叫Header.tag 檔
傳遞參數的方法則是使用標籤的屬性,例如<fragment:Header  subTitle="Hello" />
接著可以在被引入的檔案裡用 ${subTitle} 讀取此參數
也因為是標籤的屬性的緣故,傳遞的參數只能在引用的頁面裡面使用
不像其它的作法,若使用<jsp:param>會讓傳遞的參數被其他的部份看到
在設計的理念上,Tag File 是比較合理的

Tag File 與其他標籤程式庫不同
它不需要撰寫 tld 檔,除非要將它部屬在 JAR 檔中
那就必須用TLD,以<path>元素替它宣告實體位置
所以有一些專門用在 Tag File 上的指令會在下面說明

在 .tag 檔案裡可以可以加入 attribute 指令
作用類似後續會在自訂標籤筆記提到的 tld 裡的<attribute>元素
如:<%@ attribute  name="subTitle"  required="true"  rtexprvalue="true" %>
required屬性設定成 true 表示這個屬性是必要的
rtexprvalue 設定成 true 讓傳遞時可以用字串實字或表達式來輸入參數值

當屬性值很龐大時,有主體可以讓閱讀上比較方便
程式碼:
Header.tag======

<p><jsp:doBody /></p>


show.jsp=======

<%@taglib prefix="fragment" tagdir="/WEB-INF/tags" %>>

<html><body>
     <fragment:Header>
       這裡是主體內的內容
       這裡是主體內的內容
    </fragment:Header>
</body></html>

<jsp:doBody />的用意是不管標籤主體內容為何,將評算結果放進來
而body-content的宣告需要透過 tag 指令來下
如:<%@ tag body-content="tagdependent" %>
上面的程式碼設定標籤主體的內容做為純文字來處理
這個屬性的預設值是 scriptless,另外還可以設定empty
不過不管怎樣都不能在 Tag File 中使用Scriptlet

Tag File 是JSP的一部分,所以也是可以存取隱含物件
只是沒有 ServletContext 能用,須改用 JspContext 物件來存取

前一篇提過的 SkipPageException 在嵌入頁面的使用上
它會讓讓被嵌入的頁面中止,但呼叫它的頁面會繼續輸出
注意使用時的區別

JSP & Servlet 自訂標籤設計(1)

儘管JSTL已經有很豐富的功能
但依然有可能碰到找不到我們想要的功能的時候
又或者是公司內部有一套自訂的標籤程式庫來應付內部的流程
這些情形都是學習自訂標籤的理由
然而撰寫自訂標並不是容易的事,最好先確認其必要性再動手去做

先說明使用自訂標籤的方式,後面在介紹自訂標籤的製作方式
它的使用方式會有點像之前提過的 EL 函式
不過實際撰寫的結果是一個 tld 檔裡似乎無法同時描述自訂標籤與EL函式
先來個程式範例:
rand.tld=============

<?xml version="1.0" encoding="UTF-8" ?>

<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
    version="2.0">

    <tlib-version>1.2</tlib-version>              <!-- 宣告標籤程式庫版本 -->
    <short-name>randTag</short-name>     <!-- 供開發工具使用的必要元素 -->
    <uri>randTag</uri>                               <!-- taglib指令中使用的獨特名稱 -->
    <tag>
      <description>for test</description>     <!-- optional, 但是可以用來幫助描述自訂標籤 -->
      <name>myTag</name>
      <tag-class>tw.vencs.DoTag</tag-class>
      <body-content>empty</body-content>
     
      <attribute>
          <name>attr</name>
          <required>true</required>
          <rtexprvalue>true</rtexprvalue>
      </attribute>

    </tag>

</taglib>

DoTag.java=======

package tw.vencs;

import javax.servlet.jsp.tagext.SimpleTagSupport;
import javax.servlet.jsp.JspException;
import java.io.IOException;

public class DoTag extends SimpleTagSupport {
    private String demoStr;
  
    public void doTag() throws IOException, JspException{
        getJspContext().getOut().write(response());
    }
    //設定標籤裡的屬性值的方法,本例中設定此標籤有一屬性名為 attr
    public void setAttr(String demoStr){
        this.demoStr = demoStr;
    }
    //內部方法
    public String response(){
        String str = "What u need is " + demoStr;
        return str;
    }
}

show.jsp=========

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%@ taglib prefix="test" uri="randTag" %>    <!-- uri 填上所要用的 tld 檔的uri -->
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>test</title>
</head>
<body>

<test:myTag  attr="name" />

</html>


tld 檔裡的 <tag> 元素是這次的重點之一
裡面的 <name> 元素設定我們使用的標籤名稱,例如JSP裡的<test:myTag attr="name"/>
<tag-class> 設定標籤所使用的程式碼
<body-content>設定成empty表示這個標籤不能夠有東西在主體裡
<attribute>則設定此標籤所擁有的屬性
它內部的<name>元素則指定屬性的名稱
<required>設定成 true 表示使用此自訂標籤就必須為它加上這個屬性
<rtexprvalue> 負責設定屬性的判別方式,例如 true 表示值可以不為字串實字
 tld 檔的放置位置限定在WEB-INF下,或是WEB-INF的子目錄裡

自訂標籤的處理由繼承 SimpleTagSupport 的Java類別來做
它裡面需要實作的方法:有實際處理工作的doTag()和設定屬性值的方法(如本例的setAttr())
設定屬性值的方法的命名方式依照 JavaBean 規格
之前在撰寫EL函式時會宣告<function-signature>
但因為自訂標籤的使用方法只會有 doTag(),所以不會在DD中宣告方法名稱

JSP 檔的 taglib 指令有需必須要做的設定
container會比對 tld 的<uri>元素和 taglib 指令的uri屬性,並找出相符合的
JSTL裡的 uri 看起來像個URL,但那只是為了讓標籤命名獨特,並不是需要輸入實際位置
範例中我們在 attr 屬性裡填入 "name" 作為值
但因為<rtexprvalue>設定是 true
所以其實也可以用EL、Sciptlet運算式或是<jsp:attribute>標準動作來輸入值
當然如此一來,屬性型別也是一個值得討論的問題
container預期EL的評算結果會符合標籤的setter方法的參數型別
如果不這樣撰寫的話會產生例外
倘若 <rtexprvalue> 為 false 或未定義,那就只能使用字串實字

標籤主體只有在 tld 的 <body-content> 不為empty時才能有主體
<body-content>裡可以宣告的值共有 empty、scriptless 、tagdependent、JSP
scriptless 會讓標籤不能有Scriptlet,但能有樣板文本、EL、自訂及標準動作
tagdependent會讓標籤主體作為純文字處理
設定成JSP則可以在主體中使用任何JSP裡的東西

所謂不能有主體的標籤有三種方式
第一是空標籤,例如 <xxx:xxx />
第二是在開始與結束標籤之間沒有任何東西,如 <xxx:xxx></xxx:xxx>
最後是開始與結束標籤之間只有<jsp:attribute>標準動作
因為<jsp:attribute>只是另一種設定標籤屬性的方法,故不算在主體內
程式碼如:
<xxx:xxx>
     <jsp:attribute xxx="xxx">${test}</jsp:attribute>
</xxx:xxx>


接下來看另一個範例來做更深入的了解:
Iterator.tld=====

......
    <tag>
      <description></description>
      <name>myTag2</name>
      <tag-class>tw.vencs.DoTag</tag-class>
      <body-content>scriptless</body-content>
     
      <attribute>
          <name>list</name>
          <required>true</required>
          <rtexprvalue>true</rtexprvalue>
      </attribute>
    </tag>
....

DoTag.java=====

package tw.vencs;

import javax.servlet.jsp.tagext.SimpleTagSupport;
import javax.servlet.jsp.JspException;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;

public class DoTag extends SimpleTagSupport {
    private List<String> list;
   
    public void doTag() throws IOException, JspException{
        Iterator<String> i = list.iterator();
        while(i.hasNext()){
            String str = (String)i.next();
            getJspContext().setAttribute("item", str);
            getJspBody().invoke(null);
        }
    }
    public void setList(List<String> list){
        this.list = list;
    }
}


show.jsp========

...
<body>
<test:myTag2 list="${StrList }">
  ${item }<br />
</test:myTag2>
</body>
.....


getJspBody().invoke(null) 的意思是處理標籤的主體,並將它輸出到回應
null參數表示輸出會送往回應,而不是其他的 writer
範例中的標籤處理器會讀取儲存String類別的List後
以迴圈迭送的方式繞行,重新設定每一輪的"item"值後呼叫getJspBody().invoke(null)
陣列也可以用類似的方法寫成動態的輸出


標籤的運作有時需要依賴定的請求屬性
如果標籤找不到他需要的屬性,導致無法正常運作的情況下
也不能在這時候丟出 JspException,因為這樣會將整個頁面都清掉
SkipPageException 則是使用的好選擇
它能讓在標籤被呼叫之前就已經被評算的部分作為回應輸出
而例外發生後剩下的待處理部分會被丟棄
使用方法如下:
    public void doTag() throws IOException, JspException{
    .....
      if( DontWork ){                              //判斷式的部分無法運作的情形下丟出例外
        throw new SkipPageException();
      }
    }

雖然還有一種自訂標籤的實作 - Classic Tag 沒有提到
但那是JSP2.0之前使用的東西了
現在大多使用繼承 SimpleTagSupport 的類別就能解決了
實作上幾乎用不到的就不提了

2010年8月22日 星期日

網頁筆記(1)

1. HTML元素裡的每個屬性都必須用雙引號 " 包住

2. <a> 元素裡的 href 屬性作錨點的方法如下:
    目的地加入<a>元素並給予 id
    <h2><a id="point"> test point </a></h2>

    之後連結的時候在網址上添加辨識用的 id 就好了
    <a href="xxx\xxx.html#point"> click me </a>

3.在HTML 4.01的嚴格規範下,行內元素必須放在<h1>、<h2>...等區塊元素內
   例如可以將<img>元素包在<p>內, <p><img src="..." alt="..." /></p>
   <blockquote>是個較特殊的區塊元素,最好將行內元素包在區塊元素內後再整個放入

4.CSS中的margin 屬性能設定元素的邊緣和內容間的間隔

   text-decoration 能讓文字出現樣式,設定成 none 會清除上面的樣式
   underline、overline、line-through 也是能讓 text-decoration 出現效果的設定值

   border-collapse 能處理使用表格時邊框緊貼的情形
   沒用CSS做設定的話,相鄰的表格交界處看起來就像變粗的邊框
   設定成 border-collapse: collapse; 可以讓鄰近的邊框重疊,看起來只有單一邊框的外觀了

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 特性取得更詳細的資訊

2010年8月16日 星期一

JSP & Servlet 錯誤處理

錯誤頁面處理能在網站發生錯誤時,讓使用者看到比較親切的錯誤回應頁
當然有可能遇到必須依錯誤狀態指定不同的錯誤處理頁面的情形
這就是這一篇筆記所要談的

錯誤頁面的設置方法如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" isErrorPage="true"%>

使用page指令設置 isErrorPage="true"
這樣就告訴container這個頁面是錯誤頁面

接著在有可能發生錯誤或例外的頁面上作如下所示的設定:
<%@ page language="java" contentType="text/html; charset=UTF-8" errorPage="error.jsp"%>

在 errorPage 裡填入處理該錯誤的錯誤頁面位置
container會在錯誤發生時,將請求轉予該錯誤頁面

當然在所有的JSP上都使用page指令來指定錯誤頁面是很麻煩的事
在DD中也可以調整為整個Web Application宣告處理的錯誤頁面
container會使用DD中的<error-page>組態作為預設的錯誤頁面
但如果JSP頁面上有errorPage的page指令設置,會以頁面上的指令為優先
程式範例:
  <error-page>
    <exception-type>java.lang.Throwable</exception-type>
    <location>/errorPage.jsp</location>
  </error-page>

範例會在任何錯誤發生時轉送請求到所設定的頁面
<location>必須相對於整個應用程式的根目錄來設定,所以會以斜線開頭
如果不想設定成統一的處理方式的話,試試類似下面範例的作法
設定在ArithmeticException發生時會呼叫arithErrorPage.jsp:
<error-page>
    <exception-type>java.lang.ArithmeticException</exception-type>
    <location>/arithErrorPage.jsp</location>
</error-page>

除此之外也能針對HTTP狀態代碼宣告錯誤頁面
以下示範在 404狀態(找不到頁面)的錯誤頁面設定
<error-page>
    <error-code>404</error-code>
    <location>/errorPage.jsp</location>
</error-page>

因為錯誤頁面處理發生的例外
而這個例外是由container所供給的
我們可以在錯誤頁面被呼叫時,通過他來獲知發生的例外
Scriptlet裡有exception隱含物件可以用
JSP中可以用EL隱含物件 ${pageContext.exception }
這樣可以獲得更詳細的細節

例外發生時,除了將請求轉送給錯誤頁面外
自行捕捉例外並處理掉它也是解決之道
這不會在本篇筆記被討論到
下一篇紀錄JSTL的筆記會有關於在JSP中處理例外的筆記

2010年8月11日 星期三

JSP & Servlet 樣板概念簡介

這篇筆記說明如何在JSP中使用樣版減輕維護的負擔
在不涉及到後面筆記會談到的JSTL及自訂標籤的情況下
目前使用的會是include指令與<jsp:include>標準動作

include指令:

include指令會通知container將 included file 的所有內容複製到目前的頁面裡
例如現在有個頁面如下
test.jsp=========

<html><body>
<p> Insert page here</p>
<%@ include file="p.jsp" %>    //insert file to this page

</body></html>

又,我們寫個叫做 p.jsp 頁面如下
p.jsp=========

<h1>New Code!!</h1>
<p>this is amazing!!</p>

container就會自動將頁面結合成
test.jsp=========

<html><body>
<p> Insert page here</p>
<h1>New Code!!</h1>
<p>this is amazing!!</p>

</body></html>


<jsp:include>標準動作:

 <jsp:include>標準動作的程式看起來跟include指令很類似
test.jsp=========

<html><body>
<p> Insert page here</p>
<jsp:include  page="p.jsp" />    //insert file to this page

</body></html>

合成的結果是相同的
但兩者在執行的過程中卻是有差異的
inclue的作用就像是將所要引入的檔案內容複製到目標頁面貼上後,再進行轉譯
<jsp:include>則是在執行期插入所引入檔案的回應

<jsp:include>能確保總是得到最新的內容,但是會有效能上的負擔
include指令在處理不常更改的頁面時會是比較好的選擇
現在比較新的container(例如:Tomcat 5以上的版本)大多能偵測引入檔案的變更
所以使用include指令也能在引入檔案有變動時自動轉譯
<jsp:include>目前的需求建立在移植性上

<jsp:param>標籤提供了對於被嵌入的內容作客製化的處理
讓樣版能依照被套用頁面來調整內容
範例:
show.jsp========

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%@ taglib prefix="test" uri="randFunction" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>test</title>
</head>
<body>

<jsp:include page="a.jsp">
  <jsp:param value="999" name="num"/>
</jsp:include>

</body>
</html>

a.jsp=======

<p>We got number ${param.num }</p>


<jsp:param>必須包在<jsp:include>主體裡
對於被嵌入的檔案來說,它就像是其他的請求參數一般
範例的結果會在show.jsp中顯示出 We got number 999

JSP & Servlet Expression Language 使用介紹(2)

當想做的並不只是存取屬性與特性值,EL函式則可以做到功能性上的處理
當然,在JavaBean的 getter 與 setter 裡對想取得的值以程式來作處理沒有問題
只是將程式的邏輯分層等等考量,我們需要能做功能處理的函式
除此之外,本篇也會將EL剩下的東西記錄完

EL函式有一定的撰寫規則
1.撰寫的Java類別需要有公開的靜態(static)方法,而且可以接受參數
    雖然沒有強制規定,但回傳型態不應該是 void

2.撰寫標籤程式庫描述子(Tag Library Descriptor, TLD)
   TLD提供了定義函式的Java類別與呼叫函式的JSP之間的對映
   格式使用XML,應用上類似DD,但副檔名須改成 .tld
   檔案存放地點必須放在/WEB-INF目錄或其子目錄底下,container會幫我們讀取其內容

3.JSP中需加上taglib指令
   定義namespace,可以在一個JSP裡使用多個TLD的函式

4.用EL來呼叫函式

首先提供這次的程式碼:
func.java=========

package tw.vencs;

public class func {
    public static int random(){
        return (int)(Math.random()*100);
    }
}

tldTest.tld========

<?xml version="1.0" encoding="UTF-8" ?>

<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
    version="2.0">

    <tlib-version>1.2</tlib-version>   <!-- 宣告標籤版本 -->
    <short-name>randFunction</short-name>
    <uri>randFunction</uri> 

    <function>
        <name>getRand</name>
        <function-class>tw.vencs.func</function-class>
        <function-signature> int random() </function-signature>
    </function>

</taglib>

show.jsp======

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%@ taglib prefix="test" uri="randFunction" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>test</title>
</head>
<body>
${test:getRand()}
</body>
</html>


這支程式會在show.jsp裡提供一個隨機產生的數字
Java程式檔案提供了靜態的亂數方法
tld 檔案裡的 uri 元素提供了讓container辨識的功能,而不是指位置
使用EL函式須在 taglib 指令中填上要引用的函式的 uri,範例中JSP與 tld 都填上相同的 uri
<short-name>是給予IDE等開發工具辨識用的依據
這邊我們給它跟 uri 相同的名稱

<function>裡的<name>提供了給予外界呼叫函式用的名稱
JSP中的${test:getRand()}這行,getRand()就會對照 tld 檔,依其名稱使用指定的函式
<function-class>會指定該函式使用的類別
<function-signature>在所指定的類別中選擇被使用的靜態方法,型別也需要寫清楚

JSP的 taglib 宣告將引用的標籤檔案( uri 所指的 tld 檔 )以prefix命名為在此頁面使用的別名
所以${test:getRand()}就是呼叫test這個別名所指的 tld 檔裡的 getRand Function

通常我們使用EL就用到上述功能
雖然不應該在EL中進行運算或邏輯處理
但假使有一些小型的功能需求時,直接在EL中處理掉也是方便的選擇
接著來看看EL的運算子

算術運算子:
加 -> + , 減 -> - , 乘 -> * , 除 -> / or div , 求餘數 -> % or mod

邏輯運算子:
AND -> && or and , OR -> || or or , NOT -> ! or not

關係運算子:
相等 -> == or eq , 不相等 -> != or ne , 小於 -> < or lt , 大於 -> > or gt
小於或等於 -> <= or le , 大於或等於 -> >= or ge

EL裡將數字除以0得到的結果是 INFINITY ,而不是錯誤
而求餘數則不能拿0來求餘數,否則會發生例外
EL在設計上在處理 null 值時,會顯示空白而不會丟出例外

最後再提一個頁面轉向的標準動作
程式碼範例:<jsp:forward page="destination.jsp" />
使用注意事項就像其他的轉向功能一樣,轉交前不能輸出資料
這樣EL的部分就結束了

2010年8月2日 星期一

JSP & Servlet Expression Language 使用介紹(1)

基於以下考量,對開發者而言Scriptlet並不是很好的開發型態:
1.不應該預期網頁設計及美工人員懂Java
2.頁面維護考量
   混雜的HTML語法與JSP程式碼會讓頁面雜亂無章,難以做出修改及維護

JSP提供了Java Bean規格的標準動作及EL(Expression Language)來處理這些問題
它們能讓程式撰寫在別處,減少頁面的Java程式碼

當然,以效能角度而言,EL會增加解析變數名稱的負擔
對於效能有較大要求的頁面則應該多使用Scriptlet

先提到標準動作的一些注意事項
對於命名,Java Bean規格有著比較嚴格的限制
我們將參數給予 bean,經由它內部的處理後,取得我們所要的結果

bean類別的建構式絕不能接受參數
對於其內部成員變數的處理只由getter與setter負責
getter是從外部取出成員變數的值的方法
方法名稱是get後面接成員變數的名稱
每個詞的第一個字為大寫,如 getMemberID()
setter會從外部設定成員變數的值
它的寫法與getter一致,例如 setMemberID()
getter的回傳型別必須與setter的參數型別一致
若要有回傳boolean值的函式寫法會是在名稱前面加上 is,例如 isRight()
注意,整個Bean就是屬性,而不是它的成員變數
之後的範例可以看到實際的bean寫法

以之前提過的作法來說
使用Scriptlet操作的範例如下:
<% Test t = (Test) request.getAttribute("test"); %>
t value is :<%= t.getVal() %>

以標準動作改寫後:
<jsp:useBean id="test" class="Test" scope="request" />
t value is :<jsp:getProperty name="test" property="val" />

用來取得結果的是<jsp:getProperty>的這個標籤
而在那之前,必須先用<jsp:useBean>標籤來宣告及初始化所要使用的bean

id 給予了這個bean在Servlet中的辨識名稱
class是它的物件類別
scope為bean指定屬性作用域
<jsp:getProperty>裡的name指定實際使用的bean物件,對應<jsp:useBean>裡的id
property指定要讀取的特性名稱,也就是bean類別裡具有getter/setter  method的成員變數

當<jsp:useBean>被建立時,它會根據標籤裡的 id 及 scope 查詢是否有既存的 bean
如果找不到的話,它就根據class指定的類別建立一個 bean 物件
並將物件指派給 id 所指定的變數,再將這個變數當作屬性放到scope指定的作用域裡
將<jsp:useBean>與<jsp:setProperty>並用的效果,可確保一定有物件被建立,且會被指派值
但是如果想做到的效果是,僅在bean是新建立時才要指定值,該如何寫呢?
這時候的作法是如下範例:
<jsp:useBean id="test" class="Test" scope="request" />
    <jsp:setProperty name="test" property="val" value="1"  />
</jsp:useBean >

這樣一來,特定值只會在新的bean備建立時才執行
這跟Servlet轉譯之後的程式碼有關,我們不需要去了解

bean物件也可以有多型的應用
以下的程式碼是抽象類別 "Test" 與其子類別 "subTest" 的演出
<jsp:useBean id="test" type="Test" class="subTest" scope="request" />
抽象類別Test現在是沒有辦法單獨建立的
因為 class="Test" 這句程式碼轉成Servlet的實作方式會是 new Test();
實作上還是必須遵守Java的規則 
範例轉換成Servlet後的程式碼就會是  Test  test = new subTest();
這樣就可以作出多型了

假如使用了 type 卻不使用 class
Servlet會去檢查該 id 的物件是否存在在指定的作用域裡
存在的情況下可以正常運作;但不存在時,則無法運作

標準動作的標籤的 scope 預設值是 page
標籤裡的 param 屬性可以將請求參數的值指定給 bean,只要有適當的請求參數名稱即可
例如表單以POST方式送來名為 s 的參數( tag的name屬性是 s1 ):
<jsp:useBean id="test" type="Test" class="subTest" scope="request" />
<jsp:setProperty name="test" property="val" param="s1" />

這樣就能夠將參數 "s" 的值指定給bean的特性了
如果表單中欄位名稱(name屬性)等同於bean物件的 property 的值
連 param 屬性也不用設定,container會從名稱相符的請求參數中取值
如下範例:
Test.java==========

package tw.vencs;

public abstract class Test {
    private String s1;
    private String s2;
   
    public void setS1(String s1){
        this.s1 = s1;
    }
    public void setS2(String s2){
        this.s2 = s2;
    }
    public String getS1(){
        return s1;
    }
    public String getS2(){
        return s2;
    }
    public boolean isOrigin(){
       return true;
    }
}

subTest.java=======

package tw.vencs;

public class subTest extends Test {
    private int val1;
   
    public void setVal1(int val1){
        this.val1 = val1;
    }
    public int getVal1(){
        return val1;
    }
    public boolean isOrigin(){
       return false;
    }
}

test.html=======

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Test</title>
</head>
<body>
<form action="bean.jsp" method="post">
  <p>test column 1</p><input type="text" name="s1" />
  <p>test column 2</p><input type="text" name="s2" />
  <br />
  <input type="submit" value="Send" />
</form>
</body>
</html>

bean.jsp======

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Bean Test</title>
</head>
<body>
<jsp:useBean id="test" type="tw.vencs.Test" class="tw.vencs.subTest" scope="page" >
    <jsp:setProperty name="test" property="s1" />
    <jsp:setProperty name="test" property="s2" />
</jsp:useBean>

<p>The word is <jsp:getProperty name="test" property="s1" /> </p>

<%-- 下面紅字的部分使用了EL,EL在文章最後會介紹 --%>
<%-- test是上面宣告的bean物件名稱,origin 會回傳 isOrigin() 的結果,在這裡回傳false --%>
<p>The class is origin: ${test.origin} </p>
</body>
</html>


上面的範例示範僅有使用到 "s1" 請求參數
將所有的請求參數名稱完全與bean的 property 的值相等的話,還能做到更方便的操作
<jsp:useBean id="test" type="tw.vencs.Test" class="tw.vencs.subTest" scope="page" >
    <jsp:setProperty name="test" property="*" />
</jsp:useBean>

上面的程式碼中的 property 使用了萬用自元 *
它能夠指示container檢視bean裡的 getter 與 setter
而 getter 與 setter 裡不必如範例這般簡單,可以增加或修改要回傳的處理之類的程式碼
並依此判斷出bean所有的特性,並將他們對應到請求參數的名稱
一口氣設定好所有的特性

JavaBean 的特性可以是任何型別
但如果是String或是基本型別(primitive type),container能做到資料的強制轉型
其餘的類別會由container試著呼叫他們的 toString() method
當然如果輸入的請求參數無法被轉換時,會回傳錯誤
所以要先確認請求參數是否可以被順利轉型
另外,使用Scriptlet的話會沒法讓型別自動轉換運作
即使將Scriptlet與標準動作標籤混用也沒辦法

標準動作標籤的使用也不是萬能的
它並不支援巢狀特性的處理
如果Bean裡還有非字串與非基本型別的類別時
我們沒辦法這樣寫: <jsp:setProperty name="test" property="OtherType.id" />
所以接下來要說明的是EL(Expression Language)

EL表達式的型態是 ${applicationScope.mail} 這樣
效果等同於 <%= application.getAttribute("mail") %>
將表達式包在大括號中,並在前面加個 $
表達式里的第一個具名變數都是隱含物件
先聲明很重要的一點,表達式與Java並不是等號
EL提供不使用Java來存取Java物件的機制,所以兩者個語法規則有相異之處

EL的隱含物件共有:
pageScope、requestScope、sessionScope、applicationScope
param、paramValues、header、headerValues、cookie、initParam、pageContext
最剛開始提到的四個隱含物件能指定作用域
除了最後的pageContext以外的隱含物件都是Map型別,也就是鍵值對的群集
pageContext 是指向 pageContext物件的實際參考
從這邊也可以看出EL的隱含物件與JSP隱含物件並不相同

EL裡使用 . 表達隱含物件或是屬性的特性
[] 也可以做到同樣的效果,例如 ${ test["id"] } 等同 ${ test.id }
那,這兩種運算子的差別在哪邊呢?
差別在於 [] 還能表達陣列或 List,這是點號做不到的
如在Servlet中存了一筆陣列資料:
String[] SA = { "one", "two", "three"}
request.setAttribute("test", SA);

JSP中用EL的得到的值:
${test}         -> 呼叫陣列的toString() method
${test[0]}     -> one
${test["1"]}  -> two

可以看到範例的最後一個並不符合一般程式的寫法
對陣列或List來說,字串型別的索引值會被強制轉型成 int
當然這個字串也要能被轉型才不會收到錯誤訊息
這就是EL與Java不相同的其中一個地方

如果 [] 裡沒有雙引號,container會根據 [] 裡的內容搜尋該名稱的屬性進行轉換
能做為搜尋Map類別的使用,找不到時則會回傳 null
範例如下:

request.setAttribute("Convertion", "test");
${ demo[Convertion] }   <%-- 會被轉換成 ${ demo["test"] } --%>

另外,雖然 ${ test["id"] } 等同 ${ test.id } 的寫法可以使用
但是不能出現這樣的寫法 ->  ${ test["1"] } O    ${ test.1 } X
命名必須遵守Java規則,不能出現以數字開頭命名的變數或屬性

接著回頭說明EL的隱含物件
param、paramValues是很相似的隱含物件
${param.xxx} 等同於 <%=request.getParameter("xxx") %>
paramValues使用方式範例:  ${ paramValues.demo[0] }
header隱含物件會將所有的header放置在Map裡,使用法類似paramValues
它可以讓我們得到更多的資訊,如: ${ header["host"] }
想在EL裡取得Request 請求物件的方式與requestScope沒有關係
requestScope只是一個包含了Request作用域的屬性的Map物件
透過 pageContext 才能夠取得請求物件:${ pageContext.request.id }
其餘的作用域也是如此

作用域隱含物件其實並不一定需要使用
即使只知道物件的名稱,而不知道所存在的作用域
也能夠使用 ${ Object.Attribute }  這樣的寫法來呼叫
作用域提供的功能除了避免發生物件名稱衝突,呼叫正確的物件外
另一個作用是接受讓屬性名稱可以不以遵守Java命名規則來命名
例如 ${ com.Object.Attribute } 需改成 ${ requestScope["com.Object"].Attribute } 才能執行

EL的 cookie 隱含物件提供了方便的使用方法
使用Scriptlet時所用的Cookie隱含物件的讀取方式,必須繞行整個Cookie陣列才行
EL的則很輕鬆 ${ cookie.userName.value }

initParam則可以讓我們使用Context初始參數,當然必須先在DD中設定好
使用 ${ initParam.Attribute } 這樣的呼叫方式
這樣就講完了EL的隱含物件