Java Web開發(fā)中的自定義標簽

1、創(chuàng)建工程

新建一個java web工程:


新建一個java web工程

選擇工程存儲位置,輸入工廠名“JavaWebSenior”:


選擇工程存儲位置

工程結(jié)構(gòu)如下:
工程結(jié)構(gòu)

WEB-INF目錄下新建classes和lib目錄如下:

新建classes和lib目錄

設置編譯后class文件的輸出路徑,配置為前面創(chuàng)建的classes目錄灭贷。File->Project Structure:


編譯后class文件的輸出路徑

設置依賴jar包的存放位置削茁,配置為前面創(chuàng)建的lib目錄(選擇Jar Directory):


設置依賴jar包的存放位置

添加tomcat服務器:


添加tomcat服務器1
添加tomcat服務器2

這里之前已經(jīng)在IDEA中添加了Tomcat服務器,這里只需要選中之前添加的Tomcat服務器粟害,并定義一個別名(JavaWebSeniorTomcat)即可:


定義一個別名(JavaWebSeniorTomcat)

然后蕴忆,在上面的對話框中,將當前的工程添加到tomcat運行悲幅。點擊加號套鹅,選擇Artifact...

將當前的工程添加到tomcat運行

效果如下:
添加完成

然后,確定汰具,即可看到當前項目已經(jīng)添加到了tomcat運行卓鹿。


當前項目已經(jīng)添加到了tomcat運行

修改jsp代碼,如下:


寫一個jsp示例頁面

這時候留荔,啟動tomcat:


啟動tomcat

就會看到瀏覽器中自動打開http://localhost:8080吟孙,效果如下:

瀏覽器中自動打開http://localhost:8080

2、自定義標簽入門

使用自定義標簽存谎,可以去掉jsp頁面上的java代碼拔疚。

2.1 入門示意

這里首先要將tomcat的lib目錄引入到項目的依賴,不然的話既荚,好多java web相關(guān)的沒法使用稚失。


將tomcat的lib目錄引入到項目的依賴

首先,寫一個Java類恰聘,實現(xiàn)自定義標簽的內(nèi)容句各。這里實現(xiàn)一個自定義標簽,用來展現(xiàn)客戶端的ip晴叨。
com.test.selfdefine.tag.ShowIpAddressTag

package com.test.selfdefine.tag;


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;
import java.io.IOException;

/**
 * Created by chengxia on 2019/10/11.
 */
public class ShowIpAddressTag implements Tag {

    //接收傳遞進來的PageContext對象
    private PageContext pageContext;

    @Override
    public void setPageContext(PageContext pageContext) {
        this.pageContext = pageContext;
        System.out.println("setPageContext(PageContext pageContext) function executing...");
    }

    @Override
    public void setParent(Tag tag) {
        //什么也不做
    }

    @Override
    public Tag getParent() {
        return null;
    }

    @Override
    public int doStartTag() throws JspException {
        System.out.println("doStartTag() function executing...");
        HttpServletRequest request =(HttpServletRequest) pageContext.getRequest();
        JspWriter out = pageContext.getOut();
        String ip = request.getRemoteAddr();
        try {
            //這里輸出的時候會拋出IOException異常
            out.write(ip);
        } catch (IOException e) {
            //捕獲IOException異常后繼續(xù)拋出
            throw new RuntimeException(e);
        }
        return 0;
    }

    @Override
    public int doEndTag() throws JspException {
        System.out.println("doEndTag() function executing...");
        return 0;
    }

    @Override
    public void release() {
        System.out.println("release() function executing...");
    }
}

新建一個自定義標簽的配置文件凿宾。
WEB-INF/selfdefine.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用來添加對taglib(標簽庫)的描述 -->
    <description>Try Selfdefine Tag</description>
    <!--taglib(標簽庫)的版本號 -->
    <tlib-version>1.0</tlib-version>
    <!-- 這里里面不能有空格 -->
    <short-name>SelfdedineTagLibrary</short-name>
    <!--
       為自定義標簽庫設置一個uri,uri以/開頭兼蕊,/后面的內(nèi)容隨便寫初厚,如這里的/tagtest ,
       在Jsp頁面中引用標簽庫時孙技,需要通過uri找到標簽庫
       在Jsp頁面中就要這樣引入標簽庫:<%@taglib uri="/tagtest" prefix="tagtest"%>
   -->
    <uri>/tagtest</uri>

    <!--一個taglib(標簽庫)中包含多個自定義標簽产禾,每一個自定義標簽使用一個tag標記來描述  -->
    <!-- 一個tag標記對應一個自定義標簽 -->
    <tag>
        <description>用來輸出客戶端的IP地址</description>
        <!--
            為標簽處理器類配一個標簽名,在Jsp頁面中使用標簽時是通過標簽名來找到要調(diào)用的標簽處理器類的
            通過viewIP就能找到對應的me.gacl.web.tag.ViewIPTag類
         -->
        <name>viewIP</name>
        <!-- 標簽對應的處理器類-->
        <tag-class>com.test.selfdefine.tag.ShowIpAddressTag</tag-class>
        <body-content>empty</body-content>
    </tag>

</taglib>

到這里牵啦,我們就完成了一個自定義標簽的定義亚情。在后面的jsp頁面中,可以引用這個自定義標簽哈雏。
index.jsp

<%--
  Created by IntelliJ IDEA.
  User: chengxia
  Date: 2019/10/10
  Time: 8:43 AM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!-- 使用taglib指令引用gacl標簽庫楞件,標簽庫的前綴(prefix)可以隨便設置衫生,如這里設置成 prefix="xdp" -->
<%@taglib uri="/tagtest"  prefix="tagtest"%>
<html>
    <head>
        <title>Self Define Tag Test</title>
    </head>
    <body>
        <h1>Get IP Address using different ways:</h1>
        你的IP地址是(使用java代碼獲取輸出):
        <%
            //在jsp頁面中使用java代碼獲取客戶端IP地址
            String ip = request.getRemoteAddr();
            out.write(ip);
        %>
        <hr/>
        你的IP地址是(使用自定義標簽獲取輸出):
        <%--使用自定義標簽viewIP --%>
        <tagtest:viewIP/>
    </body>
</html>

完成之后,工程的目錄結(jié)構(gòu)如下土浸。


工程的目錄結(jié)構(gòu)

啟動Tomcat罪针,瀏覽器自動打開index.jsp

瀏覽器自動打開index.jsp

2.2 自定義標簽的執(zhí)行流程

我在MacOS上栅迄,用IDEA 2017進行開發(fā)站故,Tomcat 8。在我的機器上毅舆,打開/Users/chengxia/Library/Caches/IntelliJIdea2017.1/tomcat/Unnamed_HelloWorld/work/Catalina/localhost/ROOT/org/apache/jsp目錄西篓,可以看到index.jsp編譯后的java文件。
/Users/chengxia/Library/Caches/IntelliJIdea2017.1/tomcat/Unnamed_JavaWebSenior/work/Catalina/localhost/ROOT/org/apache/jsp/index_jsp.java

/*
 * Generated by the Jasper component of Apache Tomcat
 * Version: Apache Tomcat/8.0.53
 * Generated at: 2019-10-12 00:33:55 UTC
 * Note: The last modified time of this file was set to
 *       the last modified time of the source file after
 *       generation to assist with modification tracking.
 */
package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;

public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent,
                 org.apache.jasper.runtime.JspSourceImports {

  private static final javax.servlet.jsp.JspFactory _jspxFactory =
          javax.servlet.jsp.JspFactory.getDefaultFactory();

  private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants;

  static {
    _jspx_dependants = new java.util.HashMap<java.lang.String,java.lang.Long>(1);
    _jspx_dependants.put("/WEB-INF/selfdefine.tld", Long.valueOf(1570838325000L));
  }

  private static final java.util.Set<java.lang.String> _jspx_imports_packages;

  private static final java.util.Set<java.lang.String> _jspx_imports_classes;

  static {
    _jspx_imports_packages = new java.util.HashSet<>();
    _jspx_imports_packages.add("javax.servlet");
    _jspx_imports_packages.add("javax.servlet.http");
    _jspx_imports_packages.add("javax.servlet.jsp");
    _jspx_imports_classes = null;
  }

  private org.apache.jasper.runtime.TagHandlerPool _005fjspx_005ftagPool_005ftagtest_005fviewIP_005fnobody;

  private volatile javax.el.ExpressionFactory _el_expressionfactory;
  private volatile org.apache.tomcat.InstanceManager _jsp_instancemanager;

  public java.util.Map<java.lang.String,java.lang.Long> getDependants() {
    return _jspx_dependants;
  }

  public java.util.Set<java.lang.String> getPackageImports() {
    return _jspx_imports_packages;
  }

  public java.util.Set<java.lang.String> getClassImports() {
    return _jspx_imports_classes;
  }

  public javax.el.ExpressionFactory _jsp_getExpressionFactory() {
    if (_el_expressionfactory == null) {
      synchronized (this) {
        if (_el_expressionfactory == null) {
          _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
        }
      }
    }
    return _el_expressionfactory;
  }

  public org.apache.tomcat.InstanceManager _jsp_getInstanceManager() {
    if (_jsp_instancemanager == null) {
      synchronized (this) {
        if (_jsp_instancemanager == null) {
          _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig());
        }
      }
    }
    return _jsp_instancemanager;
  }

  public void _jspInit() {
    _005fjspx_005ftagPool_005ftagtest_005fviewIP_005fnobody = org.apache.jasper.runtime.TagHandlerPool.getTagHandlerPool(getServletConfig());
  }

  public void _jspDestroy() {
    _005fjspx_005ftagPool_005ftagtest_005fviewIP_005fnobody.release();
  }

  public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
        throws java.io.IOException, javax.servlet.ServletException {

final java.lang.String _jspx_method = request.getMethod();
if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method) && !javax.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) {
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSPs only permit GET POST or HEAD");
return;
}

    final javax.servlet.jsp.PageContext pageContext;
    javax.servlet.http.HttpSession session = null;
    final javax.servlet.ServletContext application;
    final javax.servlet.ServletConfig config;
    javax.servlet.jsp.JspWriter out = null;
    final java.lang.Object page = this;
    javax.servlet.jsp.JspWriter _jspx_out = null;
    javax.servlet.jsp.PageContext _jspx_page_context = null;


    try {
      response.setContentType("text/html;charset=UTF-8");
      pageContext = _jspxFactory.getPageContext(this, request, response,
                null, true, 8192, true);
      _jspx_page_context = pageContext;
      application = pageContext.getServletContext();
      config = pageContext.getServletConfig();
      session = pageContext.getSession();
      out = pageContext.getOut();
      _jspx_out = out;

      out.write("\n");
      out.write("\n");
      out.write("<!-- 使用taglib指令引用gacl標簽庫憋活,標簽庫的前綴(prefix)可以隨便設置岂津,如這里設置成 prefix=\"xdp\" -->\n");
      out.write("\n");
      out.write("<html>\n");
      out.write("    <head>\n");
      out.write("\t    <title>Self Define Tag Test</title>\n");
      out.write("    </head>\n");
      out.write("    <body>\n");
      out.write("        <h1>Get IP Address using different ways:</h1>\n");
      out.write("        你的IP地址是(使用java代碼獲取輸出):\n");
      out.write("        ");

            //在jsp頁面中使用java代碼獲取客戶端IP地址
            String ip = request.getRemoteAddr();
            out.write(ip);
        
      out.write("\n");
      out.write("        <hr/>\n");
      out.write("        你的IP地址是(使用自定義標簽獲取輸出):\n");
      out.write("        ");
      out.write("\n");
      out.write("        ");
      if (_jspx_meth_tagtest_005fviewIP_005f0(_jspx_page_context))
        return;
      out.write("\n");
      out.write("    </body>\n");
      out.write("</html>\n");
    } catch (java.lang.Throwable t) {
      if (!(t instanceof javax.servlet.jsp.SkipPageException)){
        out = _jspx_out;
        if (out != null && out.getBufferSize() != 0)
          try {
            if (response.isCommitted()) {
              out.flush();
            } else {
              out.clearBuffer();
            }
          } catch (java.io.IOException e) {}
        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
        else throw new ServletException(t);
      }
    } finally {
      _jspxFactory.releasePageContext(_jspx_page_context);
    }
  }

  private boolean _jspx_meth_tagtest_005fviewIP_005f0(javax.servlet.jsp.PageContext _jspx_page_context)
          throws java.lang.Throwable {
    javax.servlet.jsp.PageContext pageContext = _jspx_page_context;
    javax.servlet.jsp.JspWriter out = _jspx_page_context.getOut();
    //  tagtest:viewIP
    com.test.selfdefine.tag.ShowIpAddressTag _jspx_th_tagtest_005fviewIP_005f0 = (com.test.selfdefine.tag.ShowIpAddressTag) _005fjspx_005ftagPool_005ftagtest_005fviewIP_005fnobody.get(com.test.selfdefine.tag.ShowIpAddressTag.class);
    boolean _jspx_th_tagtest_005fviewIP_005f0_reused = false;
    try {
      _jspx_th_tagtest_005fviewIP_005f0.setPageContext(_jspx_page_context);
      _jspx_th_tagtest_005fviewIP_005f0.setParent(null);
      int _jspx_eval_tagtest_005fviewIP_005f0 = _jspx_th_tagtest_005fviewIP_005f0.doStartTag();
      if (_jspx_th_tagtest_005fviewIP_005f0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {
        return true;
      }
      _005fjspx_005ftagPool_005ftagtest_005fviewIP_005fnobody.reuse(_jspx_th_tagtest_005fviewIP_005f0);
      _jspx_th_tagtest_005fviewIP_005f0_reused = true;
    } finally {
      org.apache.jasper.runtime.JspRuntimeLibrary.releaseTag(_jspx_th_tagtest_005fviewIP_005f0, _jsp_getInstanceManager(), _jspx_th_tagtest_005fviewIP_005f0_reused);
    }
    return false;
  }
}

從這個代碼中可以看出,_jspService方法逐行處理jsp文件中的代碼悦即。當遇到自定義標簽時吮成,執(zhí)行_jspx_meth_tagtest_005fviewIP_005f0方法。從這個方法中辜梳,我們就能看到自定義標簽的執(zhí)行流程粱甫。
JSP引擎遇到自定義標簽時,首先創(chuàng)建標簽處理器類的實例對象作瞄,然后按照JSP規(guī)范定義的通信規(guī)則依次調(diào)用它的方法茶宵。
0、實例化自定義標簽處理器類的實例對象宗挥。

javax.servlet.jsp.PageContext pageContext = _jspx_page_context;
javax.servlet.jsp.JspWriter out = _jspx_page_context.getOut();
//  tagtest:viewIP
com.test.selfdefine.tag.ShowIpAddressTag _jspx_th_tagtest_005fviewIP_005f0 = (com.test.selfdefine.tag.ShowIpAddressTag) _005fjspx_005ftagPool_005ftagtest_005fviewIP_005fnobody.get(com.test.selfdefine.tag.ShowIpAddressTag.class);

1乌庶、public void setPageContext(PageContext pc), JSP引擎實例化標簽處理器后契耿,將調(diào)用setPageContext方法將JSP頁面的pageContext對象傳遞給標簽處理器瞒大,標簽處理器以后可以通過這個pageContext對象與JSP頁面進行通信。

_jspx_th_tagtest_005fviewIP_005f0.setPageContext(_jspx_page_context);

2搪桂、public void setParent(Tag t)透敌,setPageContext方法執(zhí)行完后,WEB容器接著調(diào)用的setParent方法將當前標簽的父標簽傳遞給當前標簽處理器踢械,如果當前標簽沒有父標簽拙泽,則傳遞給setParent方法的參數(shù)值為null。

_jspx_th_tagtest_005fviewIP_005f0.setParent(null);

3裸燎、public int doStartTag(),調(diào)用了setPageContext方法和setParent方法之后泼疑,WEB容器執(zhí)行到自定義標簽的開始標記時德绿,就會調(diào)用標簽處理器的doStartTag方法。

int _jspx_eval_tagtest_005fviewIP_005f0 = _jspx_th_tagtest_005fviewIP_005f0.doStartTag();

4、public int doEndTag()移稳,WEB容器執(zhí)行完自定義標簽的標簽體后蕴纳,就會接著去執(zhí)行自定義標簽的結(jié)束標記,此時个粱,WEB容器會去調(diào)用標簽處理器的doEndTag方法古毛。

if (_jspx_th_tagtest_005fviewIP_005f0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {
    return true;
}

這里,需要注意_jspx_meth_tagtest_005fviewIP_005f0的執(zhí)行結(jié)果如果為true都许,_jspService方法就直接返回了稻薇,后續(xù)的jsp代碼不會被處理。這就是自定義標簽中SKIP_PAGE的實現(xiàn)原理胶征。
5塞椎、public void release(),通常WEB容器執(zhí)行完自定義標簽后睛低,標簽處理器會駐留在內(nèi)存中案狠,為其它請求服務器,直至停止web應用時钱雷,web容器才會調(diào)用release方法骂铁。

3、Java Web自定義標簽的API

3.1 API概括

JspTag接口下罩抗,有兩個子分支拉庵。一個分支是Tag接口,另一個分支是SimpleTag接口澄暮。前者是最初的自定義標簽接口名段,我們稱為傳統(tǒng)的自定義標簽api拌滋,后者是后來為了簡化自定義標簽的開發(fā)推出的新的api豪娜。如下圖。


JspTag接口

JspTag接口是所有自定義標簽的父接口梅惯,它是JSP2.0中新定義的一個標記接口馍刮,沒有任何屬性和方法信夫。JspTag接口有Tag和SimpleTag兩個直接子接口,JSP2.0以前的版本中只有Tag接口卡啰,所以把實現(xiàn)Tag接口的自定義標簽也叫做傳統(tǒng)標簽静稻,把實現(xiàn)SimpleTag接口的自定義標簽叫做簡單標簽。

3.2 傳統(tǒng)標簽API

3.2.1 Tag接口

Tag接口是所有傳統(tǒng)標簽的父接口匈辱,其中定義了兩個重要方法(doStartTag振湾、doEndTag)方法和四個常量(EVAL_BODY_INCLUDE、SKIP_BODY亡脸、EVAL_PAGE押搪、SKIP_PAGE)树酪,這兩個方法和四個常量的作用如下:

(1) WEB容器在解釋執(zhí)行JSP頁面的過程中,遇到自定義標簽的開始標記就會去調(diào)用標簽處理器的doStartTag方法大州,doStartTag方法執(zhí)行完后可以向WEB容器返回常量EVAL_BODY_INCLUDE或SKIP_BODY续语。如果doStartTag方法返回EVAL_BODY_INCLUDE,WEB容器就會接著執(zhí)行自定義標簽的標簽體厦画;如果doStartTag方法返回SKIP_BODY疮茄,WEB容器就會忽略自定義標簽的標簽體,直接解釋執(zhí)行自定義標簽的結(jié)束標記根暑。

(2) WEB容器解釋執(zhí)行到自定義標簽的結(jié)束標記時力试,就會調(diào)用標簽處理器的doEndTag方法,doEndTag方法執(zhí)行完后可以向WEB容器返回常量EVAL_PAGE或SKIP_PAGE购裙。如果doEndTag方法返回常量EVAL_PAGE懂版,WEB容器就會接著執(zhí)行JSP頁面中位于結(jié)束標記后面的JSP代碼;如果doEndTag方法返回SKIP_PAGE躏率,WEB容器就會忽略JSP頁面中位于結(jié)束標記后面的所有內(nèi)容躯畴。

從doStartTag和doEndTag方法的作用和返回值的作用可以看出,開發(fā)自定義標簽時可以在doStartTag方法和doEndTag方法體內(nèi)編寫合適的Java程序代碼來實現(xiàn)具體的功能薇芝,通過控制doStartTag方法和doEndTag方法的返回值蓬抄,還可以告訴WEB容器是否執(zhí)行自定義標簽中的標簽體內(nèi)容和JSP頁面中位于自定義標簽的結(jié)束標記后面的內(nèi)容。

3.2.2 IterationTag接口

IterationTag接口繼承了Tag接口夯到,并在Tag接口的基礎上增加了一個doAfterBody方法和一個EVAL_BODY_AGAIN常量嚷缭。實現(xiàn)IterationTag接口的標簽除了可以完成Tag接口所能完成的功能外,還能夠通知WEB容器是否重復執(zhí)行標簽體內(nèi)容耍贾。對于實現(xiàn)了IterationTag接口的自定義標簽阅爽,WEB容器在執(zhí)行完自定義標簽的標簽體后,將調(diào)用標簽處理器的doAfterBody方法荐开,doAfterBody方法可以向WEB容器返回常量EVAL_BODY_AGAIN或SKIP_BODY付翁。如果doAfterBody方法返回EVAL_BODY_AGAIN,WEB容器就會把標簽體內(nèi)容再重復執(zhí)行一次晃听,執(zhí)行完后接著再調(diào)用doAfterBody方法百侧,如此往復,直到doAfterBody方法返回常量SKIP_BODY能扒,WEB容器才會開始處理標簽的結(jié)束標記和調(diào)用doEndTag方法佣渴。

可見,開發(fā)自定義標簽時初斑,可以通過控制doAfterBody方法的返回值來告訴WEB容器是否重復執(zhí)行標簽體內(nèi)容辛润,從而達到循環(huán)處理標簽體內(nèi)容的效果。例如见秤,可以通過一個實現(xiàn)IterationTag接口的標簽來迭代輸出一個集合中的所有元素频蛔,在標簽體部分指定元素的輸出格式灵迫。

在JSP API中也提供了IterationTag接口的默認實現(xiàn)類TagSupport,我們在編寫自定義標簽的標簽處理器類時晦溪,可以繼承和擴展TagSupport類,這相比實現(xiàn)IterationTag接口將簡化開發(fā)工作挣跋。

3.2.3 BodyTag接口

BodyTag接口繼承了IterationTag接口三圆,并在IterationTag接口的基礎上增加了兩個方法(setBodyContent、doInitBody)和一個EVAL_BODY_BUFFERED常量避咆。實現(xiàn)BodyTag接口的標簽除了可以完成IterationTag接口所能完成的功能舟肉,還可以對標簽體內(nèi)容進行修改。對于實現(xiàn)了BodyTag接口的自定義標簽查库,標簽處理器的doStartTag方法不僅可以返回前面講解的常量EVAL_BODY_INCLUDE或SKIP_BODY路媚,還可以返回常量EVAL_BODY_BUFFERED。如果doStartTag方法返回EVAL_BODY_BUFFERED樊销,WEB容器就會創(chuàng)建一個專用于捕獲標簽體運行結(jié)果的BodyContent對象整慎,然后調(diào)用標簽處理器的setBodyContent方法將BodyContent對象的引用傳遞給標簽處理器,WEB容器接著將標簽體的執(zhí)行結(jié)果寫入到BodyContent對象中围苫。在標簽處理器的后續(xù)事件方法中裤园,可以通過先前保存的BodyContent對象的引用來獲取標簽體的執(zhí)行結(jié)果,然后調(diào)用BodyContent對象特有的方法對BodyContent對象中的內(nèi)容(即標簽體的執(zhí)行結(jié)果)進行修改和控制其輸出剂府。

在JSP API中也提供了BodyTag接口的實現(xiàn)類BodyTagSupport拧揽,我們在編寫能夠修改標簽體內(nèi)容的自定義標簽的標簽處理器類時,可以繼承和擴展BodyTagSupport類腺占,這相比實現(xiàn)BodyTag接口將簡化開發(fā)工作淤袜。

3.2.4 傳統(tǒng)標簽接口中的各個方法可以返回的返回值說明

下圖列舉了Tag接口、IterationTag接口和BodyTag接口中的主要方法及它們分別可以返回的返回值的說明衰伯。


傳統(tǒng)標簽接口

3.3 簡單標簽接口API:SimpleTag

SimpleTag接口是JSP2.0中新增的一個標簽接口铡羡。由于傳統(tǒng)標簽使用三個標簽接口來完成不同的功能,顯得過于繁瑣嚎研,不利于標簽技術(shù)的推廣蓖墅,因此,SUN公司為降低標簽技術(shù)的學習難度临扮,在JSP 2.0中定義了一個更為簡單论矾、便于編寫和調(diào)用的SimpleTag接口。

3.3.1 SimpleTag接口簡介

SimpleTag接口與傳統(tǒng)標簽接口最大的區(qū)別在于杆勇,SimpleTag接口只定義了一個用于處理標簽邏輯的doTag方法贪壳,該方法在WEB容器執(zhí)行自定義標簽時調(diào)用,并且只被調(diào)用一次蚜退。那些使用傳統(tǒng)標簽接口所完成的功能闰靴,例如是否執(zhí)行標簽體彪笼、迭代標簽體、對標簽體內(nèi)容進行修改等功能都可以在doTag方法中完成蚂且。在doTag方法中可以拋出javax.servlet.jsp.SkipPageException異常配猫,用于通知WEB容器不再執(zhí)行JSP頁面中位于結(jié)束標記后面的內(nèi)容,這等效于在傳統(tǒng)標簽的doEndTag方法中返回Tag.SKIP_PAGE常量的情況杏死。
除了doTag方法方法泵肄,SimpleTag接口還定義了如下四個方法:

  • setJspContext方法
    用于把JSP頁面的pageContext對象傳遞給標簽處理器對象
  • setParent方法
    用于把父標簽處理器對象傳遞給當前標簽處理器對象
  • getParent方法
    用于獲得當前標簽的父標簽處理器對象
  • setJspBody方法
    用于把代表標簽體的JspFragment對象傳遞給標簽處理器對象

在JSP API中也提供了SimpleTag接口的默認實現(xiàn)類SimpleTagSupport,我們在編寫簡單標簽時淑翼,可以繼承和擴展SimpleTagSupport類腐巢,這相比實現(xiàn)SimpleTag接口將簡化開發(fā)工作。這是推薦的做法玄括,只有當默認的實現(xiàn)類不滿足需求才考慮根據(jù)具體的邏輯需求覆蓋重寫冯丙。

3.3.2 SimpleTag接口方法的執(zhí)行順序

當web容器開始執(zhí)行標簽時,會調(diào)用如下方法完成標簽的初始化:

  • WEB容器調(diào)用標簽處理器對象的setJspContext方法遭京,將代表JSP頁面的pageContext對象傳遞給標簽處理器對象胃惜。
  • WEB容器調(diào)用標簽處理器對象的setParent方法,將父標簽處理器對象傳遞給這個標簽處理器對象洁墙。注意蛹疯,只有在標簽存在父標簽的情況下,WEB容器才會調(diào)用這個方法热监。
  • 如果調(diào)用標簽時設置了屬性捺弦,容器將調(diào)用每個屬性對應的setter方法把屬性值傳遞給標簽處理器對象。如果標簽的屬性值是EL表達式或腳本表達式孝扛,則WEB容器首先計算表達式的值列吼,然后把值傳遞給標簽處理器對象。
  • 如果簡單標簽有標簽體苦始,WEB容器將調(diào)用setJspBody方法把代表標簽體的JspFragment對象傳遞進來寞钥。
  • 執(zhí)行標簽時WEB容器調(diào)用標簽處理器的doTag()方法,開發(fā)人員在方法體內(nèi)通過操作JspFragment對象陌选,就可以實現(xiàn)是否執(zhí)行理郑、迭代、修改標簽體的目的咨油。

4您炉、自定義標簽API代碼示例

4.1 傳統(tǒng)標簽API實例代碼

前面入門的例子中,我們是通過直接實現(xiàn)Tag接口來開發(fā)的自定義標簽役电。SUN公司針對tag接口提供了一個默認的實現(xiàn)類TagSupport赚爵,TagSupport類中實現(xiàn)了tag接口的所有方法,因此,這里我們可以編寫一個類繼承TagSupport類來開發(fā)演示傳統(tǒng)標簽API冀膝。

4.1.1 帶標簽體的展示顯示ip標簽

這里唁奢,我們重新實現(xiàn)一遍前面的顯示ip地址標簽。加入標簽體支持窝剖,在標簽體中麻掸,我們允許配置ip地址獲得說明。
首先枯芬,在WEB-INF/selfdefine.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用來添加對taglib(標簽庫)的描述 -->
    <description>Try Selfdefine Tag</description>
    <!--taglib(標簽庫)的版本號 -->
    <tlib-version>1.0</tlib-version>
    <!-- 這里里面不能有空格 -->
    <short-name>SelfdedineTagLibrary</short-name>
    <!--
       為自定義標簽庫設置一個uri论笔,uri以/開頭,/后面的內(nèi)容隨便寫千所,如這里的/tagtest ,
       在Jsp頁面中引用標簽庫時蒜埋,需要通過uri找到標簽庫
       在Jsp頁面中就要這樣引入標簽庫:<%@taglib uri="/tagtest" prefix="tagtest"%>
   -->
    <uri>/tagtest</uri>

    <!--一個taglib(標簽庫)中包含多個自定義標簽淫痰,每一個自定義標簽使用一個tag標記來描述  -->
    <!-- 一個tag標記對應一個自定義標簽 -->
    <tag>
        <description>用來輸出客戶端的IP地址</description>
        <!--
            為標簽處理器類配一個標簽名,在Jsp頁面中使用標簽時是通過標簽名來找到要調(diào)用的標簽處理器類的
            通過viewIP就能找到對應的me.gacl.web.tag.ViewIPTag類
         -->
        <name>viewIP</name>
        <!-- 標簽對應的處理器類-->
        <tag-class>com.test.selfdefine.tag.ShowIpAddressTag</tag-class>
        <body-content>empty</body-content>
    </tag>
    <tag>
        <description>用來輸出客戶端的IP地址</description>
        <name>viewIPWithBody</name>
        <!-- 標簽對應的處理器類-->
        <tag-class>com.test.selfdefine.tag.ShowIpAddressWithBodyTag</tag-class>
        <body-content>scriptless</body-content>
    </tag>

</taglib>

注意這里的body-content配置了不同的值整份,含義如下:
(1) empty:空標記待错,即起始標記和結(jié)束標記之間沒有內(nèi)容。

<l:hello />
<l:hello ></l:hello >

(2) scriptless:接受文本烈评、EL和JSP動作火俄,但不能含有腳本元素。

<l:hello >This is message body</l:hello>

(3) JSP:體包含JSP元素讲冠,如EL表達式瓜客,標準或定制動作以及腳本元素。

<l:hello >
<%=request.getProtocol()%>
</l:hello>

(4) tagdependent:體中可以包含看似為JSP元素的內(nèi)容竿开,但是容器不對其進行計算(當體中的內(nèi)容與JSP元素產(chǎn)生混淆時采用這個谱仪,較少用)

</l:hello var="p">  
     ${p.name }:${p.age} 
</l:hello>

然后,編寫一個繼承TagSupport類的自定義標簽處理類否彩,并按需重寫其中的方法疯攒。
com.test.selfdefine.tag.ShowIpAddressWithBodyTag

package com.test.selfdefine.tag;


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;
import javax.servlet.jsp.tagext.TagSupport;
import java.io.IOException;

/**
 * Created by chengxia on 2019/10/11.
 */
public class ShowIpAddressWithBodyTag extends TagSupport {

    //接收傳遞進來的PageContext對象
    private PageContext pageContext;

    @Override
    public void setPageContext(PageContext pageContext) {
        this.pageContext = pageContext;
        System.out.println("setPageContext(PageContext pageContext) function executing...");
    }

    @Override
    public int doStartTag() throws JspException {
        System.out.println("doStartTag() function executing...");
        HttpServletRequest request =(HttpServletRequest) pageContext.getRequest();
        JspWriter out = pageContext.getOut();
        String ip = request.getRemoteAddr();
        try {
            //這里輸出的時候會拋出IOException異常
            out.write(ip);
        } catch (IOException e) {
            //捕獲IOException異常后繼續(xù)拋出
            throw new RuntimeException(e);
        }
        return Tag.EVAL_BODY_INCLUDE;
    }
}

注意,這里為了標簽體中的內(nèi)容能夠被處理列荔,doStartTag()方法返回的是Tag.EVAL_BODY_INCLUDE敬尺。
最后是標簽調(diào)用。
index.jsp

<%--
  Created by IntelliJ IDEA.
  User: chengxia
  Date: 2019/10/10
  Time: 8:43 AM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!-- 使用taglib指令引用gacl標簽庫贴浙,標簽庫的前綴(prefix)可以隨便設置砂吞,如這里設置成 prefix="xdp" -->
<%@taglib uri="/tagtest"  prefix="tagtest"%>
<html>
    <head>
        <title>Self Define Tag Test</title>
    </head>
    <body>
        <h1>Get IP Address using different ways:</h1>
        你的IP地址是(使用java代碼獲取輸出):
        <%
            //在jsp頁面中使用java代碼獲取客戶端IP地址
            String ip = request.getRemoteAddr();
            out.write(ip);
        %>
        <hr/>
        你的IP地址是(使用自定義標簽獲取輸出):
        <%--使用自定義標簽viewIP --%>
        <tagtest:viewIP/>
        <hr/>
        你的IP地址是(使用自定義標簽獲取輸出,帶標簽體):
        <%--使用自定義標簽viewIP --%>
        <tagtest:viewIPWithBody>(Ip Address got by self define tag.)</tagtest:viewIPWithBody>
    </body>
</html>

啟動服務器之后悬而,運行效果如下:


運行效果

如果想讓自定義標簽體中的提示信息出現(xiàn)在ip地址之前呜舒,怎么做呢?只需要將輸出ip的邏輯放到doEngTag方法中就可以。如下:

package com.test.selfdefine.tag;


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;
import javax.servlet.jsp.tagext.TagSupport;
import java.io.IOException;

/**
 * Created by chengxia on 2019/10/11.
 */
public class ShowIpAddressWithBodyTag extends TagSupport {

    //接收傳遞進來的PageContext對象
    private PageContext pageContext;

    @Override
    public void setPageContext(PageContext pageContext) {
        this.pageContext = pageContext;
        System.out.println("setPageContext(PageContext pageContext) function executing...");
    }

    @Override
    public int doStartTag() throws JspException {
        return Tag.EVAL_BODY_INCLUDE;
    }

    @Override
    public int doEndTag() throws JspException{
        System.out.println("doStartTag() function executing...");
        HttpServletRequest request =(HttpServletRequest) pageContext.getRequest();
        JspWriter out = pageContext.getOut();
        String ip = request.getRemoteAddr();
        try {
            //這里輸出的時候會拋出IOException異常
            out.write(ip);
        } catch (IOException e) {
            //捕獲IOException異常后繼續(xù)拋出
            throw new RuntimeException(e);
        }
        return Tag.EVAL_PAGE;
    }
}

運行效果如下:


運行效果

如果想忽略這個標簽體袭蝗,只需改變doStartTag方法的返回值唤殴。如下:
com.test.selfdefine.tag.ShowIpAddressWithBodyTag

package com.test.selfdefine.tag;


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;
import javax.servlet.jsp.tagext.TagSupport;
import java.io.IOException;

/**
 * Created by chengxia on 2019/10/11.
 */
public class ShowIpAddressWithBodyTag extends TagSupport {

    //接收傳遞進來的PageContext對象
    private PageContext pageContext;

    @Override
    public void setPageContext(PageContext pageContext) {
        this.pageContext = pageContext;
        System.out.println("setPageContext(PageContext pageContext) function executing...");
    }

    @Override
    public int doStartTag() throws JspException {
        return Tag.SKIP_BODY;
    }

    @Override
    public int doEndTag() throws JspException{
        System.out.println("doStartTag() function executing...");
        HttpServletRequest request =(HttpServletRequest) pageContext.getRequest();
        JspWriter out = pageContext.getOut();
        String ip = request.getRemoteAddr();
        try {
            //這里輸出的時候會拋出IOException異常
            out.write(ip);
        } catch (IOException e) {
            //捕獲IOException異常后繼續(xù)拋出
            throw new RuntimeException(e);
        }
        return Tag.EVAL_PAGE;
    }
}

效果如下:


運行效果

如果想讓標簽之后的頁面元素都不被處理,只需修改doEndTag的返回值到腥。如下朵逝。
com.test.selfdefine.tag.ShowIpAddressWithBodyTag

package com.test.selfdefine.tag;


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;
import javax.servlet.jsp.tagext.TagSupport;
import java.io.IOException;

/**
 * Created by chengxia on 2019/10/11.
 */
public class ShowIpAddressWithBodyTag extends TagSupport {

    //接收傳遞進來的PageContext對象
    private PageContext pageContext;

    @Override
    public void setPageContext(PageContext pageContext) {
        this.pageContext = pageContext;
        System.out.println("setPageContext(PageContext pageContext) function executing...");
    }

    @Override
    public int doStartTag() throws JspException {
        return Tag.SKIP_BODY;
    }

    @Override
    public int doEndTag() throws JspException{
        System.out.println("doStartTag() function executing...");
        HttpServletRequest request =(HttpServletRequest) pageContext.getRequest();
        JspWriter out = pageContext.getOut();
        String ip = request.getRemoteAddr();
        try {
            //這里輸出的時候會拋出IOException異常
            out.write(ip);
        } catch (IOException e) {
            //捕獲IOException異常后繼續(xù)拋出
            throw new RuntimeException(e);
        }
        return Tag.SKIP_PAGE;
    }
}

效果如下:


運行效果

可以看到盡管顯式?jīng)]有區(qū)別,但是乡范,html文件最后的閉合標簽都是缺失的配名。原因就是自定義標簽之后的內(nèi)容,都被doEndTag()函數(shù)返回Tag.SKIP_PAGE之后略過不處理了晋辆。

4.1.2 控制jsp頁面內(nèi)容重復執(zhí)行

編寫一個類實現(xiàn)Iterationtag接口(這里采用的是繼承現(xiàn)有的實現(xiàn)了TagSupport)渠脉,控制doAfterBody()方法的返回值,如果這個方法返回EVAL_BODY_AGAIN瓶佳, 則web服務器又執(zhí)行一次標簽體芋膘,依次類推,一直執(zhí)行到doAfterBody方法返回SKIP_BODY霸饲,則標簽體才不會重復執(zhí)行为朋。

首先定義一個自定義標簽處理類。
com.test.selfdefine.tag.RepeatBodyTag

package com.test.selfdefine.tag;


import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.IterationTag;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TagSupport;

/**
 * Created by chengxia on 2019/10/11.
 */
public class RepeatBodyTag extends TagSupport {

    int repeatTimes = 3;

    /**
     * 返回Tag.EVAL_BODY_INCLUDE厚脉,以至于能夠處理標簽體
     * */
    @Override
    public int doStartTag() throws JspException {
        return Tag.EVAL_BODY_INCLUDE;
    }

    /* 控制doAfterBody()方法的返回值习寸,
      * 如果這個方法返回EVAL_BODY_AGAIN, 則web服務器又執(zhí)行一次標簽體傻工,
      * 依次類推霞溪,一直執(zhí)行到doAfterBody方法返回SKIP_BODY,則標簽體才不會重復執(zhí)行精钮。
      * @see javax.servlet.jsp.tagext.TagSupport#doAfterBody()
      */
    @Override
    public int doAfterBody() throws JspException {
        repeatTimes--;
        if (repeatTimes > 0) {
            return IterationTag.EVAL_BODY_AGAIN;
        } else {
            return IterationTag.SKIP_BODY;
        }
    }
}

在自定義標簽配置文件中添加條目:
WEB-INF/selfdefine.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用來添加對taglib(標簽庫)的描述 -->
    <description>Try Selfdefine Tag</description>
    <!--taglib(標簽庫)的版本號 -->
    <tlib-version>1.0</tlib-version>
    <!-- 這里里面不能有空格 -->
    <short-name>SelfdedineTagLibrary</short-name>
    <!--
       為自定義標簽庫設置一個uri威鹿,uri以/開頭,/后面的內(nèi)容隨便寫轨香,如這里的/tagtest 忽你,
       在Jsp頁面中引用標簽庫時,需要通過uri找到標簽庫
       在Jsp頁面中就要這樣引入標簽庫:<%@taglib uri="/tagtest" prefix="tagtest"%>
   -->
    <uri>/tagtest</uri>

    <!--一個taglib(標簽庫)中包含多個自定義標簽臂容,每一個自定義標簽使用一個tag標記來描述  -->
    <!-- 一個tag標記對應一個自定義標簽 -->
    <tag>
        <description>用來輸出客戶端的IP地址</description>
        <!--
            為標簽處理器類配一個標簽名科雳,在Jsp頁面中使用標簽時是通過標簽名來找到要調(diào)用的標簽處理器類的
            通過viewIP就能找到對應的me.gacl.web.tag.ViewIPTag類
         -->
        <name>viewIP</name>
        <!-- 標簽對應的處理器類-->
        <tag-class>com.test.selfdefine.tag.ShowIpAddressTag</tag-class>
        <body-content>empty</body-content>
    </tag>
    <tag>
        <description>用來輸出客戶端的IP地址</description>
        <name>viewIPWithBody</name>
        <!-- 標簽對應的處理器類-->
        <tag-class>com.test.selfdefine.tag.ShowIpAddressWithBodyTag</tag-class>
        <body-content>scriptless</body-content>
    </tag>
    <tag>
        <description>用來輸出客戶端的IP地址</description>
        <name>RepeatBodyTag</name>
        <!-- 標簽對應的處理器類-->
        <tag-class>com.test.selfdefine.tag.RepeatBodyTag</tag-class>
        <body-content>JSP</body-content>
    </tag>

</taglib>

寫一個jsp文件,測試剛剛創(chuàng)建的自定義標簽:
test.jsp

<%--
  Created by IntelliJ IDEA.
  User: chengxia
  Date: 2019/10/10
  Time: 8:43 AM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!-- 使用taglib指令引用gacl標簽庫脓杉,標簽庫的前綴(prefix)可以隨便設置糟秘,如這里設置成 prefix="xdp" -->
<%@taglib uri="/tagtest"  prefix="tagtest"%>
<html>
    <head>
        <title>Self Define Tag Test</title>
    </head>
    <body>
        <tagtest:RepeatBodyTag>
            <h2>This is content for repeat tag.</h2>
        </tagtest:RepeatBodyTag>
    </body>
</html>

啟動服務器,第一次訪問http://localhost:8080/test.jsp

第一次訪問

第二次訪問:


第二次訪問

至于原因球散,從前面的自定義標簽執(zhí)行流程不難理解尿赚。自定義標簽處理類,只實例化一次,第一次之后凌净,x的值就變成0了悲龟。所以,第一次之后冰寻,后面只會對該自定義標簽的標簽體渲染一次须教。

4.1.3 修改jsp頁面內(nèi)容輸出

編寫一個類實現(xiàn)BodyTag接口,控制doStartTag()方法返回EVAL_BODY_BUFFERED斩芭,則web服務器會創(chuàng)建BodyContent對象捕獲標簽體轻腺,然后在doEndTag()方法體內(nèi)陕凹,得到代表標簽體的bodyContent對象工秩,從而就可以對標簽體進行修改操作捏鱼。

SUN公司針對BodyTag接口提供了一個默認的實現(xiàn)類BodyTagSupport施无,BodyTagSupport類中實現(xiàn)了BodyTag接口的所有方法,因此我們可以編寫一個類繼承BodyTagSupport類啤誊,然后再根據(jù)需要重寫doStartTag方法和doEndTag()方法豪嚎。

同樣舷礼,也是细卧,先定義一個自定義標簽處理類。作為演示筒占,其作用是將標簽體中的內(nèi)容全部轉(zhuǎn)化為大寫贪庙。
com.test.selfdefine.tag.ModBodyContentTag

package com.test.selfdefine.tag;


import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.*;
import java.io.IOException;

/**
 * Created by chengxia on 2019/10/11.
 */
public class ModBodyContentTag extends BodyTagSupport {

    /**
     * 控制doStartTag()方法返回EVAL_BODY_BUFFERED
     * */
    @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();
        //修改標簽體里面的內(nèi)容,將標簽體的內(nèi)容轉(zhuǎn)換成大寫
        String result = content.toUpperCase();
        try {
            //輸出修改后的內(nèi)容
            this.pageContext.getOut().write(result);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return Tag.EVAL_PAGE;
    }
}

在自定義標簽配置文件中翰苫,添加自定義標簽配置止邮。
WEB-INF/selfdefine.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用來添加對taglib(標簽庫)的描述 -->
    <description>Try Selfdefine Tag</description>
    <!--taglib(標簽庫)的版本號 -->
    <tlib-version>1.0</tlib-version>
    <!-- 這里里面不能有空格 -->
    <short-name>SelfdedineTagLibrary</short-name>
    <!--
       為自定義標簽庫設置一個uri,uri以/開頭奏窑,/后面的內(nèi)容隨便寫导披,如這里的/tagtest ,
       在Jsp頁面中引用標簽庫時埃唯,需要通過uri找到標簽庫
       在Jsp頁面中就要這樣引入標簽庫:<%@taglib uri="/tagtest" prefix="tagtest"%>
   -->
    <uri>/tagtest</uri>

    <!--一個taglib(標簽庫)中包含多個自定義標簽撩匕,每一個自定義標簽使用一個tag標記來描述  -->
    <!-- 一個tag標記對應一個自定義標簽 -->
    <tag>
        <description>用來輸出客戶端的IP地址</description>
        <!--
            為標簽處理器類配一個標簽名,在Jsp頁面中使用標簽時是通過標簽名來找到要調(diào)用的標簽處理器類的
            通過viewIP就能找到對應的me.gacl.web.tag.ViewIPTag類
         -->
        <name>viewIP</name>
        <!-- 標簽對應的處理器類-->
        <tag-class>com.test.selfdefine.tag.ShowIpAddressTag</tag-class>
        <body-content>empty</body-content>
    </tag>
    <tag>
        <description>用來輸出客戶端的IP地址</description>
        <name>viewIPWithBody</name>
        <!-- 標簽對應的處理器類-->
        <tag-class>com.test.selfdefine.tag.ShowIpAddressWithBodyTag</tag-class>
        <body-content>scriptless</body-content>
    </tag>
    <tag>
        <description>用來輸出客戶端的IP地址</description>
        <name>RepeatBodyTag</name>
        <!-- 標簽對應的處理器類-->
        <tag-class>com.test.selfdefine.tag.RepeatBodyTag</tag-class>
        <body-content>JSP</body-content>
    </tag>
    <tag>
        <description>用來輸出客戶端的IP地址</description>
        <name>ModBodyContentTag</name>
        <!-- 標簽對應的處理器類-->
        <tag-class>com.test.selfdefine.tag.ModBodyContentTag</tag-class>
        <body-content>JSP</body-content>
    </tag>

</taglib>

最后墨叛,寫一個測試頁面來測試這個自定義標簽止毕。
test.jsp

<%--
  Created by IntelliJ IDEA.
  User: chengxia
  Date: 2019/10/10
  Time: 8:43 AM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!-- 使用taglib指令引用gacl標簽庫,標簽庫的前綴(prefix)可以隨便設置漠趁,如這里設置成 prefix="xdp" -->
<%@taglib uri="/tagtest"  prefix="tagtest"%>
<html>
    <head>
        <title>Self Define Tag Test</title>
    </head>
    <body>
        <tagtest:RepeatBodyTag>
            <h2>This is content for repeat tag.</h2>
        </tagtest:RepeatBodyTag>
    <hr/>
    <tagtest:ModBodyContentTag>
        This is content to be modified.
    </tagtest:ModBodyContentTag>
    </body>
</html>

重啟tomcat扁凛,訪問http://localhost:8080/test.jsp,效果如下闯传。

運行效果

4.2 簡單標簽API示例代碼

SUN公司針對SimpleTag接口提供了一個默認的實現(xiàn)類SimpleTagSupport谨朝,SimpleTagSupport類中實現(xiàn)了SimpleTag接口的所有方法,因此我們可以編寫一個類繼承SimpleTagSupport類,然后根據(jù)業(yè)務需要再重寫doTag方法字币。

4.2.1 控制jsp中自定義標簽體中內(nèi)容是否執(zhí)行

這里则披,我們編寫一個類繼承SimpleTagSupport,然后再重寫doTag方法纬朝,在doTag方法里面不調(diào)用jspFrament.invoke方法即可收叶。
首先,我們新建一個標簽處理類com.test.selfdefine.tag.SimpleTagTest1共苛。

package com.test.selfdefine.tag;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;

/**
 * Created by chengxia on 2019/10/23.
 */
public class SimpleTagTest1 extends SimpleTagSupport {
    /* 簡單標簽使用這個方法就可以完成所有的業(yè)務邏輯
    * @see javax.servlet.jsp.tagext.SimpleTagSupport#doTag()
    * 重寫doTag方法判没,控制標簽體是否執(zhí)行
    */
    @Override
    public void doTag() throws JspException, IOException {
        //得到代表jsp標簽體的JspFragment
        JspFragment jspFragment = this.getJspBody();

        //得到jsp頁面的的PageContext對象
        //PageContext pageContext = (PageContext) jspFragment.getJspContext();
        //調(diào)用JspWriter將標簽體的內(nèi)容輸出到瀏覽器
        //jspFragment.invoke(pageContext.getOut());
        //這兩行代碼和下面的一行效果一樣

        //將標簽體的內(nèi)容輸出到瀏覽器,如果注掉這一行隅茎,就不會顯示標簽體
        jspFragment.invoke(null);
    }
}

然后澄峰,我們創(chuàng)建一個標簽配置文件WEB-INF/simpletag.tld

<?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">

    <!-- description用來添加對taglib(標簽庫)的描述 -->
    <description>SimpleTag Test</description>
    <tlib-version>1.0</tlib-version>
    <short-name>SimpleTagLibraryTest</short-name>
    <!--
       為自定義標簽庫設置一個uri辟犀,uri以/開頭俏竞,/后面的內(nèi)容隨便寫,如這里的/simpletag 堂竟,
       在Jsp頁面中引用標簽庫時魂毁,需要通過uri找到標簽庫
       在Jsp頁面中就要這樣引入標簽庫:<%@taglib uri="/simpletag" prefix="simptag"%>
   -->
    <uri>/simpletag</uri>

    <!-- Invoke 'Generate' action to add tags or functions -->
    <!--一個taglib(標簽庫)中包含多個自定義標簽,每一個自定義標簽使用一個tag標記來描述  -->
    <!-- 一個tag標記對應一個自定義標簽 -->
    <tag>
        <description>SimpleTagTest1</description>
        <!--
            為標簽處理器類配一個標簽名出嘹,在Jsp頁面中使用標簽時是通過標簽名來找到要調(diào)用的標簽處理器類的
            通過SimpleTag1就能找到對應的com.test.selfdefine.tag.SimpleTagTest1類
         -->
        <name>SimpleTag1</name>
        <!-- 標簽對應的處理器類-->
        <tag-class>com.test.selfdefine.tag.SimpleTagTest1</tag-class>
        <!--
        tld文件中有四種標簽體類型 :empty  JSP  scriptless  tagdepentend
            在簡單標簽(SampleTag)中標簽體body-content的值只允許是empty和scriptless席楚,不允許設置成JSP,如果設置成JSP就會出現(xiàn)異常
            在傳統(tǒng)標簽中標簽體body-content的值只允許是empty和JSP
            如果標簽體body-content的值設置成tagdepentend,那么就表示標簽體里面的內(nèi)容是給標簽處理器類使用的税稼,
            例如:開發(fā)一個查詢用戶的sql標簽烦秩,此時標簽體重的SQL語句就是給SQL標簽的標簽處理器來使用的
            <selftag:sql>SELECT * FROM USER</selftag:sql>
            在這種情況下,sql標簽的<body-content>就要設置成tagdepentend郎仆,tagdepentend用得比較少只祠,了解一下即可
        -->
        <body-content>scriptless</body-content>
    </tag>

</taglib>

最后,寫一個jsp文件SimpleTag.jsp來測試該自定義標簽扰肌。

<%--
  Created by IntelliJ IDEA.
  User: chengxia
  Date: 2019/10/10
  Time: 8:43 AM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!-- 使用taglib指令引用simptag標簽庫抛寝,標簽庫的前綴(prefix)可以隨便設置,如這里設置成 prefix="simptag" -->
<%@ taglib prefix="simptag" uri="/simpletag" %>
<html>
    <head>
        <title>Self Define Tag Test</title>
    </head>
    <body>
        <h1>Ha Ha</h1>
        <hr/>
        <simptag:SimpleTag1>
            <h2>This is content for a simple tag.</h2>
        </simptag:SimpleTag1>
    </body>
</html>

這樣狡耻,啟動服務器墩剖,訪問http://localhost:8080/SimpleTag.jsp,效果如下夷狰。

運行效果1

如果注掉標簽處理類中的jspFragment.invoke(null);岭皂,再重啟服務器,訪問http://localhost:8080/SimpleTag.jsp沼头,效果如下爷绘。

運行效果2

這樣书劝,就是實現(xiàn)了通過SimpleTag接口來控制標簽體是否輸出到頁面。

4.2.2 控制自定義標簽的標簽體內(nèi)容重復執(zhí)行

這里土至,同樣也是編寫一個類繼承SimpleTagSupport购对,然后再重寫doTag方法,在doTag方法里面重復調(diào)用jspFrament.invoke方法即可陶因。
新建一個標簽處理類com.test.selfdefine.tag.SimpleTagRepeat骡苞。

package com.test.selfdefine.tag;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;

/**
 * Created by chengxia on 2019/10/23.
 */
public class SimpleTagRepeat extends SimpleTagSupport {
    /* 簡單標簽使用這個方法就可以完成所有的業(yè)務邏輯
    * @see javax.servlet.jsp.tagext.SimpleTagSupport#doTag()
    * 重寫doTag方法,控制標簽體是否重復執(zhí)行楷扬,這里重復輸出3次標簽體解幽。
    */
    @Override
    public void doTag() throws JspException, IOException {
        // 得到代表jsp標簽體的JspFragment
         JspFragment jspFragment = this.getJspBody();
         for (int i = 0; i < 3; i++) {
             // 將標簽體的內(nèi)容輸出到瀏覽器
             jspFragment.invoke(null);
         }
    }
}

在標簽配置文件WEB-INF/simpletag.tld中添加這一個自定義標簽配置。

<?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">

    <!-- description用來添加對taglib(標簽庫)的描述 -->
    <description>SimpleTag Test</description>
    <tlib-version>1.0</tlib-version>
    <short-name>SimpleTagLibraryTest</short-name>
    <!--
       為自定義標簽庫設置一個uri烘苹,uri以/開頭躲株,/后面的內(nèi)容隨便寫,如這里的/simpletag 镣衡,
       在Jsp頁面中引用標簽庫時霜定,需要通過uri找到標簽庫
       在Jsp頁面中就要這樣引入標簽庫:<%@taglib uri="/simpletag" prefix="simptag"%>
   -->
    <uri>/simpletag</uri>

    <!-- Invoke 'Generate' action to add tags or functions -->
    <!--一個taglib(標簽庫)中包含多個自定義標簽,每一個自定義標簽使用一個tag標記來描述  -->
    <!-- 一個tag標記對應一個自定義標簽 -->
    <tag>
        <description>SimpleTagTest1</description>
        <!--
            為標簽處理器類配一個標簽名廊鸥,在Jsp頁面中使用標簽時是通過標簽名來找到要調(diào)用的標簽處理器類的
            通過SimpleTag1就能找到對應的com.test.selfdefine.tag.SimpleTagTest1類
         -->
        <name>SimpleTag1</name>
        <!-- 標簽對應的處理器類-->
        <tag-class>com.test.selfdefine.tag.SimpleTagTest1</tag-class>
        <!--
        tld文件中有四種標簽體類型 :empty  JSP  scriptless  tagdepentend
            在簡單標簽(SampleTag)中標簽體body-content的值只允許是empty和scriptless望浩,不允許設置成JSP,如果設置成JSP就會出現(xiàn)異常
            在傳統(tǒng)標簽中標簽體body-content的值只允許是empty和JSP
            如果標簽體body-content的值設置成tagdepentend,那么就表示標簽體里面的內(nèi)容是給標簽處理器類使用的惰说,
            例如:開發(fā)一個查詢用戶的sql標簽曾雕,此時標簽體重的SQL語句就是給SQL標簽的標簽處理器來使用的
            <selftag:sql>SELECT * FROM USER</selftag:sql>
            在這種情況下,sql標簽的<body-content>就要設置成tagdepentend助被,tagdepentend用得比較少,了解一下即可
        -->
        <body-content>scriptless</body-content>
    </tag>

    <tag>
         <!-- 標簽名 -->
         <name>SimpleTagRepeat</name>
         <!-- 標簽處理器類-->
         <tag-class>com.test.selfdefine.tag.SimpleTagRepeat</tag-class>
         <!-- 標簽體允許的內(nèi)容 切诀,scriptless表示標簽體的內(nèi)容不允許是java腳本代碼-->
         <body-content>scriptless</body-content>
    </tag>

</taglib>

寫一個測試頁面SimpleTag.jsp揩环,引用這一個自定義標簽。

<%--
  Created by IntelliJ IDEA.
  User: chengxia
  Date: 2019/10/10
  Time: 8:43 AM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!-- 使用taglib指令引用simptag標簽庫幅虑,標簽庫的前綴(prefix)可以隨便設置丰滑,如這里設置成 prefix="simptag" -->
<%@ taglib prefix="simptag" uri="/simpletag" %>
<html>
    <head>
        <title>Self Define Tag Test</title>
    </head>
    <body>
        <h1>Ha Ha</h1>
        <hr/>
        <simptag:SimpleTag1>
            <h2>This is content for a simple tag.</h2>
        </simptag:SimpleTag1>
        <hr/>
        <simptag:SimpleTagRepeat>
            <h2>Content for simple tag repeat.</h2>
        </simptag:SimpleTagRepeat>
    </body>
</html>

啟動服務器之后,訪問http://localhost:8080/SimpleTag.jsp倒庵,效果如下褒墨。

運行效果

4.2.3 修改標簽體的輸出內(nèi)容

編寫一個類繼承SimpleTagSupport,然后再重寫doTag方法擎宝,在doTag方法調(diào)用jspFrament.invoke方法時郁妈,讓執(zhí)行結(jié)果寫一個自定義的緩沖中即可,然后開發(fā)人員可以取出緩沖的數(shù)據(jù)修改輸出绍申。
標簽處理類com.test.selfdefine.tag.SimpleTagModify

package com.test.selfdefine.tag;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
import java.io.StringWriter;

/**
 * Created by chengxia on 2019/10/23.
 */
public class SimpleTagModify extends SimpleTagSupport {
    /* 簡單標簽使用這個方法就可以完成所有的業(yè)務邏輯
    * @see javax.servlet.jsp.tagext.SimpleTagSupport#doTag()
    * 重寫doTag方法噩咪,控制標簽體是否重復執(zhí)行顾彰,這里將標簽體的內(nèi)容轉(zhuǎn)化為大寫之后輸出。
    */
    @Override
    public void doTag() throws JspException, IOException {
        // 得到代表jsp標簽體的JspFragment
         JspFragment jspFragment = this.getJspBody();
         StringWriter sw = new StringWriter();
         //將標簽體的內(nèi)容寫入到sw流中
         jspFragment.invoke(sw);
         //獲取sw流緩沖區(qū)的內(nèi)容
         String content = sw.getBuffer().toString();
         content = content.toUpperCase();
         PageContext pageContext = (PageContext) this.getJspContext();
         //將修改后的content輸出到瀏覽器中
         pageContext.getOut().write(content);
    }
}

在自定義標簽配置文件WEB-INF/simpletag.tld中添加該自定義標簽的配置胃碾。

<?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">

    <!-- description用來添加對taglib(標簽庫)的描述 -->
    <description>SimpleTag Test</description>
    <tlib-version>1.0</tlib-version>
    <short-name>SimpleTagLibraryTest</short-name>
    <!--
       為自定義標簽庫設置一個uri涨享,uri以/開頭,/后面的內(nèi)容隨便寫仆百,如這里的/simpletag 厕隧,
       在Jsp頁面中引用標簽庫時,需要通過uri找到標簽庫
       在Jsp頁面中就要這樣引入標簽庫:<%@taglib uri="/simpletag" prefix="simptag"%>
   -->
    <uri>/simpletag</uri>

    <!-- Invoke 'Generate' action to add tags or functions -->
    <!--一個taglib(標簽庫)中包含多個自定義標簽俄周,每一個自定義標簽使用一個tag標記來描述  -->
    <!-- 一個tag標記對應一個自定義標簽 -->
    <tag>
        <description>SimpleTagTest1</description>
        <!--
            為標簽處理器類配一個標簽名吁讨,在Jsp頁面中使用標簽時是通過標簽名來找到要調(diào)用的標簽處理器類的
            通過SimpleTag1就能找到對應的com.test.selfdefine.tag.SimpleTagTest1類
         -->
        <name>SimpleTag1</name>
        <!-- 標簽對應的處理器類-->
        <tag-class>com.test.selfdefine.tag.SimpleTagTest1</tag-class>
        <!--
        tld文件中有四種標簽體類型 :empty  JSP  scriptless  tagdepentend
            在簡單標簽(SampleTag)中標簽體body-content的值只允許是empty和scriptless,不允許設置成JSP,如果設置成JSP就會出現(xiàn)異常
            在傳統(tǒng)標簽中標簽體body-content的值只允許是empty和JSP
            如果標簽體body-content的值設置成tagdepentend栈源,那么就表示標簽體里面的內(nèi)容是給標簽處理器類使用的挡爵,
            例如:開發(fā)一個查詢用戶的sql標簽,此時標簽體重的SQL語句就是給SQL標簽的標簽處理器來使用的
            <selftag:sql>SELECT * FROM USER</selftag:sql>
            在這種情況下甚垦,sql標簽的<body-content>就要設置成tagdepentend茶鹃,tagdepentend用得比較少,了解一下即可
        -->
        <body-content>scriptless</body-content>
    </tag>

    <tag>
         <!-- 標簽名 -->
         <name>SimpleTagRepeat</name>
         <!-- 標簽處理器類-->
         <tag-class>com.test.selfdefine.tag.SimpleTagRepeat</tag-class>
         <!-- 標簽體允許的內(nèi)容 艰亮,scriptless表示標簽體的內(nèi)容不允許是java腳本代碼-->
         <body-content>scriptless</body-content>
    </tag>

    <tag>
         <!-- 標簽名 -->
         <name>SimpleTagModify</name>
         <!-- 標簽處理器類-->
         <tag-class>com.test.selfdefine.tag.SimpleTagModify</tag-class>
         <!-- 標簽體允許的內(nèi)容 闭翩,scriptless表示標簽體的內(nèi)容不允許是java腳本代碼-->
         <body-content>scriptless</body-content>
    </tag>

</taglib>

寫一個頁面SimpleTag.jsp,引用該自定義標簽迄埃。

<%--
  Created by IntelliJ IDEA.
  User: chengxia
  Date: 2019/10/10
  Time: 8:43 AM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!-- 使用taglib指令引用simptag標簽庫疗韵,標簽庫的前綴(prefix)可以隨便設置,如這里設置成 prefix="simptag" -->
<%@ taglib prefix="simptag" uri="/simpletag" %>
<html>
    <head>
        <title>Self Define Tag Test</title>
    </head>
    <body>
        <h1>Ha Ha</h1>
        <hr/>
        <simptag:SimpleTag1>
            <h2>This is content for a simple tag.</h2>
        </simptag:SimpleTag1>
        <hr/>
        <simptag:SimpleTagModify>
            <h2>Content for simple tag repeat.</h2>
        </simptag:SimpleTagModify>
    </body>
</html>

啟動服務器之后侄非,訪問http://localhost:8080/SimpleTag.jsp蕉汪,效果如下。

運行效果

4.2.4 控制后續(xù)頁面元素是否執(zhí)行

如果doTag方法拋出SkipPageException異常逞怨,后續(xù)jsp頁面的內(nèi)容將不再執(zhí)行者疤。下面還是通過繼承SimpleTagSupport類來演示。
首先叠赦,寫一個自定義標簽處理類com.test.selfdefine.tag.SimpleTagSkipRest驹马。

package com.test.selfdefine.tag;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.SkipPageException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
import java.io.StringWriter;

/**
 * Created by chengxia on 2019/10/23.
 */
public class SimpleTagSkipRest extends SimpleTagSupport {
    /* 簡單標簽使用這個方法就可以完成所有的業(yè)務邏輯
    * @see javax.servlet.jsp.tagext.SimpleTagSupport#doTag()
    * 重寫doTag方法,這里拋出SkipPageException異常除秀,讓后續(xù)的頁面不再執(zhí)行糯累。
    */
    @Override
    public void doTag() throws JspException, IOException {
        //這里拋出SkipPageException異常,讓后續(xù)的jsp頁面不執(zhí)行
        throw new SkipPageException();
    }
}

在自定義標簽配置文件WEB-INF/simpletag.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用來添加對taglib(標簽庫)的描述 -->
    <description>SimpleTag Test</description>
    <tlib-version>1.0</tlib-version>
    <short-name>SimpleTagLibraryTest</short-name>
    <!--
       為自定義標簽庫設置一個uri,uri以/開頭暂吉,/后面的內(nèi)容隨便寫仗岸,如這里的/simpletag 允耿,
       在Jsp頁面中引用標簽庫時,需要通過uri找到標簽庫
       在Jsp頁面中就要這樣引入標簽庫:<%@taglib uri="/simpletag" prefix="simptag"%>
   -->
    <uri>/simpletag</uri>

    <!-- Invoke 'Generate' action to add tags or functions -->
    <!--一個taglib(標簽庫)中包含多個自定義標簽扒怖,每一個自定義標簽使用一個tag標記來描述  -->
    <!-- 一個tag標記對應一個自定義標簽 -->
    <tag>
        <description>SimpleTagTest1</description>
        <!--
            為標簽處理器類配一個標簽名较锡,在Jsp頁面中使用標簽時是通過標簽名來找到要調(diào)用的標簽處理器類的
            通過SimpleTag1就能找到對應的com.test.selfdefine.tag.SimpleTagTest1類
         -->
        <name>SimpleTag1</name>
        <!-- 標簽對應的處理器類-->
        <tag-class>com.test.selfdefine.tag.SimpleTagTest1</tag-class>
        <!--
        tld文件中有四種標簽體類型 :empty  JSP  scriptless  tagdepentend
            在簡單標簽(SampleTag)中標簽體body-content的值只允許是empty和scriptless,不允許設置成JSP,如果設置成JSP就會出現(xiàn)異常
            在傳統(tǒng)標簽中標簽體body-content的值只允許是empty和JSP
            如果標簽體body-content的值設置成tagdepentend盗痒,那么就表示標簽體里面的內(nèi)容是給標簽處理器類使用的蚂蕴,
            例如:開發(fā)一個查詢用戶的sql標簽,此時標簽體重的SQL語句就是給SQL標簽的標簽處理器來使用的
            <selftag:sql>SELECT * FROM USER</selftag:sql>
            在這種情況下俯邓,sql標簽的<body-content>就要設置成tagdepentend骡楼,tagdepentend用得比較少,了解一下即可
        -->
        <body-content>scriptless</body-content>
    </tag>

    <tag>
         <!-- 標簽名 -->
         <name>SimpleTagRepeat</name>
         <!-- 標簽處理器類-->
         <tag-class>com.test.selfdefine.tag.SimpleTagRepeat</tag-class>
         <!-- 標簽體允許的內(nèi)容 稽鞭,scriptless表示標簽體的內(nèi)容不允許是java腳本代碼-->
         <body-content>scriptless</body-content>
    </tag>

    <tag>
         <!-- 標簽名 -->
         <name>SimpleTagModify</name>
         <!-- 標簽處理器類-->
         <tag-class>com.test.selfdefine.tag.SimpleTagModify</tag-class>
         <!-- 標簽體允許的內(nèi)容 鸟整,scriptless表示標簽體的內(nèi)容不允許是java腳本代碼-->
         <body-content>scriptless</body-content>
    </tag>

    <tag>
         <!-- 標簽名 -->
         <name>SimpleTagSkipRest</name>
         <!-- 標簽處理器類-->
         <tag-class>com.test.selfdefine.tag.SimpleTagSkipRest</tag-class>
         <!-- 標簽體允許的內(nèi)容 ,scriptless表示標簽體的內(nèi)容不允許是java腳本代碼-->
         <body-content>scriptless</body-content>
    </tag>

</taglib>

在jsp頁面SimpleTag.jsp中朦蕴,添加這個自定義標簽篮条。

<%--
  Created by IntelliJ IDEA.
  User: chengxia
  Date: 2019/10/10
  Time: 8:43 AM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!-- 使用taglib指令引用simptag標簽庫,標簽庫的前綴(prefix)可以隨便設置吩抓,如這里設置成 prefix="simptag" -->
<%@ taglib prefix="simptag" uri="/simpletag" %>
<html>
    <head>
        <title>Self Define Tag Test</title>
    </head>
    <body>
        <h1>Ha Ha</h1>
        <hr/>
        <simptag:SimpleTag1>
            <h2>This is content for a simple tag.</h2>
        </simptag:SimpleTag1>
        <hr/>
        <simptag:SimpleTagSkipRest/>
        <hr/>
        <simptag:SimpleTagModify>
            <h2>Content for simple tag repeat.</h2>
        </simptag:SimpleTagModify>
    </body>
</html>

啟動tomcat服務器之后涉茧,訪問http://localhost:8080/SimpleTag.jsp,效果如下:

運行效果

這里可以看出疹娶,SimpleTagSkipRest后面的內(nèi)容都沒有被執(zhí)行伴栓。

4.3 開發(fā)帶屬性的自定義標簽

這里通過簡單標簽接口來演示如何開發(fā)帶屬性的自定義標簽。

4.3.1 JspFragment類介紹

javax.servlet.jsp.tagext.JspFragment類是在JSP2.0中定義的雨饺,它的實例對象代表JSP頁面中的一段符合JSP語法規(guī)范的JSP片段钳垮,這段JSP片段中不能包含JSP腳本元素。
WEB容器在處理簡單標簽的標簽體時额港,會把標簽體內(nèi)容用一個JspFragment對象表示扔枫,并調(diào)用標簽處理器對象的setJspBody方法把JspFragment對象傳遞給標簽處理器對象。JspFragment類中只定義了兩個方法锹安,如下所示:
(1) getJspContext方法
用于返回代表調(diào)用頁面的JspContext對象.
(2) invoke(java.io.Writer out)方法
用于執(zhí)行JspFragment對象所代表的JSP代碼片段,參數(shù)out用于指定將JspFragment對象的執(zhí)行結(jié)果寫入到哪個輸出流對象中倚舀,如果 傳遞給參數(shù)out的值為null叹哭,則將執(zhí)行結(jié)果寫入到JspContext.getOut()方法返回的輸出流對象中(也就是寫給瀏覽器)

4.3.2 invoke方法詳解

JspFragment.invoke方法是JspFragment最重要的方法,利用這個方法可以控制是否執(zhí)行和輸出標簽體的內(nèi)容痕貌、是否迭代執(zhí)行標簽體的內(nèi)容或?qū)撕烍w的執(zhí)行結(jié)果進行修改后再輸出风罩。例如:
(1) 在標簽處理器中如果沒有調(diào)用JspFragment.invoke方法,其結(jié)果就相當于忽略標簽體內(nèi)容舵稠;
(2) 在標簽處理器中重復調(diào)用JspFragment.invoke方法超升,則標簽體內(nèi)容將會被重復執(zhí)行入宦;
(3) 若想在標簽處理器中修改標簽體內(nèi)容,只需在調(diào)用invoke方法時指定一個可取出結(jié)果數(shù)據(jù)的輸出流對象(例如StringWriter)室琢,讓標簽體的執(zhí)行結(jié)果輸出到該輸出流對象中乾闰,然后從該輸出流對象中取出數(shù)據(jù)進行修改后再輸出到目標設備,即可達到修改標簽體的目的盈滴。

4.3.3 開發(fā)帶屬性自定義標簽的步驟

要想讓一個自定義標簽具有屬性涯肩,通常需要完成兩個步驟:
(1) 在標簽處理器中編寫每個屬性對應的setter方法。
(2) 在TLD文件中描術(shù)標簽的屬性巢钓。

為自定義標簽定義屬性時病苗,每個屬性都必須按照JavaBean的屬性命名方式,在標簽處理器中定義屬性名對應的setter方法症汹,用來接收 JSP頁面調(diào)用自定義標簽時傳遞進來的屬性值硫朦。如屬性url,在標簽處理器類中就要定義相應的setUrl(String url)方法背镇。
在標簽處理器中定義相應的set方法后咬展,JSP引擎在解析執(zhí)行開始標簽前,也就是調(diào)用doStartTag方法前芽世,會調(diào)用set屬性方法挚赊,為標簽設置屬性。

tld文件中用于描述標簽屬性的<attribute>元素說明济瓢,<tag>元素的<attribute>子元素用于描述自定義標簽的一個屬性荠割,自定義標簽所具有的每個屬性都要對應一個<attribute>元素。
<attribute>元素的子元素說明如下旺矾。

attribute元素的子元素說明

4.3.4 示例:添加屬性控制標簽體的執(zhí)行次數(shù)

首先蔑鹦,寫一個標簽處理類com.test.selfdefine.tag.SimpleTagCountAttribute,其中包含count屬性和對應的setCount函數(shù)箕宙。

package com.test.selfdefine.tag;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.SkipPageException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;

/**
 * Created by chengxia on 2019/10/23.
 */
public class SimpleTagCountAttribute extends SimpleTagSupport {

    //定義一個和標簽屬性相對應的成員變量
    private int count;

    //定義一個和標簽屬性相對應的setter方法
    public void setCount(int count){
        this.count = count;
    }
    /* 簡單標簽使用這個方法就可以完成所有的業(yè)務邏輯
    * @see javax.servlet.jsp.tagext.SimpleTagSupport#doTag()
    * 重寫doTag方法嚎朽,這里根據(jù)屬性count的值,確定標簽體的執(zhí)行次數(shù)柬帕。
    */
    @Override
    public void doTag() throws JspException, IOException {
        for(int i = 0; i < count; i++){
            this.getJspBody().invoke(null);
        }
    }
}

然后哟忍,在自定義標簽配置文件WEB-INF/simpletag.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用來添加對taglib(標簽庫)的描述 -->
    <description>SimpleTag Test</description>
    <tlib-version>1.0</tlib-version>
    <short-name>SimpleTagLibraryTest</short-name>
    <!--
       為自定義標簽庫設置一個uri锅很,uri以/開頭,/后面的內(nèi)容隨便寫凤跑,如這里的/simpletag 爆安,
       在Jsp頁面中引用標簽庫時,需要通過uri找到標簽庫
       在Jsp頁面中就要這樣引入標簽庫:<%@taglib uri="/simpletag" prefix="simptag"%>
   -->
    <uri>/simpletag</uri>

    <!-- Invoke 'Generate' action to add tags or functions -->
    <!--一個taglib(標簽庫)中包含多個自定義標簽仔引,每一個自定義標簽使用一個tag標記來描述  -->
    <!-- 一個tag標記對應一個自定義標簽 -->
    <tag>
        <description>SimpleTagTest1</description>
        <!--
            為標簽處理器類配一個標簽名扔仓,在Jsp頁面中使用標簽時是通過標簽名來找到要調(diào)用的標簽處理器類的
            通過SimpleTag1就能找到對應的com.test.selfdefine.tag.SimpleTagTest1類
         -->
        <name>SimpleTag1</name>
        <!-- 標簽對應的處理器類-->
        <tag-class>com.test.selfdefine.tag.SimpleTagTest1</tag-class>
        <!--
        tld文件中有四種標簽體類型 :empty  JSP  scriptless  tagdepentend
            在簡單標簽(SampleTag)中標簽體body-content的值只允許是empty和scriptless褐奥,不允許設置成JSP,如果設置成JSP就會出現(xiàn)異常
            在傳統(tǒng)標簽中標簽體body-content的值只允許是empty和JSP
            如果標簽體body-content的值設置成tagdepentend,那么就表示標簽體里面的內(nèi)容是給標簽處理器類使用的翘簇,
            例如:開發(fā)一個查詢用戶的sql標簽撬码,此時標簽體重的SQL語句就是給SQL標簽的標簽處理器來使用的
            <selftag:sql>SELECT * FROM USER</selftag:sql>
            在這種情況下,sql標簽的<body-content>就要設置成tagdepentend缘揪,tagdepentend用得比較少耍群,了解一下即可
        -->
        <body-content>scriptless</body-content>
    </tag>

    <tag>
         <!-- 標簽名 -->
         <name>SimpleTagRepeat</name>
         <!-- 標簽處理器類-->
         <tag-class>com.test.selfdefine.tag.SimpleTagRepeat</tag-class>
         <!-- 標簽體允許的內(nèi)容 ,scriptless表示標簽體的內(nèi)容不允許是java腳本代碼-->
         <body-content>scriptless</body-content>
    </tag>

    <tag>
         <!-- 標簽名 -->
         <name>SimpleTagModify</name>
         <!-- 標簽處理器類-->
         <tag-class>com.test.selfdefine.tag.SimpleTagModify</tag-class>
         <!-- 標簽體允許的內(nèi)容 找筝,scriptless表示標簽體的內(nèi)容不允許是java腳本代碼-->
         <body-content>scriptless</body-content>
    </tag>

    <tag>
         <!-- 標簽名 -->
         <name>SimpleTagSkipRest</name>
         <!-- 標簽處理器類-->
         <tag-class>com.test.selfdefine.tag.SimpleTagSkipRest</tag-class>
         <!-- 標簽體允許的內(nèi)容 蹈垢,scriptless表示標簽體的內(nèi)容不允許是java腳本代碼-->
        <body-content>scriptless</body-content>
    </tag>

    <tag>
         <!-- 標簽名 -->
         <name>SimpleTagCountAttribute</name>
         <!-- 標簽處理器類-->
         <tag-class>com.test.selfdefine.tag.SimpleTagCountAttribute</tag-class>
         <!-- 標簽體允許的內(nèi)容 ,scriptless表示標簽體的內(nèi)容不允許是java腳本代碼-->
         <body-content>scriptless</body-content>
        <!-- 標簽的屬性描述 -->
        <attribute>
            <description>描述標簽的count屬性</description>
            <name>count</name>
            <required>true</required>
            <!-- rtexprvalue用來指示標簽的屬性值是否可以是一個表達式袖裕,一般設置為true,true就表示允許標簽的屬性值可以是一個表達式-->
            <rtexprvalue>true</rtexprvalue>
        </attribute>
    </tag>

</taglib>

最后曹抬,在jsp頁面SimpleTag.jsp中,添加這個自定義標簽的使用急鳄。

<%--
  Created by IntelliJ IDEA.
  User: chengxia
  Date: 2019/10/10
  Time: 8:43 AM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!-- 使用taglib指令引用simptag標簽庫谤民,標簽庫的前綴(prefix)可以隨便設置,如這里設置成 prefix="simptag" -->
<%@ taglib prefix="simptag" uri="/simpletag" %>
<html>
    <head>
        <title>Self Define Tag Test</title>
    </head>
    <body>
        <h1>Ha Ha</h1>
        <hr/>
        <simptag:SimpleTag1>
            <h2>This is content for a simple tag.</h2>
        </simptag:SimpleTag1>
        <hr/>
        <simptag:SimpleTagCountAttribute count="2">
            <h2>Content for simple tag repeat.</h2>
        </simptag:SimpleTagCountAttribute>
    </body>
</html>

重啟tomcat服務器之后疾宏,訪問http://localhost:8080/SimpleTag.jsp张足,效果如下。

運行效果

注意:如果標簽的屬性值是8種基本數(shù)據(jù)類型坎藐,那么在JSP頁面在傳遞字符串時为牍,JSP引擎會自動轉(zhuǎn)換成相應的類型,但如果標簽的屬性值是復合數(shù)據(jù)類型岩馍,那么JSP引擎是無法自動轉(zhuǎn)換的碉咆。但如果標簽的屬性值是復合數(shù)據(jù)類型,那么JSP引擎是無法自動轉(zhuǎn)換的蛀恩。如果一定要給標簽的復合屬性賦值疫铜,那么可以采用表達式的方式給復合屬性賦值。如下:

<%
Date d = new Date();
request.setAttribute("date", d);
%>
<simptag:SimpleTagDateAttribute date="${date}"/>
<simptag:SimpleTagDateAttribute date="<%=new Date()%>"/>

注:jsp中${}是EL表達式的常規(guī)表示方式双谆。目的是為了獲取{}中指定的對象(參數(shù)刃永、對象等)的值微王。如${name}注祖,就是從當前頁面起開始搜尋name變量念恍,搜尋的范圍依次是:page、request趣避、session、application新翎,如果未搜索到程帕,即會返回null值住练。

5、標簽開發(fā)注意事項

在傳統(tǒng)標簽中標簽體body-content的值允許是empty愁拭、JSP讲逛、scriptless、tagdependent岭埠,body-content的值如果是設置成JSP盏混,那么表示該標簽是有標簽體的,并且標簽體的內(nèi)容可以是任意的惜论,包括java代碼许赃,如果是設置成scriptless,那么表示該標簽是有標簽體的馆类,但是標簽體的內(nèi)容不能是java代碼混聊。如果傳統(tǒng)標簽和簡單標簽的標簽體body-content的值設置成tagdependent,那么就表示標簽體里面的內(nèi)容是給標簽處理器類使用的乾巧。
在簡單標簽(SampleTag)中標簽體body-content的值只允許是empty句喜、scriptless、tagdependent沟于,不允許設置成JSP咳胃,否則會拋出異常:The TLD for the class com.test.selfdefine.tag.SimpleTagModify specifies an invalid body-content (JSP) for a SimpleTag.

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末旷太,一起剝皮案震驚了整個濱河市展懈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泳秀,老刑警劉巖标沪,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嗜傅,居然都是意外死亡金句,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門吕嘀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來违寞,“玉大人,你說我怎么就攤上這事偶房〕寐” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵棕洋,是天一觀的道長挡闰。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么摄悯? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任赞季,我火速辦了婚禮,結(jié)果婚禮上奢驯,老公的妹妹穿的比我還像新娘申钩。我一直安慰自己,他們只是感情好瘪阁,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布撒遣。 她就那樣靜靜地躺著,像睡著了一般管跺。 火紅的嫁衣襯著肌膚如雪义黎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天伙菜,我揣著相機與錄音轩缤,去河邊找鬼。 笑死贩绕,一個胖子當著我的面吹牛火的,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播淑倾,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼馏鹤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了娇哆?” 一聲冷哼從身側(cè)響起湃累,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎碍讨,沒想到半個月后治力,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡勃黍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年宵统,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片覆获。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡马澈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出弄息,到底是詐尸還是另有隱情痊班,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布摹量,位于F島的核電站涤伐,受9級特大地震影響馒胆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜凝果,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一国章、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧豆村,春花似錦、人聲如沸骂删。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宁玫。三九已至粗恢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間欧瘪,已是汗流浹背眷射。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留佛掖,地道東北人妖碉。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像芥被,于是被迫代替她去往敵國和親欧宜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容