利用 JSP 2 提供的 SimpleTagSupport 開發自定義標籤
自定義標籤庫並不是 JSP 2 才出現的,JSP 1.1 版中已經增加了自定義標籤庫規範,自定義標籤庫是一種非常優秀的表現層元件技術。通過使用自定義標籤庫,可以在簡單的標籤中封裝複雜的功能。
為什麼要使用自定義標籤呢?主要是為了取代醜陋的 JSP 指令碼。在 HTML 頁面中插入 JSP 指令碼有如下幾個壞處:
- JSP 指令碼非常醜陋,難以閱讀。
- JSP 指令碼和 HTML 程式碼混雜,維護成本高。
- HTML 頁面中嵌入 JSP 指令碼,導致美工人員難以參與開發。
出於以上三點的考慮,我們需要一種可在頁面中使用的標籤,這種標籤具有和 HTML 標籤類似的語法,但由可以完成 JSP 指令碼的功能——這種標籤就是 JSP 自定義標籤。
在 JSP1.1 規範中開發自定義標籤庫比較複雜,JSP 2 規範簡化了標籤庫的開發,在 JSP 2 中開發標籤庫只需如下幾個步驟:
- 開發自定義標籤處理類;
- 建立一個 *.tld 檔案,每個 *.tld 檔案對應一個標籤庫,每個標籤庫對應多個標籤;
- 在 JSP 檔案中使用自定義標籤。
開發自定義標籤類
標籤庫和實際開發
標籤庫是非常重要的技術,通常來說,初學者、普通開發人員自己開發標籤庫的機會很少,但如果希望成為高階程式設計師,或者希望開發通用框架,就需要大量開發自定義標籤了。所有的 MVC 框架,如 Struts 2、SpringMVC、JSF 等都提供了豐富的自定義標籤。
當我們在 JSP 頁面使用一個簡單的標籤時,底層實際上由標籤處理類提供支援,從而可以使用簡單的標籤來封裝複雜的功能,從而使團隊更好地協作開發(能讓美工人員更好地參與 JSP 頁面的開發)。
早期 JSP 自定義標籤類開發過程略微複雜一些,但 JSP 2 已經簡化了這個過程,它只要自定義標籤類都必須繼承一個父類:javax.servlet.jsp.tagext.SimpleTagSupport,除此之外,JSP 自定義標籤類還有如下要求。
- 如果標籤類包含屬性,每個屬性都有對應的 getter 和 setter 方法。
- 重寫 doTag() 方法,這個方法負責生成頁面內容。
下面開發一個最簡單的自定義標籤,該標籤負責在頁面上輸出 HelloWorld。
1 2 3 4 5 6 7 8 9 10 11 |
// 標籤處理類,繼承 SimpleTagSupport 父類
public class HelloWorldTag extends SimpleTagSupport
{
// 重寫 doTag 方法,該方法在標籤結束生成頁面內容
public void doTag()throws JspException,
IOException
{
// 獲取頁面輸出流,並輸出字串
getJspContext().getOut().write("Hello World");
}
}
|
上面這個標籤處理類非常簡單,它繼承了 SimpleTagSupport 父類,並重寫 doTag() 方法,而 doTag() 方法則負責輸出頁面內容。該標籤沒有屬性,因此無須提供 setter 和 getter 方法。
建立 TLD 檔案
TLD 是 Tag Library Definition 的縮寫,即標籤庫定義,檔案的字尾是 tld,每個 TLD 檔案對應一個標籤庫,一個標籤庫中可包含多個標籤,TLD 檔案也稱為標籤庫定義檔案。
標籤庫定義檔案的根元素是 taglib,它可以包含多個 tag 子元素,每個 tag 子元素都定義一個標籤。通常我們可以到 Web 容器下複製一個標籤庫定義檔案,並在此基礎上進行修改即可。例如 Tomcat6.0,在 webapps\examples\WEB-INF\jsp2 路徑下包含了一個 jsp2-example-taglib.tld 檔案,這就是示範用的標籤庫定義檔案。
將該檔案複製到 Web 應用的 WEB-INF/ 路徑,或 WEB-INF 的任意子路徑下,並對該檔案進行簡單修改,修改後的 mytaglib.tld 檔案程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?
xml
version
=
"1.0"
encoding
=
"GBK"
?>
<
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 web-jsptaglibrary_2_0.xsd"
version
=
"2.0"
>
<
tlib-version
>1.0</
tlib-version
>
<
short-name
>mytaglib</
short-name
>
<!-- 定義該標籤庫的URI -->
<
uri
>http://www.crazyit.org/mytaglib</
uri
>
<!-- 定義第一個標籤 -->
<
tag
>
<!-- 定義標籤名 -->
<
name
>helloWorld</
name
>
<!-- 定義標籤處理類 -->
<
tag-class
>lee.HelloWorldTag</
tag-class
>
<!-- 定義標籤體為空 -->
<
body-content
>empty</
body-content
>
</
tag
>
</
taglib
>
|
上面標籤庫定義檔案也是一個標準的 XML 檔案,該 XML 檔案的根元素是 taglib 元素,因此我們每次編寫標籤庫定義檔案都直接新增該元素即可。
taglib 下有三個子元素:
- tlib-version:指定該標籤庫實現的版本,這是一個作為標識的內部版本號,對程式沒有太大的作用。
- short-name:該標籤庫的預設短名,該名稱通常也沒有太大的用處。
- uri:這個屬性非常重要,它指定該標籤庫的 URI,相當於指定該標籤庫的唯一標識。如上粗體字程式碼所示,JSP 頁面中使用標籤庫時就是根據該 URI 屬性來定位標籤庫的。
除此之外,taglib 元素下可以包含多個 tag 元素,每個 tag 元素定義一個標籤,tag 元素下至少應包含如下三個子元素:
- name:該標籤庫的名稱,這個屬性很重要,JSP 頁面中就是根據該名稱來使用此標籤的。
- tag-class:指定標籤的處理類,毋庸置疑,這個屬性非常重要,指定了標籤由哪個 Java 類來處理。
- body-content:這個屬性也很重要,它指定標籤體內容。該元素的值可以是如下幾個:
- tagdependent:指定標籤處理類自己負責處理標籤體。
- empty:指定該標籤只能作用空標籤使用。
- scriptless:指定該標籤的標籤體可以是靜態 HTML 元素,表示式語言,但不允許出現 JSP 指令碼。
- JSP:指定該標籤的標籤體可以使用 JSP 指令碼。
實際上由於 JSP 2 規範不再推薦使用 JSP 指令碼,所以 JSP 2 自定義標籤的標籤體中不能包含 JSP 指令碼。所以實際上 body-content 元素的值不可以是 JSP。
定義了上面的標籤庫定義檔案後,將標籤庫檔案放在 Web 應用的 WEB-INF 路徑,或任意子路徑下,Java Web 規範會自動載入該檔案,則該檔案定義的標籤庫也將生效。
使用標籤庫
在 JSP 頁面中確定指定標籤需要 2 點:
- 標籤庫 URI:確定使用哪個標籤庫。
- 標籤名:確定使用哪個標籤。
使用標籤庫分成以下兩個步驟:
- 匯入標籤庫:使用 taglib 編譯指令匯入標籤庫,就是將標籤庫和指定字首關聯起來。
- 使用標籤:在 JSP 頁面中使用自定義標籤。
taglib 的語法格式如下:
1 |
<%@ taglib uri="tagliburi" prefix="tagPrefix" %>
|
其中 uri 屬性確定標籤庫的 URI,這個 URI 可以確定一個標籤庫。而 prefix 屬性指定標籤庫字首,即所有使用該字首的標籤將由此標籤庫處理。
使用標籤的語法格式如下:
1 2 3 |
<
tagPrefix:tagName
tagAttribute=”tagValue” … >
<
tagBody
/>
</
tagPrefix:tagName
>
|
如果該標籤沒有標籤體,則可以使用如下語法格式:
1 |
<
tagPrefix:tagName
tagAttribute=”tagValue” … />
|
上面使用標籤的語法裡都包含了設定屬性值,前面我們介紹的 HelloWorldTag 標籤沒有任何屬性,所以使用該標籤只需用 <mytag:helloWorld/> 即可。其中 mytag 是 taglib 指令為標籤庫指定的字首,而 helloWorld 是標籤名。
下面是使用 helloWorld 標籤的 JSP 頁面程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<%@ page contentType="text/html; charset=GBK"%>
<!-- 匯入標籤庫,指定mytag字首的標籤,
由http://www.crazyit.org/mytaglib的標籤庫處理 -->
<%@ taglib uri="http://www.crazyit.org/mytaglib" prefix="mytag"%>
<
html
>
<
head
>
<
title
>自定義標籤示範</
title
>
</
head
>
<
body
bgcolor
=
"#ffffc0"
>
<
h2
>下面顯示的是自定義標籤中的內容</
h2
>
<!-- 使用標籤 ,其中mytag是標籤字首,根據taglib的編譯指令,
mytag字首將由http://www.crazyit.org/mytaglib的標籤庫處理 -->
<
mytag:helloWorld
/><
BR
>
</
body
>
</
html
>
|
上面頁面中第一行粗體字程式碼指定了 http://www.crazyit.org/mytaglib 標籤庫的字首為 mytag,第二行粗體字程式碼表明使用 mytag 字首對應標籤庫裡的 helloWorld 標籤。瀏覽該頁面將看到如圖 1 所示效果:
圖 1. 簡單標籤
帶屬性的標籤
前面的簡單標籤既沒有屬性,也沒有標籤體,用法、功能都比較簡單。實際上還有如下兩種常用的標籤:
- 帶屬性的標籤。
- 帶標籤體的標籤。
正如前面介紹的,帶屬性標籤必須為每個屬性提供對應的 setter 和 getter 方法。帶屬性標籤的配置方法與簡單標籤也略有差別,下面介紹一個帶屬性標籤的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
public class QueryTag extends SimpleTagSupport
{
//標籤的屬性
private String driver;
private String url;
private String user;
private String pass;
private String sql;
//執行資料庫訪問的物件
private Connection conn = null;
private Statement stmt = null;
private ResultSet rs = null;
private ResultSetMetaData rsmd = null;
//標籤屬性driver的setter方法
public void setDriver(String driver) {
this.driver = driver;
}
//標籤屬性url的setter方法
public void setUrl(String url) {
this.url = url;
}
//標籤屬性user的setter方法
public void setUser(String user) {
this.user = user;
}
//標籤屬性pass的setter方法
public void setPass(String pass) {
this.pass = pass;
}
//標籤屬性driver的getter方法
public String getDriver() {
return (this.driver);
}
//標籤屬性url的getter方法
public String getUrl() {
return (this.url);
}
//標籤屬性user的getter方法
public String getUser() {
return (this.user);
}
//標籤屬性pass的getter方法
public String getPass() {
return (this.pass);
}
//標籤屬性sql的getter方法
public String getSql() {
return (this.sql);
}
//標籤屬性sql的setter方法
public void setSql(String sql) {
this.sql = sql;
}
public void doTag()throws JspException,
IOException
{
try
{
//註冊驅動
Class.forName(driver);
//獲取資料庫連線
conn = DriverManager.getConnection(url,user,pass);
//建立Statement物件
stmt = conn.createStatement();
//執行查詢
rs = stmt.executeQuery(sql);
rsmd = rs.getMetaData();
//獲取列數目
int columnCount = rsmd.getColumnCount();
//獲取頁面輸出流
Writer out = getJspContext().getOut();
//在頁面輸出表格
out.write("<
table
border
=
'1'
bgColor
=
'9999cc'
width
=
'400'
>");
//遍歷結果集
while (rs.next())
{
out.write("<
tr
>");
//逐列輸出查詢到的資料
for (int i = 1 ; i <= columnCount ; i++ )
{
out.write("<
td
>");
out.write(rs.getString(i));
out.write("</
td
>");
}
out.write("</
tr
>");
}
}
catch(ClassNotFoundException cnfe)
{
cnfe.printStackTrace();
throw new JspException("自定義標籤錯誤" + cnfe.getMessage());
}
catch (SQLException ex)
{
ex.printStackTrace();
throw new JspException("自定義標籤錯誤" + ex.getMessage());
}
finally
{
//關閉結果集
try
{
if (rs != null)
rs.close();
if (stmt != null)
stmt.close();
if (conn != null)
conn.close();
}
catch (SQLException sqle)
{
sqle.printStackTrace();
}
}
}
}
|