自定义标签(二)——tld文件方式实现自定义标签

TLD文件实现自定义标签

之前我们使用的标签库都是JSTL为我们提供的,大部分的数据操作和控制都可以使用它来完成,但是如果我们项目中有特殊需求或者为了统一开发规范,那么我们也可以自己定义一套标签库供自己的团队使用。通过实现java提供的JspTag的子接口编写标签处理类,完成自定义标签的定义。

实现步骤:

  • 创建标签处理程序 (Tag Handler Class)
  • 创建标签库描述文件(Tag Library Descrptor File)
  • 在web.xml文件中配置元素(可选,如果标签库描述文件不放在WEB-INF下则需要配置)

    <jsp-config>
        <taglib>
            <!-- tld描述文件中的uri -->
            <taglib-uri></taglib-uri>
            <!-- 存放路径 -->
            <taglib-location></taglib-location>
        </taglib>
      </jsp-config>
    
  • 在JSP文件中使用taglib指令引入标签库

  • 使用标准格式调用自定义标签

JspTag标签分为简单的标签和传统的标签,树形图如下:

这里写图片描述

简单示例

示例

标签实现类

package com.yt.tag
public class IpTag implements Tag {
    private PageContext pageContext;
    /**
     * 设置pageContext
     */
    @Override
    public void setPageContext(PageContext pc) {
        this.pageContext = pc;
    }
    /**
     * 设置父类标签
     */
    @Override
    public void setParent(Tag t) {

    }
    /**
     * 获取父类标签
     */
    @Override
    public Tag getParent() {
        return null;
    }
    /**
     * 开始标签,一定会被调用
     */
    @Override
    public int doStartTag() throws JspException {
        HttpServletRequest request = (HttpServletRequest)pageContext.getRequest(); //获取request  
        JspWriter out = pageContext.getOut(); //获取out</span>  

        String ip = request.getRemoteAddr(); //通过request获取客户机的ip  
        try {  
            out.write(ip); //写到浏览器  
        } catch (IOException e) {  
            throw new RuntimeException(e);  
        }         
        return 0;  
    }
    /**
     * 结束标签
     */
    @Override
    public int doEndTag() throws JspException {
        return 0;
    }
    /**
     * 释放标签
     */
    @Override
    public void release() {

    }
}

创建标签库描述文件(TLD) myTag.tld

<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"  
    version="2.0">
    <description>A tag library exercising SimpleTag handlers</description>
    <tlib-version>1.0</tlib-version>
    <short-name>my</short-name> <!-- 定义一个短名 -->
    <uri>/WEB-INF/static/tld</uri>  <!-- 定义被jsp页面引用的uri -->
    <tag>
        <name>ip</name>
        <tag-class>com.yt.tag.IpTag</tag-class> <!-- 标签实现类 -->
        <body-content>empty</body-content>  <!--标签体为空-->
    </tag>
</taglib>

jsp页面使用自定义标签

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="m" uri="/WEB-INF/static/tld" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>MyTag</title>
</head>
<body>
    <m:ip/>
</body>
</html>

上述自定义标签执行过程:

  1. tomcat服务器启动时,加载到每个web应用,加载每个web应用的WEB-INF目录下的所有文件;
  2. JSP引擎首先通过uri和ip标签名去找tld文件,在tld中通过ip找到IpTag类;
  3. IpTag类首先调用 setPageContext 方法把页面的 pageContext 传递进来;
  4. 再调用setParent把父标签传递进来(没有则不执行),至此完成了标签的初始化工作;
  5. 然后调用doStartTag和doEndTag方法,开始和结束标签;
  6. 最后调用release方法释放标签,运行时所占的资源。

标签体

上述简单示例中标签体设置为empty,<body-content>元素的可选值有:

  • empty:无标签体。
  • JSP:传统标签支持它,SimpleTag已经不再支持使用<body-content>JSP</body-content>;标签体内容可以是任何东西:EL、JSTL、<%=%>、<%%>,以及html;
  • scriptless:标签体内容不能是Java脚本,但可以是EL、JSTL等。SimpleTag中需要标签体时使用它。
  • tagdependent:标签体内容不做运算,由标签处理类自行处理,无论标签体内容是EL、JSP、JSTL,都不会做运算。这个选项几乎没有人会使用。

传统标签

Tag接口

public interface Tag extends JspTag {
     public final static int SKIP_BODY = 0;
     public final static int EVAL_BODY_INCLUDE = 1;
     public final static int SKIP_PAGE = 5;
     public final static int EVAL_PAGE = 6;
     void setPageContext(PageContext pc);
     void setParent(Tag t);
     Tag getParent();
     int doStartTag() throws JspException;
     int doEndTag() throws JspException;
     void release();
}

tag接口的执行过程:

这里写图片描述

验证上述执行过程:

public class TimeTag implements Tag{
    private PageContext pageContext;
    private Tag parent;
    //标签属性
    private String color;

    public TimeTag() {
        super();
        System.out.println("实例化TimeTag");
    }

    @Override
    public void setPageContext(PageContext pc) {
        this.pageContext = pc;
        System.out.println("设置pageContext....");
    }

    @Override
    public void setParent(Tag t) {
        this.parent = t;
        System.out.println("设置parent.....");
    }

    @Override
    public Tag getParent() {
        return this.parent;
    }

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

    @Override
    public int doEndTag() throws JspException {
        System.out.println("doEndTag................");

        Date date=new Date();
        SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String re=df.format(date);

        try {
            //这里用PageContext的对象的getOut()方法(这样就能在页面中输出了)。
        pageContext.getOut().println("<h1 style='color:"+this.color+";'>"+re+"</h1>");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return Tag.EVAL_PAGE;
    }

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

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        System.out.println("setColor");
        this.color = color;
    }
}
<tag>
    <name>time</name>
    <tag-class>com.yt.tag.TimeTag</tag-class>
    <body-content>empty</body-content>
    <!--标签属性的信息 如果有属性,在标签实现类中set/get-->
    <attribute>
        <name>color</name>
        <required>true</required>
        <!--表示可以出来JSP表达式-->
        <rtexprvalue>true</rtexprvalue>
    </attribute>
</tag>
<m:time color="red"/>

Tag接口定义了4个静态常量:

  • SKIP_BODY:忽略标签体中的内容(作用在doStartTag方法中)
  • EVAL_BODY_INCLUDE:将标签体的内容进行输出,等同于SimpleTag中JspFragment的invoke(null)(但是获取不了)
  • SKIP_PAGE:等同于new SkipPageException(),不执行JSP页面标签后面的内容(作用在doEndTag方法中)
  • EVAL_PAGE:执行JSP页面标签后面的内容

带标签体的Tag

tld描述文件中的body-content可以是scriptless或JSP

<body-content>scriptless</body-content> <!--可以是JSP-->

TimeTag类中改变doStartTag的返回值

@Override
public int doStartTag() throws JspException {
    System.out.println("doStartTag............");
       return Tag.EVAL_BODY_INCLUDE;
}
<m:time color="red">${param.name}</m:time>

标签体内容可以是El、JSTL、HTML标签和纯文本等,标签实现类会自动解析内容输出到浏览器。如上述可以在请求url后加上?name=红色。

IterationTag接口

这个接口是Tag接口的子接口,它可以实现body的循环。

public interface IterationTag extends Tag {
    public final static int EVAL_BODY_AGAIN = 2;
    int doAfterBody() throws JspException;
}

上述接口定义了一个新的静态常量EVAL_BODY_AGAIN和方法doAfterBody。
EVAL_BODY_AGAIN:重新执行 (作用在doAfterBody方法中)

IterationTag接口的执行过程:

IterationTag

以下模拟一个类似于<c:forEache>的循环迭代

<%
        List list = new ArrayList();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        pageContext.setAttribute("list", list);
    %>
    <m:for item="${list}" var="i">
        ${i}
    </m:for>
<tag>
    <name>for</name>
    <tag-class>com.yt.tag.ForTag</tag-class>
    <body-content>scriptless</body-content>
    <attribute>
        <name>item</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
    <attribute>
        <name>var</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
</tag>
public class ForTag implements IterationTag{
    private PageContext pageContext;
    private Tag parent;
    private List item;          //需要迭代的List集合
    private String var;         //List集合元素值的变量

    private int index=0;        //索引

    public List getItem() {
        return item;
    }

    public void setItem(List item) {
        this.item = item;
    }

    public String getVar() {
        return var;
    }

    public void setVar(String var) {
        this.var = var;
    }

    public PageContext getPageContext() {
        return pageContext;
    }

    @Override
    public void setPageContext(PageContext pc) {
        this.pageContext = pc;
    }

    @Override
    public void setParent(Tag t) {
        this.parent = t;
    }

    @Override
    public Tag getParent() {
        return this.parent;
    }

    @Override
    public int doStartTag() throws JspException {
        pageContext.setAttribute(var, item.get(index));//这里设置的属性只会执行一次,被输出到浏览器
        return Tag.EVAL_BODY_INCLUDE;
    }

    @Override
    public int doEndTag() throws JspException {
        return Tag.EVAL_PAGE;
    }

    @Override
    public void release() {

    }

    @Override
    public int doAfterBody() throws JspException {
        if(++index<item.size()){
            pageContext.setAttribute(var, item.get(index)); //循环执行
            return IterationTag.EVAL_BODY_AGAIN;   //循环输出标签体到当前输出流
        }
        return Tag.SKIP_BODY;
    }
}

BodyTag接口

这个接口是IterationTag的子接口,上述标签都不可以读body的值,该标签实现了对body部分的读写。

public interface BodyTag extends IterationTag {
    public final static int EVAL_BODY_TAG = 2; //已过时
    public final static int EVAL_BODY_BUFFERED = 2;
    void setBodyContent(BodyContent b);
    void doInitBody() throws JspException;

EVAL_BODY_BUFFERED :把标签主体内容放入缓存器中不要输出(作用在主体标签中),让BodyTag调用setBodyContent获取BodyContent。

BodyTag接口的执行过程:

BodyTag

<m:body>
    我是一名java程序员
</m:body>
<tag>
    <name>body</name>
    <tag-class>com.yt.tag.BodyContentTag</tag-class>
    <body-content>scriptless</body-content>
</tag>
public class BodyContentTag implements BodyTag{

    private BodyContent bodyContent;
    private PageContext pageContext;
    private Tag parent;

    @Override
    public int doAfterBody() throws JspException {

        return Tag.SKIP_BODY;
    }
    @Override
    public void setPageContext(PageContext pc) {
        this.pageContext = pc;
    }
    @Override
    public void setParent(Tag t) {
        this.parent = t;
    }
    @Override
    public Tag getParent() {
        return this.parent;
    }
    @Override
    public int doStartTag() throws JspException {
        return BodyTag.EVAL_BODY_BUFFERED; //接下来会依次执行setBodyContent、doInitBody()和doAfterBody()
    }

    @Override
    public int doEndTag() throws JspException {
        StringBuilder sb = new StringBuilder();
        String msg = bodyContent.getString();
        String newmsg = msg.replace("java", "C#");
        sb.append("<div style='width:200px;height:200px;border:1px #ccc solid; line-height:200px;text-align:center;'>");
        sb.append(newmsg);
        sb.append("<div");
        try {
            pageContext.getOut().print(sb.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return Tag.EVAL_PAGE;
    }
    @Override
    public void release() {

    }
    @Override
    public void setBodyContent(BodyContent b) {
        this.bodyContent = b;
    }
    @Override
    public void doInitBody() throws JspException {

    }
}

BodyTagSupport类

该类实现了BodyTag, IterationTag, JspTag, Tag这些接口,并且有一个常量:bodyContent,是BodyContent类实例化的对象,已经实例化好的,可以直接用。
这个类的实现和我们上述自定义的实现类BodyContentTag 基本一致。

TagSupport类

这个类是IterationTag的实现类,和BodyTagSupport比较类似。里面封装的常量不同。区别在于二者定义的属性不同:
BodyTagSupport定义的属性

    protected BodyContent   bodyContent;

TagSupport定义的属性

    private   Tag         parent;
    private   Hashtable<String, Object> values;
    protected String      id;
    protected PageContext pageContext;

简单标签

SimpleTag接口:

在jsp2.0中也新增加的接口,可以实现它来做制作标签处理类,而不用处理一些TagSupport、BodyTagSupport类中来回传值的问题。

public interface SimpleTag extends JspTag {
    //标签执行方法
    public void doTag() throws javax.servlet.jsp.JspException, java.io.IOException;
    //设置父标签
    public void setParent( JspTag parent );
    //获取父标签
    public JspTag getParent();
    //设置pageContext
    public void setJspContext( JspContext pc );
    //设置标签体对象
    public void setJspBody( JspFragment jspBody );
}

下面同样实现与<c:forEach>类似的迭代功能,与前面实现IterationTag的类ForTag做比较,进而看出实现SimpleTag接口的简便

public class SimpleForTag implements SimpleTag{
    private JspContext jspContext;  //pageContext父类,都是抽象类,因此基本没区别
    private JspTag parent;
    private JspFragment jspBody;

    private List item;
    private String var;

    @Override
    public void doTag() throws JspException, IOException {
        JspWriter out = this.getJspContext().getOut();
        //循环遍历标签体
        for(Iterator it = item.iterator();it.hasNext();){
            String name = (String) it.next();
            this.getJspContext().setAttribute(var, name);
            this.getJspBody().invoke(null); //body-content为scriptless
        }
    }

    public List getItem() {
        return item;
    }

    public void setItem(List item) {
        this.item = item;
    }

    public String getVar() {
        return var;
    }

    public void setVar(String var) {
        this.var = var;
    }

    public JspContext getJspContext() {
        return jspContext;
    }

    public JspFragment getJspBody() {
        return jspBody;
    }

    @Override
    public void setParent(JspTag parent) {
        this.parent = parent;
    }

    @Override
    public JspTag getParent() {
        return this.parent;
    }

    @Override
    public void setJspContext(JspContext pc) {
        this.jspContext = pc;
    }

    @Override
    public void setJspBody(JspFragment jspBody) {
        this.jspBody = jspBody;
    }
}

Tag标签的生命周期:
1. 当容器(Tomcat)第一次执行到某个标签时,会创建标签处理类的实例;
2. 然后调用setJspContext(JspContext)方法,把当前JSP页面的pageContext对象传递给这个方法;
3. 如果当前标签有父标签,那么使用父标签的标签处理类对象调用setParent(JspTag)方法;
4. 如果标签有标签体,那么把标签体转换成JspFragment对象,然后调用setJspBody()方法;
5. 每次执行标签时,都调用doTag()方法,它是标签处理方法。

SimpleTagSupport类:

继承SimpleTagSuppport要比实现SimpleTag接口方便太多了,现在你只需要重写doTag()方法和添加自己需要的属性即可,其他方法都已经被SimpleTagSuppport完成了。

WEB容器在处理简单标签的标签体时,会把标签体内容用一个JspFragment对象表示,并调用标签处理器对象的setJspBody方法把JspFragment对象传递给标签处理器对象,JspFragment对象有二个方法:

public abstract class JspFragment {
     public abstract void invoke( Writer out ) throws JspException, IOException;
     public abstract JspContext getJspContext();
}

public abstract void invoke(Java.io.Writer out)用于执行JspFragment对象所代表的JSP代码片段,参数out用于指定将JspFragment对象的执行结果写入到哪个输出流对象中,如果传递给参数out的值为null,则将执行结果写入到JspContext.getOut()方法返回的输出流对象中。

自定义输出流

    @Override
    public void doTag() throws JspException, IOException {
        System.out.println("doTag");
        JspWriter out = this.getJspContext().getOut();
        //循环遍历标签体
        for(Iterator it = item.iterator();it.hasNext();){
            String name = (String) it.next();
            this.getJspContext().setAttribute(var, name);
            StringWriter sw = new StringWriter();
            this.getJspBody().invoke(sw); //body-content为scriptless
            String body = sw.toString().toUpperCase();
            out.print(body);
        }
    }

不执行标签下面的页面内容

public class SkipTag extends SimpleTagSupport{
    @Override
    public void doTag() throws JspException, IOException {
        PageContext pageContext = (PageContext) this.getJspContext();
        pageContext.getOut().print("<h1>呵呵</h1>");
        throw new SkipPageException();
    }
}

猜你喜欢

转载自blog.csdn.net/yutao_struggle/article/details/78880363
今日推荐