javaweb回顾第八篇如何创建自定义标签
前言:在javaweb开发中自定义标签的用处还是挺多的。今天和大家一起看自定义标签是如何实现的。
1:什么是标签
标签是一种XML元素,通过标签可以使JSP页面变得简介易用,而且标签具有很好的复用性。
2:自定义标签的标签库主要的接口以及类的继承实现关系图
3:一步步实现自定义标签
3.1:Tag接口
我们先看一个标签<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>
现在开始执行效果如下
3.1:IterationTag接口
上面我们都一直说的标签内容都一次性完成,但是如果是循环标题体内容怎么办,那么就用到了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, "7808@outlook.com"); UserBean user2 = new UserBean("lisi", 15, "16455@qq.com"); UserBean user3 = new UserBean("wangwu", 35, "7808@outlook.com"); 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>
效果如下
4:简单标签开发
为了简化自定义标签开发,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页面之间调用即可。关于简单标签的开发,大家可以自行实践。