3、自定义标签
3.1、开发自定义标签的步骤
1)编写一个普通的java类,继承SimpleTagSupport类,叫标签处理器类
2)在web项目的WEB-INF目录下建立rk.tld文件,这个.tld文件叫标签库的声明文件。(参考核心标签库的tld文件)
3) 在jsp页面的头部导入自定义标签库:<%@taglib uri="http://www.lsieun.com/rk" prefix="rk"%>
4) 在jsp中使用自定义标签:<rk:showIp></rk:showIp>
(1)示例代码showIP.java:
package com.rk.tag; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.PageContext; import javax.servlet.jsp.tagext.SimpleTagSupport; /** * 标签处理器类 * @author lsieun * * 1)继承SimpleTagSupport */ public class ShowIpTag extends SimpleTagSupport { /** * 2)覆盖doTag方法:向浏览器输出客户的ip地址 */ @Override public void doTag() throws JspException, IOException { // PageContext pageContext = (PageContext) this.getJspContext();//得到JSP内置对象pageContext HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();//通过pageContext得到request对象 String ip = request.getRemoteHost();//通过request得到IP地址 JspWriter out = pageContext.getOut();//通过pageContext对象得到out对象 out.write("使用自定义标签输出客户的IP地址:" + ip); } }
(2)标签库声明文件示例rk.tld
<?xml version="1.0" encoding="UTF-8" ?> <taglib xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd" version="2.1"> <description>this is lsieun‘s library</description> <display-name>lsieun‘s library</display-name> <!-- 标签库的版本 --> <tlib-version>1.1</tlib-version> <!-- 标签库前缀 --> <short-name>rk</short-name> <!-- tld文件的唯一标记 --> <uri>http://www.lsieun.com/rk</uri> <!-- 一个标签的声明 --> <tag> <description> Show client‘s IP address. </description> <!-- 标签名称 --> <name>showIP</name> <!-- 标签处理器类的全名 --> <tag-class>com.rk.tag.ShowIpTag</tag-class> <!-- 输出标签体内容格式 --> <body-content>scriptless</body-content> </tag> </taglib>
(3)jsp引入自定义标签库
<%@taglib uri="http://www.lsieun.com/rk" prefix="rk" %>
(4)在jsp中使用自定义标签
<rk:showIP></rk:showIP>
完整的JSP文件:
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> <%@taglib uri="http://www.lsieun.com/rk" prefix="rk" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>显示IP地址</title> </head> <body> 一段内容<br/> <rk:showIP></rk:showIP><br/> 一段内容<br/> </body> </html>
3.2、自定义标签的执行过程
问题: http://localhost:8080/myweb/showIP.jsp 如何访问到自定义标签?
序号 | 执行过程 |
---|---|
前提 |
tomcat服务器启动时,加载到每个web应用,加载每个web应用的WEB-INF目录下的所有文件!!!例如。web.xml, tld文件!!! 也就是说,tomcat服务器已经加载rk.tld文件。 |
1 | 访问showIP.jsp资源 |
2 | tomcat服务器把jsp文件翻译成java源文件->编译class->构造类对象->调用_jspService()方法 |
3 | 检查jsp文件的taglib指令,是否存在一个名为http://www.lsieun.com/rk的tld文件。如果没有,则报错 |
4 | 上一步已经读到rk.tld文件 |
5 | 读到<rk:showIP>后,从rk.tld文件中查询是否存在<name>为showIP的<tag>标签 |
6 | 找到对应的<tag>标签,再读取<tag-class>内容 |
7 | 得到字符串 com.rk.tag.ShowIpTag |
8 | 构造ShowIpTag对象,然后调用doTag()方法 |
3.3、自定义标签处理器类的生命周期
刚才的ShowIpTag类继承自javax.servlet.jsp.tagext.SimpleTagSupport类,
而SimpleTagSupport类实现了javax.servlet.jsp.tagext.SimpleTag接口。
SimpleTag接口类的源码,如下:
package javax.servlet.jsp.tagext; import javax.servlet.jsp.JspContext; /** * Interface for defining Simple Tag Handlers. * * <p>A SimpleTag handler must have a public no-args constructor. Most * SimpleTag handlers should extend SimpleTagSupport.</p> * * <p><b>Lifecycle</b></p> * * <p>The following is a non-normative, brief overview of the * SimpleTag lifecycle. </p> * * <ol> * <li>A new tag handler instance is created each time by the container * by calling the provided zero-args constructor. Unlike classic * tag handlers, simple tag handlers are never cached and reused by * the JSP container.</li> * <li>The <code>setJspContext()</code> and <code>setParent()</code> * methods are called by the container. The <code>setParent()</code> * method is only called if the element is nested within another tag * invocation.</li> * <li>The setters for each attribute defined for this tag are called * by the container.</li> * <li>If a body exists, the <code>setJspBody()</code> method is called * by the container to set the body of this tag, as a * <code>JspFragment</code>. If the action element is empty in * the page, this method is not called at all.</li> * <li>The <code>doTag()</code> method is called by the container. All * tag logic, iteration, body evaluations, etc. occur in this * method.</li> * <li>The <code>doTag()</code> method returns and all variables are * synchronized.</li> * </ol> * * @see SimpleTagSupport * @since 2.0 */ public interface SimpleTag extends JspTag { /**(1)第1个调用setJspContext方法,主要是为了得到JspContext(本质上是pageContext对象) * Called by the container to provide this tag handler with * the <code>JspContext</code> for this invocation. * An implementation should save this value. * */ public void setJspContext( JspContext pc ); /**(2)第2个调用setParent方法,得到当前标签的父标签 * Sets the parent of this tag, for collaboration purposes. * <p> * The container invokes this method only if this tag invocation is * nested within another tag invocation. * */ public void setParent( JspTag parent ); /**这是一个与setParent方法相对应的方法 * Returns the parent of this tag, for collaboration purposes. * */ public JspTag getParent(); /** (3)第3个调用setJspBody方法,得到当前标签的子标签 * Provides the body of this tag as a JspFragment object, able to be * invoked zero or more times by the tag handler. * <p> * This method is invoked by the JSP page implementation * object prior to <code>doTag()</code>. If the action element is * empty in the page, this method is not called at all. * */ public void setJspBody( JspFragment jspBody ); /** (4)第4个调用doTag方法,由tag library developer覆写这个方法,执行标签输出 * Called by the container to invoke this tag. * The implementation of this method is provided by the tag library * developer, and handles all tag processing, body iteration, etc. * * <p> * The JSP container will resynchronize any AT_BEGIN and AT_END * variables (defined by the associated tag file, TagExtraInfo, or TLD) * after the invocation of doTag(). * */ public void doTag() throws javax.servlet.jsp.JspException, java.io.IOException; }
SimpleTagSupport类源码,如下:
package javax.servlet.jsp.tagext; import javax.servlet.jsp.JspContext; import javax.servlet.jsp.JspException; import java.io.IOException; /** * A base class for defining tag handlers implementing SimpleTag. * <p> * The SimpleTagSupport class is a utility class intended to be used * as the base class for new simple tag handlers. The SimpleTagSupport * class implements the SimpleTag interface and adds additional * convenience methods including getter methods for the properties in * SimpleTag. * * @since 2.0 */ public class SimpleTagSupport implements SimpleTag { /** Reference to the enclosing tag.父标签 */ private JspTag parentTag; /** The JSP context for the upcoming tag invocation.一个JspContext对象,实质上pageContext对象。 */ private JspContext jspContext; /** The body of the tag.标签的内容(标签体) */ private JspFragment jspBody; /**(1)第1个执行构造函数 * Sole constructor. (For invocation by subclass constructors, * typically implicit.) */ public SimpleTagSupport() { } /**(2)第2个执行setJspContext方法 * Stores the provided JSP context in the private jspContext field. * Subclasses can access the <code>JspContext</code> via * <code>getJspContext()</code>. * * @param pc the page context for this invocation * @see SimpleTag#setJspContext */ public void setJspContext( JspContext pc ) { this.jspContext = pc; } /** * Returns the page context passed in by the container via * setJspContext. * * @return the page context for this invocation */ protected JspContext getJspContext() { return this.jspContext; } /**(3)第3个执行setParent方法 * Sets the parent of this tag, for collaboration purposes. * <p> * The container invokes this method only if this tag invocation is * nested within another tag invocation. */ public void setParent( JspTag parent ) { this.parentTag = parent; } /** * Returns the parent of this tag, for collaboration purposes. */ public JspTag getParent() { return this.parentTag; } /** (4)第4个执行setJspBody方法 * Stores the provided JspFragment. * * @param jspBody The fragment encapsulating the body of this tag. * If the action element is empty in the page, this method is * not called at all. * @see SimpleTag#setJspBody */ public void setJspBody( JspFragment jspBody ) { this.jspBody = jspBody; } /** * Returns the body passed in by the container via setJspBody. * * @return the fragment encapsulating the body of this tag, or * null if the action element is empty in the page. */ protected JspFragment getJspBody() { return this.jspBody; } /** (5)第5个执行doTag方法 * Default processing of the tag does nothing. */ public void doTag() throws JspException, IOException { } /** * Find the instance of a given class type that is closest to a given * instance. * This method uses the getParent method from the Tag and/or SimpleTag * interfaces. This method is used for coordination among * cooperating tags. * * <p> For every instance of TagAdapter * encountered while traversing the ancestors, the tag handler returned by * <tt>TagAdapter.getAdaptee()</tt> - instead of the TagAdpater itself - * is compared to <tt>klass</tt>. If the tag handler matches, it - and * not its TagAdapter - is returned. * * <p> * The current version of the specification only provides one formal * way of indicating the observable type of a tag handler: its * tag handler implementation class, described in the tag-class * subelement of the tag element. This is extended in an * informal manner by allowing the tag library author to * indicate in the description subelement an observable type. * The type should be a subtype of the tag handler implementation * class or void. * This addititional constraint can be exploited by a * specialized container that knows about that specific tag library, * as in the case of the JSP standard tag library. * * <p> * When a tag library author provides information on the * observable type of a tag handler, client programmatic code * should adhere to that constraint. Specifically, the Class * passed to findAncestorWithClass should be a subtype of the * observable type. * * */ public static final JspTag findAncestorWithClass( JspTag from, Class<?> klass) { boolean isInterface = false; if (from == null || klass == null || (!JspTag.class.isAssignableFrom(klass) && !(isInterface = klass.isInterface()))) { return null; } for (;;) { JspTag parent = null; if( from instanceof SimpleTag ) { parent = ((SimpleTag)from).getParent(); } else if( from instanceof Tag ) { parent = ((Tag)from).getParent(); } if (parent == null) { return null; } if (parent instanceof TagAdapter) { parent = ((TagAdapter) parent).getAdaptee(); } if ((isInterface && klass.isInstance(parent)) || klass.isAssignableFrom(parent.getClass())) { return parent; } from = parent; } } }
序号 | 方法名 | 说明 |
---|---|---|
1 | void setJspContext( JspContext pc ) |
设置JspContext对象(本质上是PageContext类型),(一定会被调用) 通过getJspCotext()方法得到pageContext对象 |
2 | void setParent( JspTag parent ) | 设置父标签对象,传入父标签对象,如果没有父标签,则不调用此方法。通过getParent()方法得到父标签对象。 |
3 | void setXXX(值) | 设置属性值。 |
4 | void setJspBody( JspFragment jspBody ) |
设置标签体内容。标签体内容封装到JspFragment对象中,然后传入JspFragment对象。 通过getJspBody()方法得到标签体内容。如果没有标签体内容,则不会调用此方法。 |
5 | void doTag() | 执行标签时调用的方法。(一定会被调用) |
3.4、自定义标签的作用
1)控制标签体内容是否输出
DisplayContentTag文件:
package com.rk.tag; import java.io.IOException; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.JspFragment; import javax.servlet.jsp.tagext.SimpleTagSupport; /** * 标签处理器类 * @author lsieun * */ public class DisplayContentTag extends SimpleTagSupport { @Override public void doTag() throws JspException, IOException { /** * 1)控制标签内容是否输出 * 输出: 调用jspFrament.invoke(); * 不输出: 不调用jspFrament.invoke(); */ //1.1 得到标签体内容 JspFragment jspBody = this.getJspBody(); /** * 执行invoke方法: 把标签体内容输出到指定的Writer对象中 */ //1.2 往浏览器输出内容,writer为null就是默认往浏览器输出 //JspWriter out = this.getJspContext().getOut(); //jspBody.invoke(out); jspBody.invoke(null);//等价于上面的代码 } }
.tld文件配置:
<tag> <name>display</name> <tag-class>com.rk.tag.DisplayContentTag</tag-class> <body-content>scriptless</body-content> </tag>
JSP文件:
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> <%@taglib uri="http://www.lsieun.com/rk" prefix="rk" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>控制标签体内容是否输出</title> </head> <body> 标签前的内容==========================<br> <rk:display>这里是标签体的内容</rk:display><br/> 标签后的内容==========================<br> </body> </html>
2)控制标签余下内容是否输出
OmitFollowingContentTag.java文件:
package com.rk.tag; import java.io.IOException; import javax.servlet.jsp.JspException; import javax.servlet.jsp.SkipPageException; import javax.servlet.jsp.tagext.JspFragment; import javax.servlet.jsp.tagext.SimpleTagSupport; public class OmitFollowingContentTag extends SimpleTagSupport { @Override public void doTag() throws JspException, IOException { JspFragment jspBody = getJspBody(); jspBody.invoke(null);//将标签体的内容输出到浏览器 /** * 2)控制标签余下内容是否输出 * 输出: 什么都不干! * 不输出: 抛出SkipPageException异常 */ throw new SkipPageException(); } }
.tld文件配置:
<tag>
<name>omitAfter</name>
<tag-class>com.rk.tag.OmitFollowingContentTag</tag-class>
<body-content>scriptless</body-content>
</tag>
JSP文件:
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> <%@taglib uri="http://www.lsieun.com/rk" prefix="rk" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>控制标签余下内容是否输出</title> </head> <body> 标签前的内容==========================<br> <rk:omitAfter>这里是标签体的内容</rk:omitAfter> 标签后的内容========================== <br> </body> </html>
3)控制重复输出标签体内容
DisplayNContentTag.java文件:
package com.rk.tag; import java.io.IOException; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.JspFragment; import javax.servlet.jsp.tagext.SimpleTagSupport; public class DisplayNContentTag extends SimpleTagSupport { //1.声明属性的成员变量 private Integer num; //2.关键点: 必须提供公开的setter方法,用于给属性赋值 public void setNum(Integer num) { this.num = num; } @Override public void doTag() throws JspException, IOException { //1. 得到标签体内容 JspFragment jspBody = this.getJspBody(); /** * 2.控制重复输出标签体内容 * 方法: 执行多次jspBody.invoke(null)方法 */ for(int i=1;i<=num;i++){ jspBody.invoke(null); } } }
.tld文件配置:
<tag> <name>displayN</name> <tag-class>com.rk.tag.DisplayNContentTag</tag-class> <body-content>scriptless</body-content> <!-- 属性声明 --> <attribute> <!-- 属性名称 --> <name>num</name> <!-- 是否必填 --> <required>true</required> <!-- 是否支持EL表达式 --> <rtexprvalue>false</rtexprvalue> <!-- 接受的数据类型 --> <type>java.lang.Integer</type> </attribute> </tag>
JSP文件:
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> <%@taglib uri="http://www.lsieun.com/rk" prefix="rk" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>控制重复输出标签体内容</title> </head> <body> 标签前的内容==========================<br> <rk:displayN num="3">A</rk:displayN><br/> 标签后的内容========================== <br> </body> </html>
4)改变标签体内容
ChangeContentTag.java文件:
package com.rk.tag; import java.io.IOException; import java.io.StringWriter; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.JspFragment; import javax.servlet.jsp.tagext.SimpleTagSupport; public class ChangeContentTag extends SimpleTagSupport { @Override public void doTag() throws JspException, IOException { // 得到标签体内容 JspFragment jspBody = getJspBody(); /** * 改变标签体内容 */ //1. 创建StringWriter临时容器 StringWriter sw = new StringWriter(); //2. 把标签体拷贝到临时容器 jspBody.invoke(sw); //3. 从临时容器中得到标签体内容 String content = sw.toString(); //4. 改变内容 content = content.toLowerCase(); //System.out.println(content); //5. 把改变的内容输出到浏览器 //jspBody.invoke(null); 不能使用此方式输出,因为jsbBody没有改变过 this.getJspContext().getOut().write(content); } }
.tld文件配置:
<tag> <name>change</name> <tag-class>com.rk.tag.ChangeContentTag</tag-class> <body-content>scriptless</body-content> </tag>
JSP文件:
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> <%@taglib uri="http://www.lsieun.com/rk" prefix="rk" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>控制重复输出标签体内容</title> </head> <body> 标签前的内容==========================<br> <rk:change>ABCDEFG</rk:change><br/> 标签后的内容========================== <br> </body> </html>
5)带属性的标签
3.5、输出标签体内容格式
JSP: 在传统标签中使用的。可以写和执行jsp的java代码。
scriptless: 标签体不可以写jsp的java代码
empty: 必须是空标签。
tagdependent : 标签体内容可以写jsp的java代码,但不会执行。