Java Server Pages(JSP)——8. 自定义标签

写在前面

这一章学习如何在JSTL中使用自定义标签。在JSP2.0中,增加了两个特性, 用于改善自定义标签实现。 第一个特性是一个接口——SimpleTag, 另一个特性是标签文件中定义标签的机制。

自定义标签的实现, 叫作标签处理器, 而简单标签处理器是指继承SimpleTag实现的标签处理器。

简单标签处理器

简单标签处理器有着简单的生命周期, 而且比经典标签处理器更加易于实现。 SimpleTag接口中用于标签触发的方法只有一个——doTag, 并且该方法只会执行一次。 业务逻辑、 遍历及页面内容操作都在这里实现。简单标签处理器中的页面内容都在JspFragment类的实例中体现。

简单标签的生命周期如下:

  • JSP容器通过简单标签处理器的无参数构造器创建它的实例。 因此, 简单标签处理器必需有无参数构造器。
  • JSP容器通过setJspContext的方法, 传入JspContext对象: 该对象中最重要的方法是getOut, 它能返回JspWriter, 通过JspWriter就可以把响应返回前端了。 setJspContext方法的定义如下:
public void setJspContext(JspContext jspContext){}

通常情况下, 都需要把使用传入的JspContext指定为类的成员变量以便后继使用:

  • 如果自定义标签被另一个自定义标签所嵌套, JSP容器就会调用setParent的方法, 该方法的定义如下:
public void setParent(JspTag parent){}
  • JSP容器调用该标签中所定义的每个属性的Set方法。
  • 如果需要处理页面内容, JSP容器还会调用SimpleTag接口的setJspBody方法, 把使用JspFragment封装的页面内容传过来。 当然, 如果没有页面内容, 那么JSP容器就不会调用该方法。

javax.servlet.jsp.tagext包中也包含一个SimpleTag的基础类: SimpleTagSupport。 SimpleTagSupport提供了SimpleTag所有方法的默认实现, 并便于扩展实现简单标签处理器。 在SimpleTagSupport类中用getJspContext方法返回JspContext实例, 这个实例在JSP容器调用SimpleTag的setJspContext方法时传入。

Simaple Tag示例

自定义标签需要有两个步骤: 编写标签处理器及注册标签。注意在构建标签处理器时, 需要在构建目录中有Servlet API及JSP API。 如果使用Tomcat, 可以在Tomcat的lib目录下找到包含这两个API的包(即servletapi.jar、 jsp-api.jar这两个件)。

自定义标签由组件处理器(在WEB-INF/classes目录中)及标签描述器(WEB-INF目录中的mytags.tld文文件)组成。

1.编写标签处理器
在项目src文件夹中添加静态方法,MyFirstTag类代码如下:

package customtag;

import java.io.IOException;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.JspTag;
import javax.servlet.jsp.tagext.SimpleTag;

public class MyFirstTag implements SimpleTag {
    JspContext jspContext;
    public void doTag() throws IOException, JspException {
        System.out.println("doTag");
        jspContext.getOut().print("This is my first tag.");
    } 
    public void setParent(JspTag parent) {
        System.out.println("setParent");
    } 
    public JspTag getParent() {
        System.out.println("getParent");
        return null;
    } 
    public void setJspContext(JspContext jspContext) {
        System.out.println("setJspContext");
        this.jspContext = jspContext;
    } 
    public void setJspBody(JspFragment body) {
        System.out.println("setJspBody");
    }
}

MySimpleTag类中有一个名为jspContext的JspContext类型变量。 在setJspContext方法中, 将由JSP容器中传入的JspContext对象赋给该变量。 在doTag方法中, 通过JspContext对象获取JspWriter对象实例。 然后用JspWriter方法中的print方法输出“This is my firsttag”的字符串。

2.注册标签
在标签处理器能够被JSP页面使用之前, 它需要在标签库描述器中注册一下, 这个描述器是以.tld结尾的XML文件。 本例标签库描述是一个名为mytags.tld的文件。 这个文件必须放在WEB-INF目录下。

<?xml version="1.0" encoding="UTF-8"?>
<j2ee:taglib xmlns="http://java.sun.com/xml/ns/j2ee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_1.xsd"
        version="2.1">
    <j2ee:description>Simple Tag Examples</j2ee:description>
      <j2ee:tlib-version>1.0</j2ee:tlib-version>
      <j2ee:short-name>My_First_Taglib_Exmaple</j2ee:short-name>

    <j2ee:tag>
        <j2ee:name>firstTag</j2ee:name>
        <j2ee:tag-class>app06a.customtag.MyFirstTag</j2ee:tag-class>
        <j2ee:body-content>empty</j2ee:body-content>
    </j2ee:tag>
</j2ee:taglib>

在标签描述文件中最主要的节点是tag, 它用于定义一个标签。 它可以包含一个name节点及一个tag-class的节点。 name节点用于说明这个标签的名称; tag-class则用于指出标签处理器的完整类名。 一个标签库描述器中可以定义多个标签。这一点我将在后面在同样文件中再定义新的tag。

此外, 在标签描述器中还有其他节点。 description节点用于说明这个描述器中的所有标签。 tlib-version节点用于指定自定义标签的版本。 short-name节点则是这些标签的名称。

3.使用标签
要使用自定义标签, 就要用到taglib指令。 taglib指令中的uri属性是标签描述器的绝对路径或者相对路径。 本例中使用相对路径。 但是, 如果使用的是jar包中的标签库, 就必须要使用绝对路径了。

用来测试编写的tag的JSP页面如下所示:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/tld/mytags.tld" prefix="easy"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title>Testing my first tag</title>
</head>
    <body>
        Hello!!!!
        <br/>
    <easy:firstTag></easy:firstTag>
    </body>
</html>

同样通过URL可以调用该JSP页面,效果如下显示:
这里写图片描述

一旦访问firtTagTest.jsp页面, JSP容器就会调用标签处理器中的setJspContext方法。 由于firstTagTest.jsp中的标签没有内容, 因此JSP容器也就不会在调用doTag方法前调用setJspBody的方法。 在Eclipse控制台(C)就将会得到如下内容:
这里写图片描述

注意, JSP容器并没有调用标签处理器的setParent方法, 因为这个简单标签并没有被另一个标签给嵌套。

处理属性

1.编写标签处理器
实现SimpleTag接口或者扩展SimpleTagSupporta的标签处理器都可以有属性。 下面将通过另一个例子来实现给SimpleTag标签添加属性的方法。

DataFormaterTag的标签处理器可以将逗号分隔内容转换成HTML表格。 这个标签有两个属性: header、items。 header属性值将会转成表头。

还是一样,首先定义Java静态方法,DataFormatterTag类方法代码如下:

package customtag;

import java.io.IOException;
import java.util.StringTokenizer;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class DataFormatterTag extends SimpleTagSupport {
    private String header;
    private String items;

    public void setHeader(String header) {
        this.header = header;
    }
    public void setItems(String items) {
        this.items = items;
    } 
    public void doTag() throws IOException, JspException {
        JspContext jspContext = getJspContext();
        JspWriter out = jspContext.getOut();
        out.print("<table style='border:1px solid green'>\n"
                + "<tr><td><span style='font-weight:bold'>"
                + header + "</span></td></tr>\n");
        StringTokenizer tokenizer = new StringTokenizer(items, ",");
        while (tokenizer.hasMoreTokens()) {
            String token = tokenizer.nextToken();
            out.print("<tr><td>" + token + "</td></tr>\n");
        }
        out.print("</table>");
    }
}

DataFormatterTag类有两个Set方法用于接收属性:setHeader、 setItems。 doTag方法中则实现了其余的内容。

doTag方法中, 首先通过getJspContext方法获取通过JSP容器传入的JSPContext对象:

JspContext jspContext = getJspContext();

接着, 通过JspContext实例中的getOut方法获取JspWriter对象, 它能将响应写回客户端:

JspWriter out = jspContext.getOut();

2.注册标签
注册dataFormatter标签,在tld文件中注册:

<j2ee:taglib>
  <j2ee:tag>
    <j2ee:name>dataFormatter</j2ee:name>
    <j2ee:tag-class>app06a.customtag.DataFormatterTag</j2ee:tag-class>
    <j2ee:body-content>empty</j2ee:body-content>
    <j2ee:attribute>
      <j2ee:name>header</j2ee:name>
      <j2ee:required>true</j2ee:required>
    </j2ee:attribute>
    <j2ee:attribute>
      <j2ee:name>items</j2ee:name>
      <j2ee:required>true</j2ee:required>
    </j2ee:attribute>
  </j2ee:tag>
</j2ee:taglib>

3.使用标签
在完成标签注册之后,可以使用dataFormatterTagTest.jsp页面来测试这个标签处理器了。

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/tld/mytags.tld" prefix="easy"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title>Testing DataFormatterTag</title>
</head>
<body>
    <easy:dataFormatter header="States" items="Alabama,Alaska,Georgia,Florida"/>
    <br/>
    <easy:dataFormatter header="Countries">
        <jsp:attribute name="items">
            US,UK,Canada,Korea
        </jsp:attribute>
    </easy:dataFormatter>
    <br/>
</body>
</html>

dataFormatter标签两次, 每次都使用不同的两种方式:一种是标签属性, 另一种是标准属性。

可以使用URL来访问dataFormatterTagTest.jsp,效果如下:
这里写图片描述

访问标签内容

在SimpleTag中, 可以通过JSP容器传入的JspFragment来访问标签内容。 JspFragment类提供了多次访问JSP中这部分代码的能力。 JSP片段的定义不能包含脚本或者脚本表达式, 它只能是文件模板或者JSP标准节点。

JspFragment类中有两个方法: getJspContext、invoke。 我们的定义如下:

public abstract JspContext getJspContext()
public abstract void invoke(java.io.Writer writer) throws JspException, java.io.IOException

getJspContext方法返回这个JspFragment关联的JspContext对象。 可以通过invoke方法来执行这个片段(标签的内容) , 然后通过指定的Writer对象把它直接输出。 如果把null传入invoke方法中, 那么这个Writer将会被JspFragment所关联的JspContext对家中的getOut方法返回的JspWriter方法所接管。

1.编写标签处理器
下面同样是通过一个例子来实现简单标签访问内容的功能实现。下面实现SelectElementTag方法,该方法可以输出如下格式的HTML select节点:

<select>
    <option value="value-1">text-1</option>
    <option value="value-2">text-2</option>
    ...
    <option value="value-n">text-n</option>
</select>

方法代码如下:

package customtag;
import java.io.IOException;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class SelectElementTag extends SimpleTagSupport { 
    private String[] countries = {"Australia", "Brazil", "China" };
    public void doTag() throws IOException, JspException {
        JspContext jspContext = getJspContext();
        JspWriter out = jspContext.getOut();
        out.print("<select>\n");
        for (int i=0; i<3; i++) {
            getJspContext().setAttribute("value", countries[i]);
            getJspContext().setAttribute("text", countries[i]);
            getJspBody().invoke(null);
        }
        out.print("</select>\n");
    }
}

2.注册标签
跟上面类似,在tld文件中注册自定义节点:

<j2ee:taglib>
  <j2ee:tag>
    <j2ee:name>select</j2ee:name>
    <j2ee:tag-class>app06a.customtag.SelectElementTag</j2ee:tag-class>
    <j2ee:body-content>scriptless</j2ee:body-content>
  </j2ee:tag>
</j2ee:taglib>

3.使用标签
下面是使用SelectElementTag节点的JSP页面,代码如下:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/tld/mytags.tld" prefix="easy"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <title>Testing SelectElementFormatterTag</title>
    </head>
<body>
    <easy:select>
        <option value="${value}">${text}</option>
    </easy:select>
</body>
</html>

同样通过URL调用该JSP页面,可以获得如下效果:
这里写图片描述

在Web浏览器中查看网页源代码会发现如下代码:
这里写图片描述

自定义EL函数

这里我们可以通过自定义实现表达式语言触发的函数,即自定义EL函数。

一般来说, 编写EL函数需要以下两个步骤:

  1. 创建一个包含静态方法的public类。 每个类的静态方法表示一个EL函数。 这个类可以不需要实现任何接口或者继承特定的类。 可以像发布其他任何类一样发布这个类。 这个类必须放在应用中的/WEBINF/classes目录或者它的子目录下。
  2. 用function节点在标签库描述器中注册这个函数。

function节点是taglib节点的下级节点, 它有如下子节点:

  • description: 可选, 标签说明。
  • display-name: 在XML工具中显示的缩写名字。
  • icon: 可选, 在XML工具中使用的icon节点。
  • name: 函数的唯一名字。
  • function-class: 该函数对应实现的Java类的全名。
  • function-signature: 该函数对应实现的Java静态方法。
  • example: 可选, 使用该函数的示例说明。
  • function-extension: 可以是一个或者多个节点, 在XML工具中使用, 用于提供该函数的更多的细节。

要使用这个函数, 须将taglib指令中的uri属性指向标签库描述, 并指明使用的前缀。 然后在JSP页面中使用如下语法来访问该函数:

${prefix:functionName(parameterList)}

1.编写EL函数对应Java静态方法
MyFunctions类, 封装了一个静态方法reverseString,也就是对字符串进行反序。代码如下:

package function;
public class StringFunctions {
    public static String reverseString(String s) {
        return new StringBuffer(s).reverse().toString();
    }
}

2.注册EL函数
重新编写functiontags.tld文件, 它包含描述了函数名为reverseString的function节点。 这个TLD文件必须要保存在应用的WEB-INF目录下才会生效。代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<j2ee:taglib version="2.0" 
    xmlns:j2ee="http://java.sun.com/xml/ns/j2ee" 
    xmlns:xml="http://www.w3.org/XML/1998/namespace" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://xmlns.jcp.org/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd ">

  <j2ee:description>Function Tag Examples</j2ee:description>
  <j2ee:tlib-version>1.0</j2ee:tlib-version>
  <j2ee:short-name>Func</j2ee:short-name>
  <j2ee:uri>/WEB-INF/tld/functiontags.tld</j2ee:uri>

  <j2ee:function>
    <j2ee:description>Reverses a String</j2ee:description>
    <j2ee:name>reverseString</j2ee:name>
    <j2ee:function-class>app06a.function.StringFunctions</j2ee:function-class>
    <j2ee:function-signature>
      java.lang.String reverseString(java.lang.String)
    </j2ee:function-signature>
  </j2ee:function>
</j2ee:taglib>

3.测试自定义EL函数
下面给出了测试EL函数的reverseStringFunctionTest.jsp页面代码。

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="func" uri="/WEB-INF/tld/functiontags.tld" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title>Testing reverseString function</title>
</head>
    <body>
        ${func:reverseString("Welcome! Yidadada!")}
    </body>
</html>

同样可以用URL路径访问该JSP页面。效果如下:
这里写图片描述

4.额外的BUG
如上所示,代码是能正常运行的,但是Eclipse还是出问题,显示:The function func:reverseString is undefined,就是在EL函数那里标红线。尝试额外配置web.xml文件也不能行。目前还没有找到解决方案。当然目前也不清楚这是不是Eclipse的BUG,所以存疑。

发布自定义标签

可以把自定义的标签处理器以及标签描述器打包到JAR包里, 这样就可以把它发布出来给别人使用了, 就像JSTL一样。 这种情况下, 需要包含其所有的标签处理器及描述它们的TLD文件。 此外, 还需要在描述器中的uri节点中指定绝对的URI。

为了在应用中使用这个库, 需要把这个JAR文件拷贝到应用的WEB-INF/lib目录下。 在使用的时候, 任何使用自定义标签的JSP页面都要使用这个标签库描述器中定义的URL。

写在后面

自定义标签是解决JavaBean中前端展现与后端逻辑分离的好办法。 编写自定义标签, 需要创建标签处理器, 并在标签库描述器中注册它。

在JSP 2.3中, 有两种标签处理器可以使用: 经典标签处理器和简单标签处理器。 前者需要实现Tag、IterationTag及BodyTag的接口或者扩展TagSupport、BodyTagSupport这两个基类。 另一方面, 简单标签处理器, 需要实现SimpleTag或者扩展SimpleTagSupport。 相对经典标签处理器来说, 简单标签处理器更容易实现,它拥有更简单的生命周期。 简单标签处理器是推荐的使用方法。

猜你喜欢

转载自blog.csdn.net/zy2317878/article/details/80455661
今日推荐