1. 程式人生 > >利用 JSP 2 提供的 SimpleTagSupport 開發自定義標籤

利用 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 中開發標籤庫只需如下幾個步驟:

  1. 開發自定義標籤處理類;
  2. 建立一個 *.tld 檔案,每個 *.tld 檔案對應一個標籤庫,每個標籤庫對應多個標籤;
  3. 在 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:確定使用哪個標籤庫。
  • 標籤名:確定使用哪個標籤。

使用標籤庫分成以下兩個步驟:

  1. 匯入標籤庫:使用 taglib 編譯指令匯入標籤庫,就是將標籤庫和指定字首關聯起來。
  2. 使用標籤:在 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. 簡單標籤
圖 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();              }          }      } }