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资源
2tomcat服务器把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;
	}
    }    
}






SimpleTag接口方法调用顺序
序号方法名说明
1void setJspContext( JspContext pc )

设置JspContext对象(本质上是PageContext类型),(一定会被调用)

通过getJspCotext()方法得到pageContext对象

2void setParent( JspTag parent )设置父标签对象,传入父标签对象,如果没有父标签,则不调用此方法。通过getParent()方法得到父标签对象。
3void setXXX(值)设置属性值。
4void setJspBody( JspFragment jspBody )

设置标签体内容。标签体内容封装到JspFragment对象中,然后传入JspFragment对象。

通过getJspBody()方法得到标签体内容。如果没有标签体内容,则不会调用此方法。

5void 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代码,但不会执行。