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中使用自定义标签:

(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




    
  this is lsieun's library
  lsieun's library
  
  1.1
  
  rk
  
  http://www.lsieun.com/rk

  
  
    
        Show client's IP address.
    
    
    showIP
    
    com.rk.tag.ShowIpTag
    
    scriptless
  

(3)jsp引入自定义标签库

<%@taglib uri="http://www.lsieun.com/rk" prefix="rk" %>

(4)在jsp中使用自定义标签

完整的JSP文件:

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@taglib uri="http://www.lsieun.com/rk" prefix="rk" %>



  
    显示IP地址
  
  
  
    一段内容
    
    一段内容
  



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.tld文件中查询是否存在为showIP的标签
6 找到对应的标签,再读取内容
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.
 *
 * 

A SimpleTag handler must have a public no-args constructor.  Most  * SimpleTag handlers should extend SimpleTagSupport.

 *   * 

Lifecycle

 *  * 

The following is a non-normative, brief overview of the   * SimpleTag lifecycle.  

 *  * 
     *   
  1. 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.
  2.  *   
  3. The setJspContext() and setParent()   *       methods are called by the container.  The setParent()  *       method is only called if the element is nested within another tag   *       invocation.
  4.  *   
  5. The setters for each attribute defined for this tag are called  *       by the container.
  6.  *   
  7. If a body exists, the setJspBody() method is called   *       by the container to set the body of this tag, as a   *       JspFragment.  If the action element is empty in  *       the page, this method is not called at all.
  8.  *   
  9. The doTag() method is called by the container.  All  *       tag logic, iteration, body evaluations, etc. occur in this   *       method.
  10.  *   
  11. The doTag() method returns and all variables are  *       synchronized.
  12.  * 
 *   * @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 JspContext 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.      * 

     * 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.       * 

     * This method is invoked by the JSP page implementation       * object prior to doTag().  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.      *      * 

     * 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.
 * 

 * 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 JspContext via       * getJspContext().      *       * @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.      * 

     * 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.      *      * 

 For every instance of TagAdapter      * encountered while traversing the ancestors, the tag handler returned by      * TagAdapter.getAdaptee() - instead of the TagAdpater itself -      * is compared to klass. If the tag handler matches, it - and      * not its TagAdapter - is returned.      *      * 

     * 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.      *      * 

     * 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接口方法调用顺序
序号 方法名 说明
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文件配置:

	
		display
		com.rk.tag.DisplayContentTag
		scriptless
	

JSP文件:

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@taglib uri="http://www.lsieun.com/rk" prefix="rk" %>



  
    控制标签体内容是否输出
  
  
  
    标签前的内容==========================
    这里是标签体的内容
    标签后的内容==========================
  


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文件配置:

omitAfter

com.rk.tag.OmitFollowingContentTag

scriptless

JSP文件:

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@taglib uri="http://www.lsieun.com/rk" prefix="rk" %>



  
    控制标签余下内容是否输出
  
  
  
    标签前的内容==========================
    这里是标签体的内容     标签后的内容========================== 
  




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文件配置:

	
		displayN
		com.rk.tag.DisplayNContentTag
		scriptless
		
		
			
			num
			
			true
			
			false
			
			java.lang.Integer
		
	


JSP文件:

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@taglib uri="http://www.lsieun.com/rk" prefix="rk" %>



  
    控制重复输出标签体内容
  
  
  
    标签前的内容==========================
    A
    标签后的内容========================== 
  



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文件配置:

	
		change
		com.rk.tag.ChangeContentTag
		scriptless
	


JSP文件:

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@taglib uri="http://www.lsieun.com/rk" prefix="rk" %>



  
    控制重复输出标签体内容
  
  
  
    标签前的内容==========================
    ABCDEFG
    标签后的内容========================== 
  


5)带属性的标签



3.5、输出标签体内容格式

JSP:   在传统标签中使用的。可以写和执行jsp的java代码。

scriptless:  标签体不可以写jsp的java代码

empty:    必须是空标签。

tagdependent : 标签体内容可以写jsp的java代码,但不会执行。