前言:在javaweb開發中自定義標簽的用處還是挺多的。今天和大家一起看自定義標簽是如何實現的。
標簽是一種XML元素,通過標簽可以使JSP頁面變得簡介易用,而且標簽具有很好的復用性。
我們先看一個標簽<td></td>這個標簽有開始標簽和結束標簽,而且還有<tr>這樣的父標簽,那麼實現一個簡單的標簽需要什麼呢
第一:開始標簽 第二:結束標簽第三:資源釋放3個方法,而且還有父標簽,如果我們要得到這個JSP上的內容我們還需要一個PageContext那麼現在我們應該清晰了實現一個標簽需要的元素。ok我們來看看Tag接口都有哪些內容
3.1.1:int doStartTag() throws JspException;這個是開始執行的起始方法
3.1.2:int doEndTag() throws JspException;這個是即將結束的結束方法
3.1.3:void release();釋放對象的資源
3.1.4:void setPageContext(PageContext pc);設置當前頁的上下文對象
3.1.5: void setParent(Tag t);設置父標簽
3.1.6:Tag getParent();獲取父標簽
通過上面的介紹我們現在應該知道怎麼去寫一個標簽了,我們小試牛刀一下
public class HelloTag implements Tag{ private PageContext pageContext; private Tag parent; public void setPageContext(PageContext pc) { this.pageContext=pc;//這個方法由jsp頁面的實現對象調用 } public void setParent(Tag t) { this.parent=t; } public Tag getParent() { return parent; } public int doStartTag() throws JspException { return SKIP_BODY; } public int doEndTag() throws JspException { //利用pageContext來獲取jspWriter對象 JspWriter out=pageContext.getOut(); try { //利用JSPWriter向客戶端輸入信息 out.print("Hello Tag"); } catch (IOException e) { e.printStackTrace(); } return SKIP_PAGE; } public void release() { }
其中SKIP_BODY表示忽略標簽體內容,下面我們會說到。既然寫完了一個標簽體我們就開始配置了
首先在WEB-INFO創建一個tlds文件夾然後創建一個tld文件然後設置如下
<tag> <name>hello</name> <tag-class>com.lp.tags.HelloTag</tag-class> <body-content>empty</body-content>//表示標簽沒有內容 </tag>
然後我們在創建一個jsp文件然後在jsp文件頭部加上Taglib指令元素<%@ taglib uri="/WEB-INF/tlds/CustomTaglib.tld" prefix="hello"%>
在jsp頁面就可以直接引用HelloTag標簽了比喻我的是<hello:hello></hello:hello>
啟動運行結果如下
有人又問了如果<td>這樣的標簽都有屬性啊,如果有屬性我怎麼辦呢,這個也簡單沒有屬性我們就加入屬性。我們來實現一個加法的自定義標簽。這個我使用TagSupport類,從上面圖中我們可以看出這個類實現了Tag接口,它會使我們寫標簽更加簡單
public class AddTag extends TagSupport{ private int num1; private int num2; public int getNum2() { return num2; } public void setNum2(int num2) { this.num2 = num2; } public int getNum1() { return num1; } public void setNum1(int num1) { this.num1 = num1; } public int doEndTag() throws JspException { JspWriter out=pageContext.getOut(); int num=num1+num2; try { out.print(num); } catch (IOException e) { e.printStackTrace(); } return EVAL_PAGE; }
有人又問你為什麼沒有寫doStartTag方法啊,其實TagSupport類已經幫我們實現了,它默認情況是忽略標簽中的內容的。現在我們在此配置tld文件
<tag> <name>add</name> <tag-class>com.lp.tags.AddTag</tag-class> <attribute>//表示屬性 <name>num1</name>屬性命名 <required>true</required>是否必須輸入 <rtexprvalue>true</rtexprvalue>是否是可運行的表達式 </attribute> <attribute> <name>num2</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag>
現在我們在jsp中加入以下代碼
<%@ taglib uri="/WEB-INF/tlds/CustomTaglib.tld" prefix="addtaglib"%>
<body> 自定義的標簽: <% int num1 = Integer.parseInt(request.getParameter("num1")); int num2 = Integer.parseInt(request.getParameter("num2"));%> 算法: <addtaglib:add num2="<%=num1 %>" num1="<%=num2 %>"></addtaglib:add> </body>
再次運行我們看看結果
有人又說了,你這寫的標簽都沒有標簽內容,你能不能實現一個標簽帶有內容的呢,ok這個沒問題,剛剛我們說了SKIP_BODY表示忽略標簽內容那麼有個相反的EVAL_BODY_INCLUDE表示帶有標簽中的內容,在這裡我們一起實現一個Switch case default三個標簽體聯用的簡單功能
我們先看SwitchTag標簽
public class SwitchTag extends TagSupport{ private static final long serialVersionUID = 1L; //用於判斷子標簽是否已執行 private boolean childTagExec; public SwitchTag() { childTagExec=false; } public int doStartTag() throws JspException { //當遇到switch的起始標簽的時候子標簽還沒執行 childTagExec=false; return EVAL_BODY_INCLUDE;//此時開始執行Switch內部的Case標簽了 } /** * 由子標簽處理器對象調用,用於判斷是否可以執行自身的標簽體 * @return */ public synchronized boolean isExec() { return (!childTagExec); } /** * 如果子標簽任何一個滿足條件就調用這個方法 通知父標簽 * 這樣其他子標簽就忽略他們自身標簽體,從而實現Switch case */ public synchronized void childTagSucceeded() { childTagExec=true; } public void release() { childTagExec=false; }
CaseTag
public class CaseTag extends TagSupport{ private static final long serialVersionUID = 1L; private boolean cond;//表示條件(比喻case:1此類) public CaseTag() { cond=false; } public void setCond(boolean cond) { this.cond=cond; } public int doStartTag() throws JspException { Tag parent=getParent();//獲取父標簽 //判斷是否可以執行自身標簽 if(!((SwitchTag)parent).isExec()) { return SKIP_BODY; } //如果條件為true,則通知父標簽有一個子標簽滿足條件 //否則忽略標簽體 if(cond) { ((SwitchTag)parent).childTagSucceeded(); return EVAL_BODY_INCLUDE; } else { return SKIP_BODY; } } public void release() { cond=false; }
DefaultTag
public class DefaultTag extends TagSupport{ private static final long serialVersionUID = 1L; public int doStartTag() throws JspException { Tag parent=getParent(); if (!((SwitchTag)parent).isExec()) { return SKIP_BODY; } ((SwitchTag)parent).childTagSucceeded();//如果所有Case都不滿足則執行Default標簽 return EVAL_BODY_INCLUDE; } }
我們在次配置tld文件
<tag> <name>switch</name> <tag-class>com.lp.tags.SwitchTag</tag-class> <body-content>jsp</body-content> </tag> <tag> <name>case</name> <tag-class>com.lp.tags.CaseTag</tag-class> <body-content>jsp</body-content> <attribute> <name>cond</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag> <tag> <name>default</name> <tag-class>com.lp.tags.DefaultTag</tag-class> <body-content>jsp</body-content> </tag>
其中<body-content>jsp</body-content>中jsp表示支持jsp具有的一切功能(比喻jsp9種內置對象)
<body> <% String userName = request.getParameter("userName"); %> <mytag:switch> <mytag:case cond='<%=userName.equals("zhangsan")%>'> <%out.print("張三");%> </mytag:case> <mytag:case cond='<%=userName.equals("lisi")%>'> <%out.print("李四");%> </mytag:case> <mytag:case cond='<%=userName.equals("wangwu")%>'> <%out.print("王五");%> </mytag:case> <mytag:default> <%out.print("無");%> </mytag:default> </mytag:switch> </body>
現在開始執行效果如下
上面我們都一直說的標簽內容都一次性完成,但是如果是循環標題體內容怎麼辦,那麼就用到了IterationTag接口,此接口增加了一個方法
public int doAfterBody() throws JspException該方法表示每次對標簽體處理之後被調用也就是說在doStartTag方法之後doEndTag方法之前被調用,如果沒有的話就不執行。
新增加了一個常量EVAL_BODY_AGAIN表示再次執行標簽體。現在我們實現一個獲取多條用戶信息展示的功能
public class UserBean implements Serializable{ private static final long serialVersionUID = 1L; public UserBean(){} public UserBean(String userName,int age,String email) { this.age=age; this.email=email; this.userName=userName; } private String userName; private int age; private String email; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }
public class IterateTag extends TagSupport{ private static final long serialVersionUID = 1L; private Iterator items;//獲取集合 private String itemId;//對象的標識 private Object item;//迭代對象中的每一個對象 public IterateTag() { items=null; } public void release() { items=null; } /** * 得到集合的迭代對象 */ public void setItems(Collection cl) { if(cl.size()>0) items=cl.iterator(); } public void setVar(String var) { this.itemId=var; } public int doStartTag()throws JspException { if(items.hasNext())//首先被執行 { item=items.next(); } else{ return SKIP_BODY; } saveItems();//把迭代的對象保存在pageContext中 return EVAL_BODY_INCLUDE; } public int doAfterBody() throws JspException { if(items.hasNext())//直到把迭代對象中的每一項都放進pageContext中 { item=items.next(); } else{ return SKIP_BODY; } saveItems(); return EVAL_BODY_AGAIN; } public void saveItems() { if(item==null) { pageContext.removeAttribute(itemId,pageContext.PAGE_SCOPE); } else{ pageContext.setAttribute(itemId, item);//如果加入相同的id會進行覆蓋 } } }
<tag> <name>iterate</name> <tag-class>com.lp.tags.IterateTag</tag-class> <body-content>JSP</body-content> <attribute> <name>items</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>var</name> <required>true</required> <rtexprvalue>false</rtexprvalue> </attribute> </tag>
<body> <% ArrayList al = new ArrayList(); UserBean user1 = new UserBean("zhangsan", 25, "[email protected]"); UserBean user2 = new UserBean("lisi", 15, "[email protected]"); UserBean user3 = new UserBean("wangwu", 35, "[email protected]"); al.add(user1); al.add(user2); al.add(user3); %> <table> <tr> <td>用戶名</td> <td>年齡</td> <td>郵箱</td> </tr> <iterator:iterate items="<%=al%>" var="user"> <jsp:useBean id="user" class="com.lp.beans.UserBean"></jsp:useBean> <tr> <td><jsp:getProperty property="userName" name="user" /></td> <td>${user.age}</td> <td>${user["email"]}</td> </tr> </iterator:iterate> </table> </body>
效果如下
為了簡化自定義標簽開發,JSP2.0加入了簡單標簽的開發實現的接口是SimpleTag,我們一起看下SimpleTag的主要方法
4.1: public void setJspContext( JspContext pc )該方法被容器調用,設置JspContext,JspContext 是PageContext的基類
4.2:public void setParent( JspTag parent );設置父標簽
4.3:public JspTag getParent();獲取父標簽
4.4:public void setJspBody( JspFragment jspBody );該方法用於設置標簽體標簽體,標簽體由JspFragment對象提供,可以把JspFragment看做是一個對象封裝一段JSP代碼,可以被多次執行。
4.5:public void doTag(),主要處理標簽和標簽體的業務邏輯
public class WelcomeSimpleTag extends SimpleTagSupport{ private JspFragment jspFragment; private String name; public void setJspBody(JspFragment jspBody) { this.jspFragment=jspBody; } public void setName(String name) { this.name=name; } public void doTag() throws JspException, IOException { JspContext jspContext=getJspContext(); JspWriter out=jspContext.getOut(); out.print(name); jspFragment.invoke(null); }
然後在jsp頁面之間調用即可。關於簡單標簽的開發,大家可以自行實踐。