为什么要使用自定义标签?
  JSTL标签库只提供了简单的输出等功能,没有实现任何的HTML代码封装,并且某些复杂类型转换,或者逻辑处理的时候,JSTL标签库完成不了,需要自定义标签!
   编写自定义标签的步骤:
     - 编写一个实现Tag接口的Java类【标签处理器类】
- 在WEB-INF目录下创建tld(Tag Library Descriptor)文件,在tld文件中对标签处理类(实现Tag接口的Java类)进行描述
快速入门
     	public class showIp implements Tag { 	 	    @Override 	    public void setPageContext(PageContext pageContext) { 	 	    } 	 	    @Override 	    public void setParent(Tag tag) { 	 	    } 	 	    @Override 	    public Tag getParent() { 	        return null; 	    } 	 	    @Override 	    public int doStartTag() throws JspException { 	        return 0; 	    } 	 	    @Override 	    public int doEndTag() throws JspException { 	        return 0; 	    } 	 	    @Override 	    public void release() { 	 	    } 	}  
     - 既然要获取到客户机的IP地址,那么request对象是必不可少的。现在问题来了,在Tag重写的方法好像不能直接获取到request对象啊。
- 经过我一番仔细的观察,发现了下面这个方法:
	    @Override 	    public void setPageContext(PageContext pageContext) { 	 	    }  
     - 既然能获取到pageContext对象,那么其他8大内置对象还不是随随便便?于是乎,我就定义一个成员变量pageContext,在setPageContext()方法中传递过来的pageContext赋值给我定义的成员变量即可!
 	    private PageContext pageContext = null;  	    @Override 	    public void setPageContext(PageContext pageContext) { 	        this.pageContext = pageContext; 	    }  
     - 好的,看回我们的需求:使用标签输出客户机的IP地址。在上面剩余5个方法中,最有可能就是在doStartTag()方法中编写代码!
      @Override     public int doStartTag() throws JspException {          //获取到request对象         HttpServletRequest httpServletRequest = (HttpServletRequest) pageContext.getRequest();          //获取到客户机的ip地址         String ip = httpServletRequest.getRemoteAddr();                  //获取输出到浏览器的对象         JspWriter jspWriter = pageContext.getOut();                  //下面的异常只能捕获,因为子类的异常不能比父类多         try {             jspWriter.write(ip);         } catch (IOException e) {             e.printStackTrace();         }          return 0;     }  
     - 接着,编写tld文件,描述实现Tag接口的Java类【标签处理类】。
 	<?xml version="1.0" encoding="ISO-8859-1"?> 	 	<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"> 	 	    <tlib-version>1.0</tlib-version> 	    <short-name>zhongfucheng</short-name> 	    <uri>/zhongfucheng</uri> 	 	    <!-- Invoke 'Generate' action to add tags or functions --> 	    <tag> 	        <name>viewIp</name> 	        <tag-class>tag.showIp</tag-class> 	        <body-content>empty</body-content> 	    </tag> 	 	 	</taglib>  
     
  
   
   标签处理类详细说明
  看完上面的程序,大部分人都是懵逼的。因为还不知道它具体是怎么用的,调用顺序是什么。
     	public interface Tag extends JspTag { 	    int SKIP_BODY = 0; 	    int EVAL_BODY_INCLUDE = 1; 	    int SKIP_PAGE = 5; 	    int EVAL_PAGE = 6; 	 	    void setPageContext(PageContext var1); 	 	    void setParent(Tag var1); 	 	    Tag getParent(); 	 	    int doStartTag() throws JspException; 	 	    int doEndTag() throws JspException; 	 	    void release(); 	}   
     - 上面程序的执行流程:         - JSP引擎遇到自定义标签,首先创建标签处理器类的实例对象。
- JSP引擎实例化完标签处理器类后,调用setPageContext()方法,将pageContext对象传递给标签处理器类,使得标签处理器类可以通过pageContext对象与JSP页面进行通信!
- setPageContext()方法执行完后,调用setParent()方法,将当前标签的父标签传递给当前处理器类,如果当前标签没有父标签,则传入null
- 当WEB容器执行到自定义标签的开始标记时,调用doStartTag()方法。
- 当WEB容器执行到自定义标签的结束标记时,调用doEndTag()方法。
- 一般来说,当WEB容器执行完自定义标签后,标签处理器类会驻留在内存中,直至停止WEB应用时,WEB容器才会调用release()方法
 
 
  
   
     -  我们现在已经清楚了方法的执行顺序了,可Tag接口的源码还有4个变量阿,它们是用来做什么的呢?我们在编写JSP页面时,经常需要在页面中引入一些逻辑,例如:      - 控制JSP页面某一部分(标签体)是否执行
- 控制整个JSP页面是否执行
- 控制JSP页面内容重复执行
- 修改JSP页面内容输出
 
-  再看回4个变量的名字,我们可以发现,这4个变量就是用来做逻辑判断的! 
-  我们来测试一下吧,在doEndTag()方法中,返回的是SKIP_PAGE变量,看下会怎么样! 
     @Override     public int doEndTag() throws JspException {         return SKIP_PAGE;     }   
    
   
     - 好像是没什么区别!我们再查看一下源代码,发现执行完标签后,后面的代码全都没有执行!
 
  
   
     - doStartTag()方法使用的是SKIP_BODY和EVAL_BODY_INCLUDE这两个变量,判断是否执行标签体的内容。
- doEndTag()方法使用的是SKIP_PAGE和EVAL_PAGE这两个变量,判断是否执行剩下页面的内容
- 控制JSP页面内容重复执行和修改JSP页面内容输出后面会有!
tld文件详细说明
   
  
   
   
  
   
         <tlib-version>1.0</tlib-version>     <short-name>myshortname</short-name>     <uri>http://mycompany.com</uri>          <tag>         <name></name>         <tag-class></tag-class>         <body-content></body-content>     </tag>  
     - 我们一个一个来看:         - shortname推荐使用prefix
- uri就是引入这个标签库使用的uri
- name为标签名
- tagclass为实现类
- bodycontent为标签体的限制,它有4个值: EMPTY【不允许有标签体】,JSP【允许有JSP代码】 ,scriptless【不允许有脚本代码(也就是<%%>),允许有EL表达式,文本,JSP行为】 , tagdepentend【标签体内的JSP代码不会被解析,直接输出文本】
 
TagSupport类
  大部分时候我们都不需要实现Tag接口来编写自定义标签,TagSupport是Tag的一个模板类,实现了pageContext,parent的getter、setter方法以及一些其他的功能。我们要做的就是重写doStartTag()和doEndTag()方法
     	public class Demo1 extends TagSupport { 	 	    @Override 	    public int doStartTag() throws JspException { 	 	        //获取到request对象 	        HttpServletRequest httpServletRequest = (HttpServletRequest) pageContext.getRequest(); 	 	        String method = httpServletRequest.getMethod(); 	 	        JspWriter jspWriter = pageContext.getOut(); 	 	        try { 	            jspWriter.write(method); 	        } catch (IOException e) { 	            e.printStackTrace(); 	        } 	 	        return 0; 	    } 	}  
          <tag>         <name>showMethod</name>         <tag-class>tag.Demo1</tag-class>         <body-content>empty</body-content>     </tag>  
    
   
   带属性的标签
  上面我们编写的自定义标签都没有附带属性的,我们在使用core标签库的时候,标签一般都带有属性。
  其实JSTL标签库的原理就是自定义标签,把自定义标签搞明白了,对JSTL标签库的使用就有更好的理解了!
     -  想要自定义标签带有属性也非常简单,只要在标签处理器类上加一个成员变量和setter、getter(),再在tld文件中描述下该属性即可!它的原理是这样的:当标签使用到属性的时候,引擎就会调用它的setter()方法 
-  下面我想要完成的功能是:使用标签的人,传入一个字符串格式就可以显示想要的格式日期 
-  编写标签处理器类,增加一个成员变量以及对应的setter、getter方法 
 	public class Demo1 extends TagSupport { 	 	 	    //创建成员对象,对应的setter、getter方法 	    private String format = null; 	 	 	    @Override 	    public int doStartTag() throws JspException { 	 	        //创建日期格式化对象 	        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format); 	 	        //格式化日期并向浏览器输出 	        try { 	            pageContext.getOut().write(simpleDateFormat.format(new Date())); 	        } catch (IOException e) { 	            e.printStackTrace(); 	        } 	 	        return 0; 	    } 	 	    public String getFormat() { 	        return format; 	    } 	 	    public void setFormat(String format) { 	        this.format = format; 	    } 	}  
     - 在tld文件中描述标签和属性,name代表的是属性的名字,required代表的是是否为必须,rtexprvalue代表能否使用EL表达式
     <tag>         <name>formatDate</name>         <tag-class>tag.Demo1</tag-class>         <body-content>empty</body-content>         <attribute>             <name>format</name>             <required>true</required>             <rtexprvalue>true</rtexprvalue>         </attribute>     </tag>  
     
  
   
   
  
   
   标签的继承关系
     - 在深入讲解之前,我们先来看一下各种Tag接口、类之间的关系,这样学习下去才不会晕!
 
  
   
   IterationTag说明
     - 我们已经使用过了Tag接口和TagSupport类了。接下来我们看一下IterationTag是什么玩意。
 	public interface IterationTag extends Tag { 	    int EVAL_BODY_AGAIN = 2; 	 	    int doAfterBody() throws JspException; 	}  
     - 从关系图我们也可以看出,IterationTag接口实现了Tag接口,InterationTag接口和Tag接口最主要的区别就是多了个doAfterBody()方法和EVAL_BODY_AGAIN变量
- 理解起来也很简单:当doAfterBody()返回的是EVAL_BODY_AGAIN变量,那么标签体的内容就一直循环!当然了,TagSupport也实现了Iteration接口,也就是说TagSupport类也能完成Iteration接口的事情!
- 我们来使用一下吧:
 	public class Demo1 extends TagSupport { 	 	    @Override 	    public int doStartTag() throws JspException { 	 	        try { 	            pageContext.getOut().write("hello"); 	        } catch (IOException e) { 	            e.printStackTrace(); 	        } 	 	        //执行标签体 	        return EVAL_BODY_INCLUDE; 	    } 	 	    @Override 	    public int doAfterBody() throws JspException { 	 	        //标签体不断循环,直到doAfterBody()返回的是SKIP_BODY 	        return EVAL_BODY_AGAIN; 	     	    } 	}  
     - tld文件中描述,既然标签体有内容,就不能用empty了!
     <tag>         <name>foreverEval</name>         <tag-class>tag.Demo1</tag-class>         <body-content>tagdependent</body-content>     </tag>  
     
  
   
     - doAfterBody()中只要返回的是SKPI_BODY就退出循环,执行doEndTag()方法
 	    //定义一个变量,规定标签体循环的次数 	    int x = 0; 	     	    @Override 	    public int doStartTag() throws JspException { 	 	        try { 	            pageContext.getOut().write("hello"); 	        } catch (IOException e) { 	            e.printStackTrace(); 	        } 	 	        //执行标签体 	        return EVAL_BODY_INCLUDE; 	    } 	 	    @Override 	    public int doAfterBody() throws JspException { 	         	        x++; 	        if (x >= 10) { 	            return SKIP_BODY; 	        } 	 	        //标签体不断循环,直到doAfterBody()返回的是SKIP_BODY 	        return EVAL_BODY_AGAIN; 	 	    }  
    
   
   BodyTag说明
  前面我们已经使用到了带标签体的自定义标签了,前面的都是只能直接输出而得不到标签体的内容,既然得不到标签体的内容,就更别说修改标签体了!
     - 此时,我们就需要BodyTag接口的支持了!它专门用来处理带标签体的标签,下面我们来看一下BodyTag的源码!
 	public interface BodyTag extends IterationTag { 	    /** @deprecated */ 	    int EVAL_BODY_TAG = 2;  	    int EVAL_BODY_BUFFERED = 2; 	 	    void setBodyContent(BodyContent var1); 	 	    void doInitBody() throws JspException; 	}  
     BodyTagSupport说明
      	public class BodyTagSupport extends TagSupport implements BodyTag { 	    protected BodyContent bodyContent; 	 	    public BodyTagSupport() { 	    } 	 	    public int doStartTag() throws JspException { 	        return 2; 	    } 	 	    public int doEndTag() throws JspException { 	        return super.doEndTag(); 	    } 	 	    public void setBodyContent(BodyContent b) { 	        this.bodyContent = b; 	    } 	 	    public void doInitBody() throws JspException { 	    } 	 	    public int doAfterBody() throws JspException { 	        return 0; 	    } 	 	    public void release() { 	        this.bodyContent = null; 	        super.release(); 	    } 	 	    public BodyContent getBodyContent() { 	        return this.bodyContent; 	    } 	 	    public JspWriter getPreviousOut() { 	        return this.bodyContent.getEnclosingWriter(); 	    } 	}  
     - 可以发现:BodyTagSupport主要扩充了以下的内容:         - 把BodyContent直接定义为成员变量,在获取标签体内容的时候就不需要通过getBodyContent()获取了
- 提供获取JspWriter的方法,不需要从pageConext中获取了
- 以上的两个扩充都简化了我们的代码书写!
 
  		protected BodyContent bodyContent;  	    public JspWriter getPreviousOut() { 	        return this.bodyContent.getEnclosingWriter(); 	    }   
     - 从BodyTag接口中,我就说到了:标签体的内容封装到了BodyContent类中,那么BodyContent类究竟是什么?我们来看一下源码:
 	public abstract class BodyContent extends JspWriter { 	    private JspWriter enclosingWriter; 	 	    protected BodyContent(JspWriter e) { 	        super(-2, false); 	        this.enclosingWriter = e; 	    } 	 	    public void flush() throws IOException { 	        throw new IOException("Illegal to flush within a custom tag"); 	    } 	 	    public void clearBody() { 	        try { 	            this.clear(); 	        } catch (IOException var2) { 	            throw new Error("internal error!;"); 	        } 	    } 	 	    public abstract Reader getReader(); 	 	    public abstract String getString(); 	 	    public abstract void writeOut(Writer var1) throws IOException; 	 	    public JspWriter getEnclosingWriter() { 	        return this.enclosingWriter; 	    } 	}   
     	//将数据转变成Reader对象     public abstract Reader getReader();  	//将数据转变成String对象     public abstract String getString();  
     	public class Demo1 extends BodyTagSupport { 	 	    @Override 	    public int doStartTag() throws JspException { 	 	 	        //想要获取到标签体的内容,就要返回EVAL_BODY_BUFFERED变量 	 	        return EVAL_BODY_BUFFERED; 	 	    } 	 	    @Override 	    public int doEndTag() throws JspException { 	 	        //获取到标签体的内容 	        String value = bodyContent.getString(); 	 	        //将标签体的内容转成小写并输出 	        try { 	            this.getPreviousOut().write(value.toLowerCase()); 	        } catch (IOException e) { 	            e.printStackTrace(); 	        } 	 	        return super.doEndTag(); 	    } 	 	}   
         <tag>         <name>BodyContentToLowerCase</name>         <tag-class>tag.Demo1</tag-class>         <body-content>tagdependent</body-content>     </tag>  
     
  
   
     如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章的同学,可以关注微信公众号:Java3y.