JavaWeb开发与代码的编写(十)

JavaWeb开发与代码的编写(十)

自定义标签

 自定义标签主要用于移除Jsp页面中的java代码。

编写一个实现Tag接口的Java类(标签处理器类)

 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.Tag;
 
 public class ViewIPTag implements Tag {
 
     //接收传递进来的PageContext对象
     private PageContext pageContext;
     
     @Override
     public int doEndTag() throws JspException {
         System.out.println("调用doEndTag()方法");
         return 0;
     } 
     @Override
     public int doStartTag() throws JspException {
        System.out.println("调用doStartTag()方法");
         HttpServletRequest request =(HttpServletRequest) pageContext.getRequest();
         JspWriter out = pageContext.getOut();
         String ip = request.getRemoteAddr();
         try {
             //这里输出的时候会抛出IOException异常
             out.write(ip);
         } catch (IOException e) {
             //捕获IOException异常后继续抛出
             throw new RuntimeException(e);
         }
         return 0;
     }
 
     @Override
     public Tag getParent() {
         return null;
     }
 
     @Override
     public void release() {
         System.out.println("调用release()方法");
     }
 
    @Override
     public void setPageContext(PageContext pageContext) {
        System.out.println("setPageContext(PageContext pageContext)");
        this.pageContext = pageContext;
    }
 
     @Override
    public void setParent(Tag arg0) {
 
     }
 
 }

2、在WEB-INF/目录下新建tld文件,在tld文件中对标签处理器类进行描述

  

  gacl.tld文件的代码如下:

 <?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用来添加对taglib(标签库)的描述 -->
     <description>孤傲苍狼开发的自定义标签库</description>
     <!--taglib(标签库)的版本号 -->
     <tlib-version>1.0</tlib-version>
     <short-name>GaclTagLibrary</short-name>
     <!-- 
         为自定义标签库设置一个uri,uri以/开头,/后面的内容随便写,如这里的/gacl ,
         在Jsp页面中引用标签库时,需要通过uri找到标签库
         在Jsp页面中就要这样引入标签库:<%@taglib uri="/gacl" prefix="gacl"%>
     -->
     <uri>/gacl</uri>
     
     <!--一个taglib(标签库)中包含多个自定义标签,每一个自定义标签使用一个tag标记来描述  -->
     <!-- 一个tag标记对应一个自定义标签 -->
      <tag>
         <description>这个标签的作用是用来输出客户端的IP地址</description>
         <!-- 
             为标签处理器类配一个标签名,在Jsp页面中使用标签时是通过标签名来找到要调用的标签处理器类的
             通过viewIP就能找到对应的me.gacl.web.tag.ViewIPTag类
          -->
         <name>viewIP</name>
         <!-- 标签对应的处理器类-->
         <tag-class>me.gacl.web.tag.ViewIPTag</tag-class>
        <body-content>empty</body-content>
     </tag>
    
 </taglib>

 

在Jsp页面中使用自定义标签

  1、使用"<%@taglib uri="标签库的uri"  prefix="标签的使用前缀"%>"指令引入要使用的标签库。

例如:在jspTag_Test1.jsp中引用gacl标签库

 <%@ page language="java" pageEncoding="UTF-8"%>
 <!-- 使用taglib指令引用gacl标签库,标签库的前缀(prefix)可以随便设置,如这里设置成 prefix="xdp" -->
 <%@taglib uri="/gacl"  prefix="xdp"%>
 <!DOCTYPE HTML>
 <html>
   <head>
     <title>输出客户端的IP</title>
   </head>
   
   <body>
     你的IP地址是(使用java代码获取输出):
     <%
         //在jsp页面中使用java代码获取客户端IP地址
         String ip = request.getRemoteAddr();
         out.write(ip);
     %>
     <hr/>
      你的IP地址是(使用自定义标签获取输出):
      <%--使用自定义标签viewIP --%>
      <xdp:viewIP/>
   </body>
 </html>

  标签的运行效果如下:

  从运行效果种可以看到,使用自定义标签就可以将jsp页面上的java代码移除掉,如需要在jsp页面上输出客户端的IP地址时,使用 <xdp:viewIP/>标签就可以代替jsp页面上的这些代码:

 <%
         //在jsp页面中使用java代码获取客户端IP地址
         String ip = request.getRemoteAddr();
         out.write(ip);
 %>

这就是开发和使用自定义标签的好处,可以让我们的Jsp页面上不嵌套java代码。

 

自定义标签的执行流程

  JSP引擎遇到自定义标签时,首先创建标签处理器类的实例对象,然后按照JSP规范定义的通信规则依次调用它的方法。
    1、public void setPageContext(PageContext pc), JSP引擎实例化标签处理器后,将调用setPageContext方法将JSP页面的pageContext对象传递给标签处理器,标签处理器以后可以通过这个pageContext对象与JSP页面进行通信。
    2、public void setParent(Tag t),setPageContext方法执行完后,WEB容器接着调用的setParent方法将当前标签的父标签传递给当前标签处理器,如果当前标签没有父标签,则传递给setParent方法的参数值为null。
    3、public int doStartTag(),调用了setPageContext方法和setParent方法之后,WEB容器执行到自定义标签的开始标记时,就会调用标签处理器的doStartTag方法。
    4、public int doEndTag(),WEB容器执行完自定义标签的标签体后,就会接着去执行自定义标签的结束标记,此时,WEB容器会去调用标签处理器的doEndTag方法。
    5、public void release(),通常WEB容器执行完自定义标签后,标签处理器会驻留在内存中,为其它请求服务器,直至停止web应用时,web容器才会调用release方法。

   我们在tomcat服务器的"work\Catalina\localhost\JavaWeb_JspTag_study_20140816\org\apache\jsp"目录下可以找到将jspTag_Test1.jsp翻译成Servlet后的java源代码,如下图所示:

  打开jspTag_005fTest1_jsp.java文件,可以看到setPageContext(PageContext pc)、setParent(Tag t)、doStartTag()、doEndTag()、release()这5个方法的调用顺序和过程。

  jspTag_005fTest1_jsp.java的代码如下:

 3 import javax.servlet.*;
  4 import javax.servlet.http.*;
  5 import javax.servlet.jsp.*;
  6 
  7 public final class jspTag_005fTest1_jsp extends org.apache.jasper.runtime.HttpJspBase
  8     implements org.apache.jasper.runtime.JspSourceDependent {
  9 
 10   private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();
 11 
 12   private static java.util.List _jspx_dependants;
 13 
 14   static {
 15     _jspx_dependants = new java.util.ArrayList(1);
 16     _jspx_dependants.add("/WEB-INF/gacl.tld");
 17   }
 18 
 19   private org.apache.jasper.runtime.TagHandlerPool _005fjspx_005ftagPool_005fxdp_005fviewIP_005fnobody;
 20 
 21   private javax.el.ExpressionFactory _el_expressionfactory;
 22   private org.apache.AnnotationProcessor _jsp_annotationprocessor;
 23 
 24   public Object getDependants() {
 25     return _jspx_dependants;
 26   }
 27 
 28   public void _jspInit() {
 29     _005fjspx_005ftagPool_005fxdp_005fviewIP_005fnobody = org.apache.jasper.runtime.TagHandlerPool.getTagHandlerPool(getServletConfig());
 30     _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
 31     _jsp_annotationprocessor = (org.apache.AnnotationProcessor) getServletConfig().getServletContext().getAttribute(org.apache.AnnotationProcessor.class.getName());
 32   }
 33 
 34   public void _jspDestroy() {
 35     _005fjspx_005ftagPool_005fxdp_005fviewIP_005fnobody.release();
 36   }
 37 
 38   public void _jspService(HttpServletRequest request, HttpServletResponse response)
 39         throws java.io.IOException, ServletException {
 40 
 41     PageContext pageContext = null;
 42     HttpSession session = null;
 43     ServletContext application = null;
 44     ServletConfig config = null;
 45     JspWriter out = null;
 46     Object page = this;
 47     JspWriter _jspx_out = null;
 48     PageContext _jspx_page_context = null;
 49 
 50 
 51     try {
 52       response.setContentType("text/html;charset=UTF-8");
 53       pageContext = _jspxFactory.getPageContext(this, request, response,
 54                   null, true, 8192, true);
 55       _jspx_page_context = pageContext;
 56       application = pageContext.getServletContext();
 57       config = pageContext.getServletConfig();
 58       session = pageContext.getSession();
 59       out = pageContext.getOut();
 60       _jspx_out = out;
 61 
 62       out.write("\r\n");
 63       out.write("<!-- 引用gacl标签库,标签库的前缀(prefix)可以随便设置,如这里设置成 prefix=\"gacl\" -->\r\n");
 64       out.write("\r\n");
 65       out.write("<!DOCTYPE HTML>\r\n");
 66       out.write("<html>\r\n");
 67       out.write("  <head>\r\n");
 68       out.write("    <title>输出客户端的IP</title>\r\n");
 69       out.write("  </head>\r\n");
 70       out.write("  \r\n");
 71       out.write("  <body>\r\n");
 72       out.write("    你的IP地址是(使用java代码获取输出):\r\n");
 73       out.write("    ");
 74 
 75         //在jsp页面中使用java代码获取客户端IP地址
 76         String ip = request.getRemoteAddr();
 77         out.write(ip);
 78     
 79       out.write("\r\n");
 80       out.write("    <hr/>\r\n");
 81       out.write("     你的IP地址是(使用自定义标签获取输出):");
 82       if (_jspx_meth_xdp_005fviewIP_005f0(_jspx_page_context))
 83         return;
 84       out.write("\r\n");
 85       out.write("  </body>\r\n");
 86       out.write("</html>\r\n");
 87     } catch (Throwable t) {
 88       if (!(t instanceof SkipPageException)){
 89         out = _jspx_out;
 90         if (out != null && out.getBufferSize() != 0)
 91           try { out.clearBuffer(); } catch (java.io.IOException e) {}
 92         if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
 93       }
 94     } finally {
 95       _jspxFactory.releasePageContext(_jspx_page_context);
 96     }
 97   }
 98 
 99   private boolean _jspx_meth_xdp_005fviewIP_005f0(PageContext _jspx_page_context)
100           throws Throwable {
101     PageContext pageContext = _jspx_page_context;
102     JspWriter out = _jspx_page_context.getOut();
103     //  xdp:viewIP
104     me.gacl.web.tag.ViewIPTag _jspx_th_xdp_005fviewIP_005f0 = (me.gacl.web.tag.ViewIPTag) _005fjspx_005ftagPool_005fxdp_005fviewIP_005fnobody.get(me.gacl.web.tag.ViewIPTag.class);
105     _jspx_th_xdp_005fviewIP_005f0.setPageContext(_jspx_page_context);
106     _jspx_th_xdp_005fviewIP_005f0.setParent(null);
107     int _jspx_eval_xdp_005fviewIP_005f0 = _jspx_th_xdp_005fviewIP_005f0.doStartTag();
108     if (_jspx_th_xdp_005fviewIP_005f0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {
109       _005fjspx_005ftagPool_005fxdp_005fviewIP_005fnobody.reuse(_jspx_th_xdp_005fviewIP_005f0);
110       return true;
111     }
112     _005fjspx_005ftagPool_005fxdp_005fviewIP_005fnobody.reuse(_jspx_th_xdp_005fviewIP_005f0);
113     return false;
114   }
115 }

  下面重点分析一下上述代码中标红色的那个 private boolean _jspx_meth_xdp_005fviewIP_005f0(PageContext _jspx_page_context)方法中的代码

  ①、这里是实例化一个viewIP标签处理器类me.gacl.web.tag.ViewIPTag的对象

  //  xdp:viewIP
     me.gacl.web.tag.ViewIPTag _jspx_th_xdp_005fviewIP_005f0 = (me.gacl.web.tag.ViewIPTag) _005fjspx_005ftagPool_005fxdp_005fviewIP_005fnobody.get(me.gacl.web.tag.ViewIPTag.class);

 ②、实例化标签处理器后,调用setPageContext方法将JSP页面的pageContext对象传递给标签处理器

_jspx_th_xdp_005fviewIP_005f0.setPageContext(_jspx_page_context);

 ③、setPageContext方法执行完后,接着调用的setParent方法将当前标签的父标签传递给当前标签处理器,如果当前标签没有父标签,则传递给setParent方法的参数值为null

_jspx_th_xdp_005fviewIP_005f0.setParent(null);

  ④、调用了setPageContext方法和setParent方法之后,WEB容器执行到自定义标签的开始标记时,就会调用标签处理器的doStartTag方法

int _jspx_eval_xdp_005fviewIP_005f0 = _jspx_th_xdp_005fviewIP_005f0.doStartTag();

      ⑤、WEB容器执行完自定义标签的标签体后,就会接着去执行自定义标签的结束标记,此时,WEB容器会去调用标签处理器的doEndTag方法

if (_jspx_th_xdp_005fviewIP_005f0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE)

 这就是自定义标签的执行流程。

 

传统标签

标签技术的API类继承关系

在现在的jsp标签开发中,很少直接使用传统标签来开发了,目前用得较多的都是简单标签,所以Jsp的传统标签开发了解一下即可。

标签API介绍

JspTag接口

  JspTag接口是所有自定义标签的父接口,它是JSP2.0中新定义的一个标记接口,没有任何属性和方法。JspTag接口有Tag和SimpleTag两个直接子接口,JSP2.0以前的版本中只有Tag接口,所以把实现Tag接口的自定义标签也叫做传统标签,把实现SimpleTag接口的自定义标签叫做简单标签。

Tag接口

  Tag接口是所有传统标签的父接口,其中定义了两个重要方法(doStartTag、doEndTag)方法和四个常量(EVAL_BODY_INCLUDE、SKIP_BODY、EVAL_PAGE、SKIP_PAGE),这两个方法和四个常量的作用如下:

  (1)WEB容器在解释执行JSP页面的过程中,遇到自定义标签的开始标记就会去调用标签处理器的doStartTag方法,doStartTag方法执行完后可以向WEB容器返回常量EVAL_BODY_INCLUDE或SKIP_BODY。如果doStartTag方法返回EVAL_BODY_INCLUDE,WEB容器就会接着执行自定义标签的标签体;如果doStartTag方法返回SKIP_BODY,WEB容器就会忽略自定义标签的标签体,直接解释执行自定义标签的结束标记。

  (2)WEB容器解释执行到自定义标签的结束标记时,就会调用标签处理器的doEndTag方法,doEndTag方法执行完后可以向WEB容器返回常量EVAL_PAGE或SKIP_PAGE。如果doEndTag方法返回常量EVAL_PAGE,WEB容器就会接着执行JSP页面中位于结束标记后面的JSP代码;如果doEndTag方法返回SKIP_PAGE,WEB容器就会忽略JSP页面中位于结束标记后面的所有内容。

  从doStartTag和doEndTag方法的作用和返回值的作用可以看出,开发自定义标签时可以在doStartTag方法和doEndTag方法体内编写合适的Java程序代码来实现具体的功能,通过控制doStartTag方法和doEndTag方法的返回值,还可以告诉WEB容器是否执行自定义标签中的标签体内容和JSP页面中位于自定义标签的结束标记后面的内容。

 

IterationTag接口

  IterationTag接口继承了Tag接口,并在Tag接口的基础上增加了一个doAfterBody方法和一个EVAL_BODY_AGAIN常量。实现IterationTag接口的标签除了可以完成Tag接口所能完成的功能外,还能够通知WEB容器是否重复执行标签体内容。对于实现了IterationTag接口的自定义标签,WEB容器在执行完自定义标签的标签体后,将调用标签处理器的doAfterBody方法,doAfterBody方法可以向WEB容器返回常量EVAL_BODY_AGAIN或SKIP_BODY。如果doAfterBody方法返回EVAL_BODY_AGAIN,WEB容器就会把标签体内容再重复执行一次,执行完后接着再调用doAfterBody方法,如此往复,直到doAfterBody方法返回常量SKIP_BODY,WEB容器才会开始处理标签的结束标记和调用doEndTag方法。

  可见,开发自定义标签时,可以通过控制doAfterBody方法的返回值来告诉WEB容器是否重复执行标签体内容,从而达到循环处理标签体内容的效果。例如,可以通过一个实现IterationTag接口的标签来迭代输出一个集合中的所有元素,在标签体部分指定元素的输出格式。

  在JSP API中也提供了IterationTag接口的默认实现类TagSupport,我们在编写自定义标签的标签处理器类时,可以继承和扩展TagSupport类,这相比实现IterationTag接口将简化开发工作。

 

BodyTag接口

BodyTag接口继承了IterationTag接口,并在IterationTag接口的基础上增加了两个方法(setBodyContent、doInitBody)和一个EVAL_BODY_BUFFERED常量。实现BodyTag接口的标签除了可以完成IterationTag接口所能完成的功能,还可以对标签体内容进行修改。对于实现了BodyTag接口的自定义标签,标签处理器的doStartTag方法不仅可以返回前面讲解的常量EVAL_BODY_INCLUDE或SKIP_BODY,还可以返回常量EVAL_BODY_BUFFERED。如果doStartTag方法返回EVAL_BODY_BUFFERED,WEB容器就会创建一个专用于捕获标签体运行结果的BodyContent对象,然后调用标签处理器的setBodyContent方法将BodyContent对象的引用传递给标签处理器,WEB容器接着将标签体的执行结果写入到BodyContent对象中。在标签处理器的后续事件方法中,可以通过先前保存的BodyContent对象的引用来获取标签体的执行结果,然后调用BodyContent对象特有的方法对BodyContent对象中的内容(即标签体的执行结果)进行修改和控制其输出。

  在JSP API中也提供了BodyTag接口的实现类BodyTagSupport,我们在编写能够修改标签体内容的自定义标签的标签处理器类时,可以继承和扩展BodyTagSupport类,这相比实现BodyTag接口将简化开发工作。

 

SimpleTag接口

SimpleTag接口是JSP2.0中新增的一个标签接口。由于传统标签使用三个标签接口来完成不同的功能,显得过于繁琐,不利于标签技术的推广,因此,SUN公司为降低标签技术的学习难度,在JSP 2.0中定义了一个更为简单、便于编写和调用的SimpleTag接口。SimpleTag接口与传统标签接口最大的区别在于,SimpleTag接口只定义了一个用于处理标签逻辑的doTag方法,该方法在WEB容器执行自定义标签时调用,并且只被调用一次。那些使用传统标签接口所完成的功能,例如是否执行标签体、迭代标签体、对标签体内容进行修改等功能都可以在doTag方法中完成。

  在JSP API中也提供了SimpleTag接口的实现类SimpleTagSupport,我们在编写简单标签时,可以继承和扩展SimpleTagSupport类,这相比实现SimpleTag接口将简化开发工作。

 

传统标签接口中的各个方法可以返回的返回值说明

  下图列举了Tag接口、IterationTag接口和BodyTag接口中的主要方法及它们分别可以返回的返回值的说明。

 

开发传统标签实现页面逻辑

  开发人员在编写Jsp页面时,经常还需要在页面中引入一些逻辑,例如:

  • 控制jsp页面某一部分内容是否执行。
  • 控制整个jsp页面是否执行。
  • 控制jsp页面内容重复执行。
  • 修改jsp页面内容输出。

  自定义标签除了可以移除jsp页面java代码外,它也可以实现以上功能。

控制jsp页面某一部分内容是否执行  

  编写一个类实现tag接口,控制doStartTag()方法的返回值,如果这个方法返回EVAL_BODY_INCLUDE,则执行标签体,如果返回SKIP_BODY,则不执行标签体。

  SUN公司针对tag接口提供了一个默认的实现类TagSupport,TagSupport类中实现了tag接口的所有方法,因此我们可以编写一个类继承TagSupport类,然后再重写doStartTag方法。

示例代码如下:

TagDemo1.java

 import javax.servlet.jsp.JspException;
  import javax.servlet.jsp.tagext.Tag;
  import javax.servlet.jsp.tagext.TagSupport;
  
  /**
   * @author gacl
   * TagSupport类实现了Tag接口,TagDemo1继承TagSupport类
  * 
  */
 public class TagDemo1 extends TagSupport {
 
     /* 重写doStartTag方法,控制标签体是否执行
      * @see javax.servlet.jsp.tagext.TagSupport#doStartTag()
      */
     @Override
     public int doStartTag() throws JspException {
         //如果这个方法返回EVAL_BODY_INCLUDE,则执行标签体,如果返回SKIP_BODY,则不执行标签体
         //return Tag.EVAL_BODY_INCLUDE;
         return Tag.SKIP_BODY;
     }
 }

  在WEB-INF目录下的tld文件中添加对该标签处理类的描述,如下:

 <tag>
         <name>demo1</name>
         <tag-class>me.gacl.web.tag.TagDemo1</tag-class>
         <!--demo1标签有标签体,所以这里的body-content设置为JSP-->
         <body-content>JSP</body-content>
 </tag>

 在jsp页面中导入并使用自定义标签,如下:

 <%@ page language="java" pageEncoding="UTF-8"%>
 <%--在jsp页面中导入自定义标签库 --%>
 <%@taglib uri="/gacl" prefix="gacl" %>
 <!DOCTYPE HTML>
 <html>
   <head>
     <title>控制标签体是否执行</title>
   </head>
   
   <body>
   <%--在jsp页面中使用自定义标签 demo1标签是带有标签体的,标签体的内容是"孤傲苍狼"这几个字符串--%>
     <gacl:demo1>
         孤傲苍狼
     </gacl:demo1>
   </body>
 </html>

 运行效果如下:

 

 

控制整个jsp页面是否执行

  编写一个类实现tag接口,控制doEndTag()方法的返回值,如果这个方法返回EVAL_PAGE,则执行标签余下的jsp页面,如果返回SKIP_PAGE,则不执行余下的jsp。

示例代码如下:

TagDemo2.java

  import javax.servlet.jsp.JspException;
  import javax.servlet.jsp.tagext.Tag;
  import javax.servlet.jsp.tagext.TagSupport;
  
  /**
   * @author gacl
   * TagSupport类实现了Tag接口,TagDemo2继承TagSupport类
  */
 public class TagDemo2 extends TagSupport{
 
     /* 重写doEndTag方法,控制jsp页面是否执行
     * @see javax.servlet.jsp.tagext.TagSupport#doEndTag()
      */
     @Override
     public int doEndTag() throws JspException {
         //如果这个方法返回EVAL_PAGE,则执行标签余下的jsp页面,如果返回SKIP_PAGE,则不执行余下的jsp
         return Tag.SKIP_PAGE;
         //return Tag.EVAL_PAGE;
     }

    
 }

  在WEB-INF目录下的tld文件中添加对该标签处理类的描述,如下:

 <tag>
         <name>demo2</name>
         <tag-class>me.gacl.web.tag.TagDemo2</tag-class>
         <!--demo2标签没有标签体,所以这里的body-content设置为empty-->
         <body-content>empty</body-content>
</tag>

 在jsp页面中导入并使用自定义标签,如下:

 <%@ page language="java" pageEncoding="UTF-8"%>
 <%--在jsp页面中导入自定义标签库 --%>
 <%@taglib uri="/gacl" prefix="gacl" %>
 <!DOCTYPE HTML>
 <html>
   <head>
     <title>控制jsp页面是否执行</title>
   </head>
   
   <body>
          <h1>jsp页面的内容1</h1>
          <%--在jsp页面中使用自定义标签 demo2标签是不带标签体的--%>
          <gacl:demo2/>
          <h1>jsp页面的内容2</h1>
   </body>
 </html>

  运行效果如下:

 

 

控制jsp页面内容重复执行

  编写一个类实现Iterationtag接口,控制doAfterBody()方法的返回值,如果这个方法返回EVAL_BODY_AGAIN, 则web服务器又执行一次标签体,依次类推,一直执行到doAfterBody方法返回SKIP_BODY,则标签体才不会重复执行。

示例代码如下:

TagDemo3.java

 import javax.servlet.jsp.JspException;
 import javax.servlet.jsp.tagext.IterationTag;
 import javax.servlet.jsp.tagext.Tag;
 import javax.servlet.jsp.tagext.TagSupport;
 
 public class TagDemo3 extends TagSupport {
 
     int x = 5;
     @Override
     public int doStartTag() throws JspException {
         return Tag.EVAL_BODY_INCLUDE;
     }
     
     /* 控制doAfterBody()方法的返回值,
      * 如果这个方法返回EVAL_BODY_AGAIN, 则web服务器又执行一次标签体,
      * 依次类推,一直执行到doAfterBody方法返回SKIP_BODY,则标签体才不会重复执行。
      * @see javax.servlet.jsp.tagext.TagSupport#doAfterBody()
      */
     @Override
     public int doAfterBody() throws JspException {
         x--;
         if(x>0){
             return IterationTag.EVAL_BODY_AGAIN;
         }else{
             return IterationTag.SKIP_BODY;
        }
     } 
 }

  在WEB-INF目录下的tld文件中添加对该标签处理类的描述,如下:

 <tag>
         <name>demo3</name>
         <tag-class>me.gacl.web.tag.TagDemo3</tag-class>
         <!--demo3标签有标签体,所以这里的body-content设置为JSP-->
         <body-content>JSP</body-content>
 </tag>

  在jsp页面中导入并使用自定义标签,如下:

 <%@ page language="java" pageEncoding="UTF-8"%>
 <%--在jsp页面中导入自定义标签库 --%>
 <%@taglib uri="/gacl" prefix="gacl" %>
 <!DOCTYPE HTML>
 <html>
   <head>
     <title>控制页面内容重复执行5次</title>
   </head>   
   <body>
   <%--在jsp页面中使用自定义标签 demo3标签--%>
       <gacl:demo3>
           <h3>jsp页面的内容</h3>
       </gacl:demo3>
   </body>
 </html>

运行效果如下:

  

 

修改jsp页面内容输出

  编写一个类实现BodyTag接口,控制doStartTag()方法返回EVAL_BODY_BUFFERED,则web服务器会创建BodyContent对象捕获标签体,然后在doEndTag()方法体内,得到代表标签体的bodyContent对象,从而就可以对标签体进行修改操作。

  SUN公司针对BodyTag接口提供了一个默认的实现类BodyTagSupport,BodyTagSupport类中实现了BodyTag接口的所有方法,因此我们可以编写一个类继承BodyTagSupport类,然后再根据需要重写doStartTag方法和doEndTag()方法。

示例代码如下:

TagDemo4.java

 import java.io.IOException;
  
  import javax.servlet.jsp.JspException;
  import javax.servlet.jsp.tagext.BodyContent;
  import javax.servlet.jsp.tagext.BodyTag;
  import javax.servlet.jsp.tagext.BodyTagSupport;
  import javax.servlet.jsp.tagext.Tag;
 
 /**
  * @author gacl
  * BodyTagSupport类实现了BodyTag接口接口,TagDemo4继承 BodyTagSupport类
  */
 public class TagDemo4 extends BodyTagSupport {
 
     /* 控制doStartTag()方法返回EVAL_BODY_BUFFERED
      * @see javax.servlet.jsp.tagext.BodyTagSupport#doStartTag()
      */
     @Override
     public int doStartTag() throws JspException {
         return BodyTag.EVAL_BODY_BUFFERED;
     }
     
     @Override
     public int doEndTag() throws JspException {
         
         //this.getBodyContent()得到代表标签体的bodyContent对象
         BodyContent bodyContent = this.getBodyContent();         //拿到标签体
        String content = bodyContent.getString();
         //修改标签体里面的内容,将标签体的内容转换成大写
         String result = content.toUpperCase();
         try {
             //输出修改后的内容
             this.pageContext.getOut().write(result);
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
         
         return Tag.EVAL_PAGE;
     }
 }

在WEB-INF目录下的tld文件中添加对该标签处理类的描述,如下:

 <tag>
         <name>demo4</name>
         <tag-class>me.gacl.web.tag.TagDemo4</tag-class>
         <!--demo4标签有标签体,所以这里的body-content设置为JSP-->
         <body-content>JSP</body-content>
 </tag>

 在jsp页面中导入并使用自定义标签,如下:


  <%@ page language="java" pageEncoding="UTF-8"%>
  <%--在jsp页面中导入自定义标签库 --%>
  <%@taglib uri="/gacl" prefix="gacl" %>
  <!DOCTYPE HTML>
  <html>
    <head>
      <title>修改jsp页面内容输出</title>
    </head>
    
   <body>
   <%--在jsp页面中使用自定义标签 demo4标签--%>
       <gacl:demo4>
           <h3>xdp_gacl</h3>
       </gacl:demo4>
   </body>
 </html>

运行效果如下:

 

 

简单标签

 由于传统标签使用三个标签接口来完成不同的功能,显得过于繁琐,不利于标签技术的推广, SUN公司为降低标签技术的学习难度,在JSP 2.0中定义了一个更为简单、便于编写和调用的SimpleTag接口来实现标签的功能。

实现SimpleTag接口的标签通常称为简单标签。简单标签共定义了5个方法:

  • setJspContext方法
  • setParent和getParent方法
  • setJspBody方法
  • doTag方法(非常重要),简单标签使用这个方法就可以完成所有的业务逻辑

 

SimpleTag方法介绍

setJspContext方法

  用于把JSP页面的pageContext对象传递给标签处理器对象

setParent方法

  用于把父标签处理器对象传递给当前标签处理器对象

getParent方法

用于获得当前标签的父标签处理器对象

setJspBody方法

  用于把代表标签体的JspFragment对象传递给标签处理器对象

doTag方法

  用于完成所有的标签逻辑,包括输出、迭代、修改标签体内容等。在doTag方法中可以抛出javax.servlet.jsp.SkipPageException异常,用于通知WEB容器不再执行JSP页面中位于结束标记后面的内容,这等效于在传统标签的doEndTag方法中返回Tag.SKIP_PAGE常量的情况。

 

SimpleTag接口方法的执行顺序

  当web容器开始执行标签时,会调用如下方法完成标签的初始化:

  1. WEB容器调用标签处理器对象的setJspContext方法,将代表JSP页面的pageContext对象传递给标签处理器对象。
  2. WEB容器调用标签处理器对象的setParent方法,将父标签处理器对象传递给这个标签处理器对象。注意,只有在标签存在父标签的情况下,WEB容器才会调用这个方法。
  3. 如果调用标签时设置了属性,容器将调用每个属性对应的setter方法把属性值传递给标签处理器对象。如果标签的属性值是EL表达式或脚本表达式,则WEB容器首先计算表达式的值,然后把值传递给标签处理器对象。
  4. 如果简单标签有标签体,WEB容器将调用setJspBody方法把代表标签体的JspFragment对象传递进来。
  5. 执行标签时WEB容器调用标签处理器的doTag()方法,开发人员在方法体内通过操作JspFragment对象,就可以实现是否执行、迭代、修改标签体的目的。

 

开发简单标签实现页面逻辑

  SUN公司针对SimpleTag接口提供了一个默认的实现类SimpleTagSupport,SimpleTagSupport类中实现了SimpleTag接口的所有方法,因此我们可以编写一个类继承SimpleTagSupport类,然后根据业务需要再重写doTag方法。

控制jsp页面某一部分内容是否执行

 编写一个类继承SimpleTagSupport,然后再重写doTag方法,在doTag方法里面不调用jspFrament.invoke方法即可。

示例代码如下:

SimpleTagDemo1.java

import java.io.IOException;
 
 import javax.servlet.jsp.JspException;
 import javax.servlet.jsp.PageContext;
 import javax.servlet.jsp.tagext.JspFragment;
 import javax.servlet.jsp.tagext.SimpleTagSupport;
 
 /**
  * @author gacl
  * SimpleTagSupport类实现了SimpleTag接口,
  * SampleTagDemo1类继承SimpleTagSupport
  */
 public class SimpleTagDemo1 extends SimpleTagSupport {

    /* 简单标签使用这个方法就可以完成所有的业务逻辑
      * @see javax.servlet.jsp.tagext.SimpleTagSupport#doTag()
     * 重写doTag方法,控制标签体是否执行
     */
     @Override
     public void doTag() throws JspException, IOException {
        //得到代表jsp标签体的JspFragment
       JspFragment jspFragment = this.getJspBody();
        
        //得到jsp页面的的PageContext对象
         //PageContext pageContext = (PageContext) jspFragment.getJspContext();
         //调用JspWriter将标签体的内容输出到浏览器
         //jspFragment.invoke(pageContext.getOut());
         
         //将标签体的内容输出到浏览器
        jspFragment.invoke(null);
        
    }
}

 在WEB-INF目录下新建一个simpletag.tld文件,然后在simpletag.tld文件中添加对该标签处理类的描述,如下:

  

  simpletag.tld文件代码如下:

 <?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用来添加对taglib(标签库)的描述 -->
     <description>孤傲苍狼开发的SimpleTag自定义标签库</description>
     <!--taglib(标签库)的版本号 -->
    <tlib-version>1.0</tlib-version>
     <short-name>GaclSimpleTagLibrary</short-name>
     <!-- 
        为自定义标签库设置一个uri,uri以/开头,/后面的内容随便写,如这里的/simpletag ,
        在Jsp页面中引用标签库时,需要通过uri找到标签库
        在Jsp页面中就要这样引入标签库:<%@taglib uri="/simpletag" prefix="gacl"%>
    -->
     <uri>/simpletag</uri>
    
    <!--一个taglib(标签库)中包含多个自定义标签,每一个自定义标签使用一个tag标记来描述  -->
    <!-- 一个tag标记对应一个自定义标签 -->
     <tag>
        <description>SimpleTag(简单标签)Demo1</description>
         <!-- 
            为标签处理器类配一个标签名,在Jsp页面中使用标签时是通过标签名来找到要调用的标签处理器类的
            通过demo1就能找到对应的me.gacl.web.simpletag.SimpleTagDemo1类
         -->
        <name>demo1</name>
        <!-- 标签对应的处理器类-->
        <tag-class>me.gacl.web.simpletag.SimpleTagDemo1</tag-class>
       <!-- 
        tld文件中有四种标签体类型 :empty  JSP  scriptless  tagdepentend  
           在简单标签(SampleTag)中标签体body-content的值只允许是empty和scriptless,不允许设置成JSP,如果设置成JSP就会出现异常
            在传统标签中标签体body-content的值只允许是empty和JSP
            如果标签体body-content的值设置成tagdepentend,那么就表示标签体里面的内容是给标签处理器类使用的,
             例如:开发一个查询用户的sql标签,此时标签体重的SQL语句就是给SQL标签的标签处理器来使用的
            <gacl:sql>SELECT * FROM USER</gacl:sql>
            在这种情况下,sql标签的<body-content>就要设置成tagdepentend,tagdepentend用得比较少,了解一下即可
         -->
        <body-content>scriptless</body-content>
     </tag>
</taglib>

  在jsp页面中导入并使用自定义标签,如下:

 <%@ page language="java" pageEncoding="UTF-8"%>
 <%--在jsp页面中导入自定义标签库 --%>
 <%@taglib uri="/simpletag" prefix="gacl" %>
 <!DOCTYPE HTML>
 <html>
   <head>
    <title>用简单标签控制标签体是否执行</title>
   </head>
   
   <body>
  
   <%--在jsp页面中使用自定义标签 demo1标签是带有标签体的,标签体的内容是"孤傲苍狼"这几个字符串--%>
     <gacl:demo1>
        孤傲苍狼
     </gacl:demo1>
  </body>
</html>

 运行效果如下:

 

控制jsp页面内容重复执行

  编写一个类继承SimpleTagSupport,然后再重写doTag方法,在doTag方法里面重复调用jspFrament.invoke方法即可。

示例代码如下:

SimpleTagDemo2.java

 import java.io.IOException;
 
 import javax.servlet.jsp.JspException;
 import javax.servlet.jsp.tagext.JspFragment;
 import javax.servlet.jsp.tagext.SimpleTagSupport;
 /**
  * @author gacl
  * SimpleTagSupport类实现了SimpleTag接口,
  * SampleTagDemo2类继承SimpleTagSupport
  */
 public class SimpleTagDemo2 extends SimpleTagSupport {
 
     /* 简单标签使用这个方法就可以完成所有的业务逻辑
     * @see javax.servlet.jsp.tagext.SimpleTagSupport#doTag()
     * 重写doTag方法,控制标签执行5次
      */
     @Override
     public void doTag() throws JspException, IOException {
        // 得到代表jsp标签体的JspFragment
        JspFragment jspFragment = this.getJspBody();
        for (int i = 0; i < 5; i++) {
             // 将标签体的内容输出到浏览器
             jspFragment.invoke(null);
         }
     }
}

  在WEB-INF目录下的simpletag.tld文件中添加对该标签处理类的描述,如下:

 <tag>
         <!-- 标签名 -->
         <name>demo2</name>
         <!-- 标签处理器类-->
         <tag-class>me.gacl.web.simpletag.SimpleTagDemo2</tag-class>
         <!-- 标签体允许的内容 ,scriptless表示标签体的内容不允许是java脚本代码-->
         <body-content>scriptless</body-content>
 </tag>

  在jsp页面中导入并使用自定义标签,如下:

 <%@ page language="java" pageEncoding="UTF-8"%>
  <%--在jsp页面中导入自定义标签库 --%>
  <%@taglib uri="/simpletag" prefix="gacl" %>
  <!DOCTYPE HTML>
  <html>
    <head>
      <title>用简单标签控制标签体执行5次</title>
    </head>
    
   <body>
   
   <%--在jsp页面中使用自定义标签 --%>
     <gacl:demo2>
         孤傲苍狼<p/>
     </gacl:demo2>
   </body>
 </html>

 运行效果如下:

 

 

修改jsp页面内容输出

  编写一个类继承SimpleTagSupport,然后再重写doTag方法,在doTag方法调用jspFrament.invoke方法时,让执行结果写一个自定义的缓冲中即可,然后开发人员可以取出缓冲的数据修改输出。

示例代码如下:

SimpleTagDemo3.java

 import java.io.IOException;
 import java.io.StringWriter;
 
 import javax.servlet.jsp.JspException;
 import javax.servlet.jsp.PageContext;
 import javax.servlet.jsp.tagext.JspFragment;
 import javax.servlet.jsp.tagext.SimpleTagSupport;
 
/**
  * @author gacl
  * SimpleTagSupport类实现了SimpleTag接口,
  * SampleTagDemo3类继承SimpleTagSupport
  */
 public class SimpleTagDemo3 extends SimpleTagSupport {

    /* 简单标签使用这个方法就可以完成所有的业务逻辑
     * @see javax.servlet.jsp.tagext.SimpleTagSupport#doTag()
     * 重写doTag方法,修改标签体里面的内容,将标签体的内容转换成大写
      */
    @Override
     public void doTag() throws JspException, IOException {
        // 得到代表jsp标签体的JspFragment
        JspFragment jspFragment = this.getJspBody();
        StringWriter sw = new StringWriter();
        //将标签体的内容写入到sw流中
       jspFragment.invoke(sw);
        //获取sw流缓冲区的内容
         String content = sw.getBuffer().toString();
         content = content.toUpperCase();
         PageContext pageContext = (PageContext) this.getJspContext();
        //将修改后的content输出到浏览器中
         pageContext.getOut().write(content);
    }
 }

  在WEB-INF目录下的simpletag.tld文件中添加对该标签处理类的描述,如下:

 <tag>
         <!-- 标签名 -->
         <name>demo3</name>
         <!-- 标签处理器类-->
         <tag-class>me.gacl.web.simpletag.SimpleTagDemo3</tag-class>
         <!-- 标签体允许的内容 ,scriptless表示标签体的内容不允许是java脚本代码-->
         <body-content>scriptless</body-content>
 </tag>

在jsp页面中导入并使用自定义标签,如下:

 <%@ page language="java" pageEncoding="UTF-8"%>
  <%--在jsp页面中导入自定义标签库 --%>
  <%--<%@taglib uri="/simpletag" prefix="gacl" %>--%>
  <%--在jsp页面中也可以使用这种方式导入标签库,直接把uri设置成标签库的tld文件所在目录 --%>
  <%@taglib uri="/WEB-INF/simpletag.tld" prefix="gacl"%>
  <!DOCTYPE HTML>
  <html>
    <head>
      <title>用简单标签修改jsp页面内容输出</title>
   </head>
   
   <body>
   
   <%--在jsp页面中使用自定义标签 --%>
     <gacl:demo3>
         gacl_xdp
     </gacl:demo3>
   </body>
 </html>

  运行效果如下:

 

控制整个jsp页面是否执行

  编写一个类继承SimpleTagSupport,然后再重写doTag方法,在doTag方法抛出SkipPageException异常即可,jsp收到这个异常,将忽略标签余下jsp页面的执行。

示例代码如下:

SimpleTagDemo4.java

 import java.io.IOException;
 import javax.servlet.jsp.JspException;
 import javax.servlet.jsp.SkipPageException;
 import javax.servlet.jsp.tagext.SimpleTagSupport;
 
 /**
  * @author gacl
  * SimpleTagSupport类实现了SimpleTag接口,
  * SampleTagDemo4类继承SimpleTagSupport
  */
 public class SimpleTagDemo4 extends SimpleTagSupport {
 
     /* 简单标签使用这个方法就可以完成所有的业务逻辑
      * @see javax.servlet.jsp.tagext.SimpleTagSupport#doTag()
      * 重写doTag方法,控制标签余下的Jsp不执行
      */
     @Override
     public void doTag() throws JspException, IOException {
         //抛出一个SkipPageException异常就可以控制标签余下的Jsp不执行
         throw new SkipPageException();
     }
 }

  在WEB-INF目录下的simpletag.tld文件中添加对该标签处理类的描述,如下:

 <tag>
         <!-- 标签名 -->
         <name>demo4</name>
         <!-- 标签处理器类-->
         <tag-class>me.gacl.web.simpletag.SimpleTagDemo4</tag-class>
         <!-- 标签体允许的内容 ,empty表示该标签没有标签体-->
         <body-content>empty</body-content>
 </tag>

 在jsp页面中导入并使用自定义标签,如下:

 <%@ page language="java" pageEncoding="UTF-8"%>
 <%--在jsp页面中导入自定义标签库 --%>
 <%--<%@taglib uri="/simpletag" prefix="gacl" %>--%>
 <%--在jsp页面中也可以使用这种方式导入标签库,直接把uri设置成标签库的tld文件所在目录 --%>
 <%@taglib uri="/WEB-INF/simpletag.tld" prefix="gacl"%>
 <!DOCTYPE HTML>
 <html>
   <head>
     <title>用简单标签控制标签余下的Jsp不执行</title>
   </head>
 
   <body>
       <h1>孤傲苍狼</h1>
        <%--在jsp页面中使用自定义标签 --%>
        <gacl:demo4/>
        <!-- 这里的内容位于 <gacl:demo4/>标签后面,因此不会输出到页面上 -->
       <h1>白虎神皇</h1>
   </body>
 </html>

  运行效果如下:

 

简单标签开发的一些注意细节

标签类编写细节

  开发标签类时,不要直接去实现SimpleTag接口,而是应该继承SimpleTagSupport类,SimpleTagSupport类是SimpleTag接口的一个默认实现类,通过继承SimpleTagSupport类,就可以直接使用SimpleTagSupport类已经实现的那些方法,如果SimpleTagSupport类的方法实现不满足业务要求,那么就可以根据具体的业务情况将相应的方法进行重写。

tld文件中标签体类型设置细节

  我们开发好一个简单标签后,需要在tld文件中添加对该标签的描述,例如:

 <tag>
         <!-- 标签名 -->
         <name>demo2</name>
         <!-- 标签处理器类-->
         <tag-class>me.gacl.web.simpletag.SimpleTagDemo2</tag-class>
         <!-- 标签体允许的内容 ,scriptless表示标签体的内容不允许是java脚本代码-->
         <body-content>scriptless</body-content>
 </tag>

  开发好一个标签后,在tld文件中使用<tag>来描述一个标签,描述的内容包括标签名(name),标签处理器类(tag-class),标签体的内容(body-content)。

  tld文件中有四种标签体(body-content)类型 :empty、JSP、scriptless、tagdependent 

简单标签标签体的细节注意问题:
     在简单标签(SampleTag)中标签体body-content的值只允许是empty、scriptless、tagdependent,不允许设置成JSP,如果设置成JSP就会出现异常:

The TLD for the class me.gacl.web.simpletag.SimpleTagDemo1 specifies an invalid body-content (JSP) for a SimpleTag

  body-content的值如果设置成empty,那么就表示该标签没有标签体,如果是设置成scriptless,那么表示该标签是有标签体的,但是标签体中的内容不可以是<%java代码%>,例如:

 <gacl:xdpdemo1>
    <%
            //嵌套在标签体中的java代码
         int i= 0;
     %>
       孤傲苍狼
 </gacl:xdpdemo1>

否则运行标签时就会出现如下错误:

Scripting elements ( &lt;%!, &lt;jsp:declaration, &lt;%=, &lt;jsp:expression, &lt;%, &lt;jsp:scriptlet ) are disallowed here

 

tld文件中标签库的uri设置细节

  如果在一个项目中使用或者开发了多个标签库,例如:

  

  那么标签库的uri不能设置成相同的,否则在Jsp页面中通过uri引用标签库时就不知道引用哪一个标签库了,如果真的有那么巧,两个标签库的uri是刚好一样的,如下图所示:

  

  那么在jsp页面中引用标签库时如果"<%@taglib uri="/gacl" prefix="gacl" %>"这样引用,那么就无法判断当前引用的标签库到底是gacl.tld标签库中的标签还是simpletag.tld标签库中的标签,因为两个标签库的uri刚好都是"/gacl",在两个标签库的引用uri一样的情况下,为了能够在jsp中区别到底引用的是哪个标签库,可以换一种引用方式:<%@taglib uri="要引用的标签库的tld文件目录" prefix="gacl"%>,使用taglib指令引入标签库时,taglib指令的uri属性指定为标签库的tld文件目录,这样就可以区别开了,例如:

  引用gacl.tld标签库:<%@taglib uri="/WEB-INF/gacl.tld" prefix="gacl"%>、

  引用simpletag.tld标签库:<%@taglib uri="/WEB-INF/simpletag.tld" prefix="gacl"%>

  所以当在项目中引用了多个标签库,如果标签库的uri刚好是一样的,就可以用这种方式解决。

 

简单标签开发步骤总结

  1、编写一个类继承SimpleTagSupport类,然后根据业务需要重写SimpleTagSupport类中已经实现了的方法,一般情况下只需要重写doTag()方法即可。

  2、在WEB-INF目录下创建一个tld文件,在tld文件中添加对该标签的描述。tld文件不一定放在WEB-INF目录下,也可以放在别的目录,习惯是放在WEB-INF目录下。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wespten

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值