一、为什么需要自定义标签
1)与JSP页面整体统一
2)还具有一定的业务逻辑功能,例如:循环、判断等
二、自定标签开发步骤
一】第一步:写一个自定义的标签处理类
public class Demo implements SimpleTag{ private PageContext pageContext; //PageContext是JspContext的子类 @Override public void setJspContext(JspContext jspContext) { System.out.println("Do setJspContext"); //得到jsp页面对象 pageContext = (PageContext) jspContext; } @Override public void doTag() throws JspException, IOException { System.out.println("Do doTag"); //取得HttpServletRequest对象 HttpServletRequest request= (HttpServletRequest) pageContext.getRequest(); //得到ip地址 String ip = request.getRemoteAddr(); System.out.println(ip); //取得out <---> JspWriter对象 JspWriter out = pageContext.getOut(); //向浏览器输出IP地址信息 out.write("<font size='30px' color='red'>" + "ip:" + ip + "</font>"); } @Override public JspTag getParent() { return null; } @Override public void setJspBody(JspFragment arg0) { } @Override public void setParent(JspTag arg0) { } }
二】第二步:在/WEB-INF/目录下,写一个.tld文件,目的是让web容器知道自定义标签和标签处理类的对应关系
<?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"> <description>JSTL 1.1 core library</description> <display-name>JSTL core</display-name> <tlib-version>1.1</tlib-version> <short-name>sky</short-name> <uri>http://com.suse/jsp/jstl/sky</uri> <tag> <name>ip</name> <tag-class>com.suse.simpletag.Demo</tag-class> <body-content>empty</body-content> <!--empty:此标签为空标签--> </tag> </taglib>
三】第三步:在JSP页面中,通过<%@taglib%>指令引用标签库
<%@ taglib prefix="sky" uri="http://com.suse/jsp/jstl/sky"%> <sky:ip />
三、标签处理过程
1)SimpleTag接口中有5个方法,这5个方法容器会在适当的时候选择调用,
2)其中,doTag()方法最为核心,该方法中封装了该标签的处理业务逻辑
3)项目中通常都会使用SimpleTagSupport类,该类已经对SimpleTag接口实现
四、案例:
一】控制标签中的内容是否执行( <sky:execute>标签 )
1)执行标签体 :调用invoke(null)方法
2)不执行标签体 :不调用invoke(null)方法
code: public class ExecuteTag extends SimpleTagSupport { @Override public void doTag() throws JspException, IOException { //将此标签内的内容封装成JspFragment对象 JspFragment jspFragment = this.getJspBody(); //将标签中的内容进行输出,null表示默认输出到浏览器中,否则会输出到相应的流中 jspFragment.invoke(null); //若没有这条语句,那么此标签的内容便不会输出到浏览器页面中 } } tag: <tag> <name>execute</name> <tag-class>com.suse.simpletag.ExecuteTag</tag-class> <body-content>scriptless</body-content> <!-- //scriptless表示不允许有脚本存在此标签内部 --> </tag>
二】控制标签后的内容是否执行
方法:抛出 SkipPageException 异常。导致标签体后的内容不执行
code: public class SkipTag extends SimpleTagSupport { @Override public void doTag() throws JspException, IOException { JspFragment jspFragment = this.getJspBody(); jspFragment.invoke(null); //设置此标签后的内容不再执行。方法:抛出一个 SkipPageException() 异常。 throw new SkipPageException(); } } tag: <tag> <name>skip</name> <tag-class>com.suse.simpletag.SkipTag</tag-class> <body-content>scriptless</body-content> </tag>
三】将标签体中内容转换成大写
public class UpperTag extends SimpleTagSupport { @Override public void doTag() throws JspException, IOException { JspFragment jspFragment = this.getJspBody(); //创建一个缓冲区,用于存储标签中的内容 StringWriter writer = new StringWriter(); jspFragment.invoke(writer); //将缓冲区中的内容进行处理(小写 --> 大写) StringBuffer buffer = writer.getBuffer(); String upString = buffer.toString().toUpperCase(); //将转换后的内容输出到浏览器中 JspContext jspContext = this.getJspContext(); JspWriter out = jspContext.getOut(); out.write(upString); }
四】开发带属性的标签:
1)在标签处理器中编写每个属性对应的setter方法
2)在TLD文件中描述标签属性
code: public class ForSkip extends SimpleTagSupport{ private String var; private int begin; private int end; private int step = 1; //增量默认为1 public void setVar(String var) { this.var = var; } public void setBegin(int begin) { this.begin = begin; } public void setEnd(int end) { this.end = end; } public void setStep(int step) { this.step = step; } @Override public void doTag() throws JspException, IOException { for (int i = this.begin; i <= this.end; i += this.step ) { this.getJspContext().setAttribute(this.var, i); this.getJspBody().invoke(null); } } } tld: <tag> <name>forskip</name> <tag-class>com.suse.simpletag.ForSkip</tag-class> <body-content>scriptless</body-content> <attribute> <name>var</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>begin</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>end</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>step</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag> jsp: <sky:forskip var="item" begin="1" end="25" step="3"> ${item} </sky:forskip>
五】开发带有父标签的自定义标签
思路:当某些标签是排它执行,此时:
1) 可以在这些标签外,嵌入一个父标签,
2) 并在父标签中做一个标志位,判断是否执行过。
code: //1, <sky:choose> content </sky:choose> public class ChooseTag extends SimpleTagSupport { private boolean done = false; public boolean isDone() { return done; } public void setDone(boolean done) { this.done = done; } @Override public void doTag() throws JspException, IOException { JspFragment jspFragment = this.getJspBody(); jspFragment.invoke(null); } } //2,<sky:while test="${expr}"> content </sky:while> public class WhileTag extends SimpleTagSupport { private boolean test; public void setTest(boolean test) { this.test = test; } @Override public void doTag() throws JspException, IOException { //得到父标签后获取是否已经做过了 ChooseTag chooseTag = (ChooseTag) this.getParent(); boolean done = chooseTag.isDone(); //如果没有做过,并且表达式成立 if (!done && test) { this.getJspBody().invoke(null); chooseTag.setDone(true); } } } //3,<sky:otherwise>content</sky:otherwise> public class OtherWiseTag extends SimpleTagSupport { @Override public void doTag() throws JspException, IOException { //得到父类标签对象,获取到Done的状态,进行相应的处理 ChooseTag chooseTag = (ChooseTag) this.getParent(); boolean done = chooseTag.isDone(); if (!done) { this.getJspBody().invoke(null); chooseTag.setDone(true); } } } tld: <tag> <name>choose</name> <tag-class>com.suse.simpletag.ChooseTag</tag-class> <body-content>scriptless</body-content> </tag> <tag> <name>while</name> <tag-class>com.suse.simpletag.WhileTag</tag-class> <body-content>scriptless</body-content> <attribute> <name>test</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag> <tag> <name>otherwise</name> <tag-class>com.suse.simpletag.OtherWiseTag</tag-class> <body-content>scriptless</body-content> </tag> jsp: <% pageContext.setAttribute("age", 25); %> <sky:choose> <sky:while test="${age > 16}"> 你成年了<br /> </sky:while> <sky:otherwise> 你未成年 <br /> </sky:otherwise> </sky:choose>
六】防盗链
code: public class Reference extends SimpleTagSupport{ private String url; private String error; public void setUrl(String url) { this.url = url; } public void setError(String error) { this.error = error; } @Override public void doTag() throws JspException, IOException { //得到页面内置对象 PageContext pageContext = (PageContext) this.getJspContext(); HttpServletRequest request = (HttpServletRequest) pageContext.getRequest(); HttpServletResponse response = (HttpServletResponse) pageContext.getResponse(); //获取标签体中的内容并进行处理 JspFragment jspFragment = this.getJspBody(); String refer = request.getHeader("referer"); System.out.println(refer); if (this.url.equals(refer)) { //referer头表明来自于哪里 jspFragment.invoke(null); } else { try { request.getRequestDispatcher(this.error).forward(request, response); } catch (ServletException e) { e.printStackTrace(); } } } } tld: <tag> <name>refer</name> <tag-class>com.suse.simpletag.Reference</tag-class> <body-content>scriptless</body-content> <attribute> <name>url</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>error</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag> jsp: <sky:refer error="/ad.jsp" url="http://localhost:8080/day_16/index.jsp"> <a href="#">下载</a> </sky:refer>
七】仿写forEach自定义标签
code: //Foreach自定义标签 //Connection类族: <sky:forEach var="item" items="${list}">${item}</sky:forEach> <sky:forEach var="item" items="${set}">${item}</sky:forEach> //Map类族:<sky:forEach var="en" items="map">${en.key} ----- ${en.value}</sky:forEach> public class ForEachTag extends SimpleTagSupport { /*用一个Collection类来进行转换*/ private Collection coll; private Object items; private String var; public void setItems(Object items) { /*设置coll的值:当为Collection类族时直接转换,否则保存Set<Entry<K,V>>形式的Connection*/ if (items instanceof Collection) { coll = (Collection) items; } else if(items instanceof Map) { Map map = (Map) items; coll = map.entrySet(); } } public void setVar(String var) { this.var = var; } @Override public void doTag() throws JspException, IOException { for (Iterator it = coll.iterator(); it.hasNext(); ) { //得到单个对象 Object obj = (Object) it.next(); //将单个对象放入域对象中 PageContext pageContext = (PageContext) this.getJspContext(); pageContext.getRequest().setAttribute(this.var, obj); //将标签封装的表达式输出到页面中 this.getJspBody().invoke(null); } } tld: <tag> <name>forEach</name> <tag-class>com.suse.simpletag.ForEachTag</tag-class> <body-content>scriptless</body-content> <attribute> <name>var</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>items</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag> jsp: <% List<String> list = new ArrayList<String>(); list.add("jack"); list.add("merry"); list.add("berry"); list.add("sky"); pageContext.setAttribute("LIST", list); Map<String, String> map = new HashMap<String, String>(); map.put("id", "121010"); map.put("username", "jack"); map.put("age", "15"); pageContext.setAttribute("MAP", map); %> <sky:forEach items="${LIST}" var="item"> ${item} </sky:forEach> <br /> <sky:forEach items="${MAP}" var="en"> ${en.key}:${en.value} <br /> </sky:forEach>
八】过滤器(防止恶意JS代码)
code: //<simple:filter>标签处理类 public class FilterTag extends SimpleTagSupport { public void doTag() throws JspException, IOException { JspFragment jspFragment = this.getJspBody(); StringWriter writer = new StringWriter(); jspFragment.invoke(writer); String temp = writer.getBuffer().toString(); //结果必定是转义后的字符串 temp = filter(temp); PageContext pageContext = (PageContext) this.getJspContext(); pageContext.getOut().write(temp); } public String filter(String message) { if (message == null) return (null); char content[] = new char[message.length()]; message.getChars(0, message.length(), content, 0); StringBuffer result = new StringBuffer(content.length + 50); for (int i = 0; i < content.length; i++) { switch (content[i]) { case '<': result.append("<"); break; case '>': result.append(">"); break; case '&': result.append("&"); break; case '"': result.append("""); break; default: result.append(content[i]); } } return (result.toString()); } }