2010年8月23日 星期一

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 的類別就能解決了
實作上幾乎用不到的就不提了

沒有留言:

張貼留言