Java Web开发中的自定义标签

1、创建工程

新建一个java web工程:


新建一个java web工程

选择工程存储位置,输入工厂名“JavaWebSenior”:


选择工程存储位置

工程结构如下:
工程结构

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

新建classes和lib目录

设置编译后class文件的输出路径,配置为前面创建的classes目录。File->Project Structure:


编译后class文件的输出路径

设置依赖jar包的存放位置,配置为前面创建的lib目录(选择Jar Directory):


设置依赖jar包的存放位置

添加tomcat服务器:


添加tomcat服务器1
添加tomcat服务器2

这里之前已经在IDEA中添加了Tomcat服务器,这里只需要选中之前添加的Tomcat服务器,并定义一个别名(JavaWebSeniorTomcat)即可:


定义一个别名(JavaWebSeniorTomcat)

然后,在上面的对话框中,将当前的工程添加到tomcat运行。点击加号,选择Artifact...

将当前的工程添加到tomcat运行

效果如下:
添加完成

然后,确定,即可看到当前项目已经添加到了tomcat运行。


当前项目已经添加到了tomcat运行

修改jsp代码,如下:


写一个jsp示例页面

这时候,启动tomcat:


启动tomcat

就会看到浏览器中自动打开http://localhost:8080,效果如下:

浏览器中自动打开http://localhost:8080

2、自定义标签入门

使用自定义标签,可以去掉jsp页面上的java代码。

2.1 入门示意

这里首先要将tomcat的lib目录引入到项目的依赖,不然的话,好多java web相关的没法使用。


将tomcat的lib目录引入到项目的依赖

首先,写一个Java类,实现自定义标签的内容。这里实现一个自定义标签,用来展现客户端的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异常后继续抛出
            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




    
    Try Selfdefine Tag
    
    1.0
    
    SelfdedineTagLibrary
    
    /tagtest

    
    
    
        用来输出客户端的IP地址
        
        viewIP
        
        com.test.selfdefine.tag.ShowIpAddressTag
        empty
    


到这里,我们就完成了一个自定义标签的定义。在后面的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 uri="/tagtest"  prefix="tagtest"%>

    
        Self Define Tag Test
    
    
        

Get IP Address using different ways:

你的IP地址是(使用java代码获取输出): <% //在jsp页面中使用java代码获取客户端IP地址 String ip = request.getRemoteAddr(); out.write(ip); %>
你的IP地址是(使用自定义标签获取输出): <%--使用自定义标签viewIP --%>

完成之后,工程的目录结构如下。


工程的目录结构

启动Tomcat,浏览器自动打开index.jsp

浏览器自动打开index.jsp

2.2 自定义标签的执行流程

我在MacOS上,用IDEA 2017进行开发,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 _jspx_dependants;

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

  private static final java.util.Set _jspx_imports_packages;

  private static final java.util.Set _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 getDependants() {
    return _jspx_dependants;
  }

  public java.util.Set getPackageImports() {
    return _jspx_imports_packages;
  }

  public java.util.Set 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("\n");
      out.write("\n");
      out.write("\n");
      out.write("    \n");
      out.write("\t    Self Define Tag Test\n");
      out.write("    \n");
      out.write("    \n");
      out.write("        

Get IP Address using different ways:

\n"); out.write(" 你的IP地址是(使用java代码获取输出):\n"); out.write(" "); //在jsp页面中使用java代码获取客户端IP地址 String ip = request.getRemoteAddr(); out.write(ip); out.write("\n"); out.write("
\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(" \n"); out.write("\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文件中的代码。当遇到自定义标签时,执行_jspx_meth_tagtest_005fviewIP_005f0方法。从这个方法中,我们就能看到自定义标签的执行流程。
JSP引擎遇到自定义标签时,首先创建标签处理器类的实例对象,然后按照JSP规范定义的通信规则依次调用它的方法。
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引擎实例化标签处理器后,将调用setPageContext方法将JSP页面的pageContext对象传递给标签处理器,标签处理器以后可以通过这个pageContext对象与JSP页面进行通信。

_jspx_th_tagtest_005fviewIP_005f0.setPageContext(_jspx_page_context);

2、public void setParent(Tag t),setPageContext方法执行完后,WEB容器接着调用的setParent方法将当前标签的父标签传递给当前标签处理器,如果当前标签没有父标签,则传递给setParent方法的参数值为null。

_jspx_th_tagtest_005fviewIP_005f0.setParent(null);

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

int _jspx_eval_tagtest_005fviewIP_005f0 = _jspx_th_tagtest_005fviewIP_005f0.doStartTag();

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

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

这里,需要注意_jspx_meth_tagtest_005fviewIP_005f0的执行结果如果为true,_jspService方法就直接返回了,后续的jsp代码不会被处理。这就是自定义标签中SKIP_PAGE的实现原理。
5、public void release(),通常WEB容器执行完自定义标签后,标签处理器会驻留在内存中,为其它请求服务器,直至停止web应用时,web容器才会调用release方法。

3、Java Web自定义标签的API

3.1 API概括

JspTag接口下,有两个子分支。一个分支是Tag接口,另一个分支是SimpleTag接口。前者是最初的自定义标签接口,我们称为传统的自定义标签api,后者是后来为了简化自定义标签的开发推出的新的api。如下图。


JspTag接口

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

3.2 传统标签API

3.2.1 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页面中位于自定义标签的结束标记后面的内容。

3.2.2 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接口将简化开发工作。

3.2.3 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接口将简化开发工作。

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

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


传统标签接口

3.3 简单标签接口API:SimpleTag

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

3.3.1 SimpleTag接口简介

SimpleTag接口与传统标签接口最大的区别在于,SimpleTag接口只定义了一个用于处理标签逻辑的doTag方法,该方法在WEB容器执行自定义标签时调用,并且只被调用一次。那些使用传统标签接口所完成的功能,例如是否执行标签体、迭代标签体、对标签体内容进行修改等功能都可以在doTag方法中完成。在doTag方法中可以抛出javax.servlet.jsp.SkipPageException异常,用于通知WEB容器不再执行JSP页面中位于结束标记后面的内容,这等效于在传统标签的doEndTag方法中返回Tag.SKIP_PAGE常量的情况。
除了doTag方法方法,SimpleTag接口还定义了如下四个方法:

  • setJspContext方法
    用于把JSP页面的pageContext对象传递给标签处理器对象
  • setParent方法
    用于把父标签处理器对象传递给当前标签处理器对象
  • getParent方法
    用于获得当前标签的父标签处理器对象
  • setJspBody方法
    用于把代表标签体的JspFragment对象传递给标签处理器对象

在JSP API中也提供了SimpleTag接口的默认实现类SimpleTagSupport,我们在编写简单标签时,可以继承和扩展SimpleTagSupport类,这相比实现SimpleTag接口将简化开发工作。这是推荐的做法,只有当默认的实现类不满足需求才考虑根据具体的逻辑需求覆盖重写。

3.3.2 SimpleTag接口方法的执行顺序

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

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

4、自定义标签API代码示例

4.1 传统标签API实例代码

前面入门的例子中,我们是通过直接实现Tag接口来开发的自定义标签。SUN公司针对tag接口提供了一个默认的实现类TagSupport,TagSupport类中实现了tag接口的所有方法,因此,这里我们可以编写一个类继承TagSupport类来开发演示传统标签API。

4.1.1 带标签体的展示显示ip标签

这里,我们重新实现一遍前面的显示ip地址标签。加入标签体支持,在标签体中,我们允许配置ip地址获得说明。
首先,在WEB-INF/selfdefine.tld中添加标签定义:




    
    Try Selfdefine Tag
    
    1.0
    
    SelfdedineTagLibrary
    
    /tagtest

    
    
    
        用来输出客户端的IP地址
        
        viewIP
        
        com.test.selfdefine.tag.ShowIpAddressTag
        empty
    
    
        用来输出客户端的IP地址
        viewIPWithBody
        
        com.test.selfdefine.tag.ShowIpAddressWithBodyTag
        scriptless
    


注意这里的body-content配置了不同的值,含义如下:
(1) empty:空标记,即起始标记和结束标记之间没有内容。



(2) scriptless:接受文本、EL和JSP动作,但不能含有脚本元素。

This is message body

(3) JSP:体包含JSP元素,如EL表达式,标准或定制动作以及脚本元素。


<%=request.getProtocol()%>

(4) tagdependent:体中可以包含看似为JSP元素的内容,但是容器不对其进行计算(当体中的内容与JSP元素产生混淆时采用这个,较少用)

  
     ${p.name }:${p.age} 

然后,编写一个继承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异常后继续抛出
            throw new RuntimeException(e);
        }
        return Tag.EVAL_BODY_INCLUDE;
    }
}

注意,这里为了标签体中的内容能够被处理,doStartTag()方法返回的是Tag.EVAL_BODY_INCLUDE
最后是标签调用。
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 uri="/tagtest"  prefix="tagtest"%>

    
        Self Define Tag Test
    
    
        

Get IP Address using different ways:

你的IP地址是(使用java代码获取输出): <% //在jsp页面中使用java代码获取客户端IP地址 String ip = request.getRemoteAddr(); out.write(ip); %>
你的IP地址是(使用自定义标签获取输出): <%--使用自定义标签viewIP --%>
你的IP地址是(使用自定义标签获取输出,带标签体): <%--使用自定义标签viewIP --%> (Ip Address got by self define tag.)

启动服务器之后,运行效果如下:


运行效果

如果想让自定义标签体中的提示信息出现在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异常后继续抛出
            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异常后继续抛出
            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异常后继续抛出
            throw new RuntimeException(e);
        }
        return Tag.SKIP_PAGE;
    }
}

效果如下:


运行效果

可以看到尽管显式没有区别,但是,html文件最后的闭合标签都是缺失的。原因就是自定义标签之后的内容,都被doEndTag()函数返回Tag.SKIP_PAGE之后略过不处理了。

4.1.2 控制jsp页面内容重复执行

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

首先定义一个自定义标签处理类。
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服务器又执行一次标签体,
      * 依次类推,一直执行到doAfterBody方法返回SKIP_BODY,则标签体才不会重复执行。
      * @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




    
    Try Selfdefine Tag
    
    1.0
    
    SelfdedineTagLibrary
    
    /tagtest

    
    
    
        用来输出客户端的IP地址
        
        viewIP
        
        com.test.selfdefine.tag.ShowIpAddressTag
        empty
    
    
        用来输出客户端的IP地址
        viewIPWithBody
        
        com.test.selfdefine.tag.ShowIpAddressWithBodyTag
        scriptless
    
    
        用来输出客户端的IP地址
        RepeatBodyTag
        
        com.test.selfdefine.tag.RepeatBodyTag
        JSP
    


写一个jsp文件,测试刚刚创建的自定义标签:
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 uri="/tagtest"  prefix="tagtest"%>

    
        Self Define Tag Test
    
    
        
            

This is content for repeat tag.

启动服务器,第一次访问http://localhost:8080/test.jsp

第一次访问

第二次访问:


第二次访问

至于原因,从前面的自定义标签执行流程不难理解。自定义标签处理类,只实例化一次,第一次之后,x的值就变成0了。所以,第一次之后,后面只会对该自定义标签的标签体渲染一次。

4.1.3 修改jsp页面内容输出

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

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

同样,也是,先定义一个自定义标签处理类。作为演示,其作用是将标签体中的内容全部转化为大写。
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();
        //修改标签体里面的内容,将标签体的内容转换成大写
        String result = content.toUpperCase();
        try {
            //输出修改后的内容
            this.pageContext.getOut().write(result);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return Tag.EVAL_PAGE;
    }
}

在自定义标签配置文件中,添加自定义标签配置。
WEB-INF/selfdefine.tld




    
    Try Selfdefine Tag
    
    1.0
    
    SelfdedineTagLibrary
    
    /tagtest

    
    
    
        用来输出客户端的IP地址
        
        viewIP
        
        com.test.selfdefine.tag.ShowIpAddressTag
        empty
    
    
        用来输出客户端的IP地址
        viewIPWithBody
        
        com.test.selfdefine.tag.ShowIpAddressWithBodyTag
        scriptless
    
    
        用来输出客户端的IP地址
        RepeatBodyTag
        
        com.test.selfdefine.tag.RepeatBodyTag
        JSP
    
    
        用来输出客户端的IP地址
        ModBodyContentTag
        
        com.test.selfdefine.tag.ModBodyContentTag
        JSP
    


最后,写一个测试页面来测试这个自定义标签。
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 uri="/tagtest"  prefix="tagtest"%>

    
        Self Define Tag Test
    
    
        
            

This is content for repeat tag.


This is content to be modified.

重启tomcat,访问http://localhost:8080/test.jsp,效果如下。

运行效果

4.2 简单标签API示例代码

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

4.2.1 控制jsp中自定义标签体中内容是否执行

这里,我们编写一个类继承SimpleTagSupport,然后再重写doTag方法,在doTag方法里面不调用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 {
    /* 简单标签使用这个方法就可以完成所有的业务逻辑
    * @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 Test
    1.0
    SimpleTagLibraryTest
    
    /simpletag

    
    
    
    
        SimpleTagTest1
        
        SimpleTag1
        
        com.test.selfdefine.tag.SimpleTagTest1
        
        scriptless
    


最后,写一个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 prefix="simptag" uri="/simpletag" %>

    
        Self Define Tag Test
    
    
        

Ha Ha


This is content for a simple tag.

这样,启动服务器,访问http://localhost:8080/SimpleTag.jsp,效果如下。

运行效果1

如果注掉标签处理类中的jspFragment.invoke(null);,再重启服务器,访问http://localhost:8080/SimpleTag.jsp,效果如下。

运行效果2

这样,就是实现了通过SimpleTag接口来控制标签体是否输出到页面。

4.2.2 控制自定义标签的标签体内容重复执行

这里,同样也是编写一个类继承SimpleTagSupport,然后再重写doTag方法,在doTag方法里面重复调用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 {
    /* 简单标签使用这个方法就可以完成所有的业务逻辑
    * @see javax.servlet.jsp.tagext.SimpleTagSupport#doTag()
    * 重写doTag方法,控制标签体是否重复执行,这里重复输出3次标签体。
    */
    @Override
    public void doTag() throws JspException, IOException {
        // 得到代表jsp标签体的JspFragment
         JspFragment jspFragment = this.getJspBody();
         for (int i = 0; i < 3; i++) {
             // 将标签体的内容输出到浏览器
             jspFragment.invoke(null);
         }
    }
}

在标签配置文件WEB-INF/simpletag.tld中添加这一个自定义标签配置。





    
    SimpleTag Test
    1.0
    SimpleTagLibraryTest
    
    /simpletag

    
    
    
    
        SimpleTagTest1
        
        SimpleTag1
        
        com.test.selfdefine.tag.SimpleTagTest1
        
        scriptless
    

    
         
         SimpleTagRepeat
         
         com.test.selfdefine.tag.SimpleTagRepeat
         
         scriptless
    


写一个测试页面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 prefix="simptag" uri="/simpletag" %>

    
        Self Define Tag Test
    
    
        

Ha Ha


This is content for a simple tag.


Content for simple tag repeat.

启动服务器之后,访问http://localhost:8080/SimpleTag.jsp,效果如下。

运行效果

4.2.3 修改标签体的输出内容

编写一个类继承SimpleTagSupport,然后再重写doTag方法,在doTag方法调用jspFrament.invoke方法时,让执行结果写一个自定义的缓冲中即可,然后开发人员可以取出缓冲的数据修改输出。
标签处理类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 {
    /* 简单标签使用这个方法就可以完成所有的业务逻辑
    * @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中添加该自定义标签的配置。





    
    SimpleTag Test
    1.0
    SimpleTagLibraryTest
    
    /simpletag

    
    
    
    
        SimpleTagTest1
        
        SimpleTag1
        
        com.test.selfdefine.tag.SimpleTagTest1
        
        scriptless
    

    
         
         SimpleTagRepeat
         
         com.test.selfdefine.tag.SimpleTagRepeat
         
         scriptless
    

    
         
         SimpleTagModify
         
         com.test.selfdefine.tag.SimpleTagModify
         
         scriptless
    


写一个页面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 prefix="simptag" uri="/simpletag" %>

    
        Self Define Tag Test
    
    
        

Ha Ha


This is content for a simple tag.


Content for simple tag repeat.

启动服务器之后,访问http://localhost:8080/SimpleTag.jsp,效果如下。

运行效果

4.2.4 控制后续页面元素是否执行

如果doTag方法抛出SkipPageException异常,后续jsp页面的内容将不再执行。下面还是通过继承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 {
    /* 简单标签使用这个方法就可以完成所有的业务逻辑
    * @see javax.servlet.jsp.tagext.SimpleTagSupport#doTag()
    * 重写doTag方法,这里抛出SkipPageException异常,让后续的页面不再执行。
    */
    @Override
    public void doTag() throws JspException, IOException {
        //这里抛出SkipPageException异常,让后续的jsp页面不执行
        throw new SkipPageException();
    }
}

在自定义标签配置文件WEB-INF/simpletag.tld中,添加这个标签的定义。





    
    SimpleTag Test
    1.0
    SimpleTagLibraryTest
    
    /simpletag

    
    
    
    
        SimpleTagTest1
        
        SimpleTag1
        
        com.test.selfdefine.tag.SimpleTagTest1
        
        scriptless
    

    
         
         SimpleTagRepeat
         
         com.test.selfdefine.tag.SimpleTagRepeat
         
         scriptless
    

    
         
         SimpleTagModify
         
         com.test.selfdefine.tag.SimpleTagModify
         
         scriptless
    

    
         
         SimpleTagSkipRest
         
         com.test.selfdefine.tag.SimpleTagSkipRest
         
         scriptless
    


在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 prefix="simptag" uri="/simpletag" %>

    
        Self Define Tag Test
    
    
        

Ha Ha


This is content for a simple tag.



Content for simple tag repeat.

启动tomcat服务器之后,访问http://localhost:8080/SimpleTag.jsp,效果如下:

运行效果

这里可以看出,SimpleTagSkipRest后面的内容都没有被执行。

4.3 开发带属性的自定义标签

这里通过简单标签接口来演示如何开发带属性的自定义标签。

4.3.1 JspFragment类介绍

javax.servlet.jsp.tagext.JspFragment类是在JSP2.0中定义的,它的实例对象代表JSP页面中的一段符合JSP语法规范的JSP片段,这段JSP片段中不能包含JSP脚本元素。
WEB容器在处理简单标签的标签体时,会把标签体内容用一个JspFragment对象表示,并调用标签处理器对象的setJspBody方法把JspFragment对象传递给标签处理器对象。JspFragment类中只定义了两个方法,如下所示:
(1) getJspContext方法
用于返回代表调用页面的JspContext对象.
(2) invoke(java.io.Writer out)方法
用于执行JspFragment对象所代表的JSP代码片段,参数out用于指定将JspFragment对象的执行结果写入到哪个输出流对象中,如果 传递给参数out的值为null,则将执行结果写入到JspContext.getOut()方法返回的输出流对象中(也就是写给浏览器)

4.3.2 invoke方法详解

JspFragment.invoke方法是JspFragment最重要的方法,利用这个方法可以控制是否执行和输出标签体的内容、是否迭代执行标签体的内容或对标签体的执行结果进行修改后再输出。例如:
(1) 在标签处理器中如果没有调用JspFragment.invoke方法,其结果就相当于忽略标签体内容;
(2) 在标签处理器中重复调用JspFragment.invoke方法,则标签体内容将会被重复执行;
(3) 若想在标签处理器中修改标签体内容,只需在调用invoke方法时指定一个可取出结果数据的输出流对象(例如StringWriter),让标签体的执行结果输出到该输出流对象中,然后从该输出流对象中取出数据进行修改后再输出到目标设备,即可达到修改标签体的目的。

4.3.3 开发带属性自定义标签的步骤

要想让一个自定义标签具有属性,通常需要完成两个步骤:
(1) 在标签处理器中编写每个属性对应的setter方法。
(2) 在TLD文件中描术标签的属性。

为自定义标签定义属性时,每个属性都必须按照JavaBean的属性命名方式,在标签处理器中定义属性名对应的setter方法,用来接收 JSP页面调用自定义标签时传递进来的属性值。如属性url,在标签处理器类中就要定义相应的setUrl(String url)方法。
在标签处理器中定义相应的set方法后,JSP引擎在解析执行开始标签前,也就是调用doStartTag方法前,会调用set属性方法,为标签设置属性。

tld文件中用于描述标签属性的元素说明,元素的子元素用于描述自定义标签的一个属性,自定义标签所具有的每个属性都要对应一个元素。
元素的子元素说明如下。

attribute元素的子元素说明

4.3.4 示例:添加属性控制标签体的执行次数

首先,写一个标签处理类com.test.selfdefine.tag.SimpleTagCountAttribute,其中包含count属性和对应的setCount函数。

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;
    }
    /* 简单标签使用这个方法就可以完成所有的业务逻辑
    * @see javax.servlet.jsp.tagext.SimpleTagSupport#doTag()
    * 重写doTag方法,这里根据属性count的值,确定标签体的执行次数。
    */
    @Override
    public void doTag() throws JspException, IOException {
        for(int i = 0; i < count; i++){
            this.getJspBody().invoke(null);
        }
    }
}

然后,在自定义标签配置文件WEB-INF/simpletag.tld中,添加这个自定义标签的定义。





    
    SimpleTag Test
    1.0
    SimpleTagLibraryTest
    
    /simpletag

    
    
    
    
        SimpleTagTest1
        
        SimpleTag1
        
        com.test.selfdefine.tag.SimpleTagTest1
        
        scriptless
    

    
         
         SimpleTagRepeat
         
         com.test.selfdefine.tag.SimpleTagRepeat
         
         scriptless
    

    
         
         SimpleTagModify
         
         com.test.selfdefine.tag.SimpleTagModify
         
         scriptless
    

    
         
         SimpleTagSkipRest
         
         com.test.selfdefine.tag.SimpleTagSkipRest
         
        scriptless
    

    
         
         SimpleTagCountAttribute
         
         com.test.selfdefine.tag.SimpleTagCountAttribute
         
         scriptless
        
        
            描述标签的count属性
            count
            true
            
            true
        
    


最后,在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 prefix="simptag" uri="/simpletag" %>

    
        Self Define Tag Test
    
    
        

Ha Ha


This is content for a simple tag.


Content for simple tag repeat.

重启tomcat服务器之后,访问http://localhost:8080/SimpleTag.jsp,效果如下。

运行效果

注意:如果标签的属性值是8种基本数据类型,那么在JSP页面在传递字符串时,JSP引擎会自动转换成相应的类型,但如果标签的属性值是复合数据类型,那么JSP引擎是无法自动转换的。但如果标签的属性值是复合数据类型,那么JSP引擎是无法自动转换的。如果一定要给标签的复合属性赋值,那么可以采用表达式的方式给复合属性赋值。如下:

<%
Date d = new Date();
request.setAttribute("date", d);
%>


注:jsp中${}是EL表达式的常规表示方式。目的是为了获取{}中指定的对象(参数、对象等)的值。如${name},就是从当前页面起开始搜寻name变量,搜寻的范围依次是:page、request、session、application,如果未搜索到,即会返回null值。

5、标签开发注意事项

在传统标签中标签体body-content的值允许是empty、JSP、scriptless、tagdependent,body-content的值如果是设置成JSP,那么表示该标签是有标签体的,并且标签体的内容可以是任意的,包括java代码,如果是设置成scriptless,那么表示该标签是有标签体的,但是标签体的内容不能是java代码。如果传统标签和简单标签的标签体body-content的值设置成tagdependent,那么就表示标签体里面的内容是给标签处理器类使用的。
在简单标签(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.

参考资料

  • javaweb学习总结(二十三)——jsp自定义标签开发入门
  • javaweb学习总结(二十四)——jsp传统标签开发
  • jsp自定义标签:body-content取值的含义
  • javaweb学习总结(二十五)——jsp简单标签开发(一)
  • javaweb学习总结(二十六)——jsp简单标签标签库开发(二)
  • jsp中${}的意思

你可能感兴趣的:(Java Web开发中的自定义标签)