JSP自定义标签详解

一、引入标签

其实在JSP中我们已经使用过了几个标签,比如、等;这些标签都是预先定义好的,如果我们需要自定义标签,就需要学习标签编程。
标签编程的优点就是灵活性+封装性。
灵活性体现在属性的赋值上,可以赋任意值。比如 //A处可以赋任意的URL;
封装性体现在内部的行为的封装,因为标签是用一个类实现的,因此类的方法中能够包含任意复杂的动作。
因此标签是为了能够在JSP中使用尽可能少的Scriptlet;
比如:

 <table>
 <%
    for(int i=0;i<10;i++){
 %>
    <tr>
 <%
        for(int j=0;j<10;j++){
 %>
        <td><%=i*j%></td>
 <%
        }
 %>
    </tr>
 <%
    }
 %>
 </table>

这个代码非常混乱,但是如果通过标签,从这段代码都封装在一个标签里,则代码就清晰了很多。

接下来是我通过标签封装后的JSP页面效果;

<%@ page contentType="text/html" pageEncoding="GBK"%>
<%@ taglib prefix="xiazdong" uri="xiazdong"%>
<html>
    <head>
        <title></title>
    </head>
    <body>
        <xiazdong:table row="5" col="3"/>
    </body>
</html>

是不是大大缩短了呢??再来看看我在背后干了什么。。。。
TableTagSupport.java

package org.tagext;
import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;
public class TableTagSupport extends TagSupport{
    private String row;
    private String col;
    public String getRow(){
        return row;
    }
    public String getCol(){
        return col;
    }
    public void setRow(String row){
        this.row = row;
    }
    public void setCol(String col){
        this.col = col;
    }
    public int doStartTag()throws JspException{
        JspWriter out = super.pageContext.getOut();
        try{
            out.println("<table border=\"3\"> ");
            for(int i=0;i<Integer.parseInt(row);i++){
                out.println("<tr>");
                for(int j=0;j<Integer.parseInt(col);j++){
                    out.println("<td>"+i*j+"</td>");
                }
                out.println("</tr>");
            }
            out.println("</table>");
        }
        catch(Exception e){}
        return TagSupport.SKIP_BODY;
    }
}

是不是很神奇。。。那接下来就看看实现的过程吧。
注意,在编写标签之前,必须把tomcat\lib中的jsp-api.jar文件配置在CLASSPATH中才可以;

二、基本标签编写

1、TagSupport类

如果要编写一个标签类,则必须继承javax.servlet.jsp.tagext.TagSupport;
TagSupport中提供了很多常用方法:
(1) public int doStartTag()throws JspException; // 标签开始时调用
能够返回SKIP_BODY(跳过标签体)、EVAL_BODY_INCLUDE(执行标签体)
(2) public int doEndTag()throws JspException; // 标签结束时调用
能够返回SKIP_PAGE(立刻停止执行)、EVAL_PAGE(JSP正常运行完毕);
(3) int SKIP_BODY; // 跳过标签体
(4) int EVAL_BODY_INCLUDE; //执行标签体
(5) int EVAL_BODY_AGAIN; // 重复执行标签体,主要是因为集合迭代输出,只能在doAfterBody中使用;
(6) public int doAfterBody()throws JspException; // 执行完一次标签体后调用的函数;
能够返回SKIP_BODY(结束标签体)、EVAL_BODY_AGAIN(重复执行标签体)
(7) JspWriter out = super.pageContext.getOut();//获取向网页输出的输出流;
举例讲述这些函数和常量代表什么意思:

<xiazdong:hello>                                <!--标签头 -->
        <h3><%="xiazdong"%></h3>            <!--    标签体-->
</xiazdong:hello>                                <!-- 标签尾-->

执行流程如下:
(1)doStartTag(); 时调用,如果是EVAL_BODY_INCLUDE,则继续;如果是SKIP_BODY,则执行(4)
(2)执行标签体;
(3)如果实现了doAfterBody,则执行;如果返回SKIP_BODY,则执行(4) ; 如果返回EVAL_BODY_AGAIN,则重复执行doAfterBody;
(4)doEndTag():标签尾调用;

2.制作无属性标签

为了清晰,我们以例子说明。
代码举例1:实现显示Hello world;

1.编写HelloTag.java

package org.tagext;
import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;

public class HelloTag extends TagSupport{
    public int doStartTag()throws JspException{
        JspWriter out = super.pageContext.getOut();
        try{
            out.println("<h3>Hello world!!!</h3>");
        }
        catch(Exception e){}
        return TagSupport.SKIP_BODY;
    }
}

注意点:
(1)javax.servlet.jsp.tagext.TagSupport,必须记住;
(2)JspWriter out = super.pageContext.getOut();
(3)public int doStartTag()throws JspException;必须记住;

2.编写xiazdong.tld

tld文件是标签描述文件,是整个标签编程的核心,用来描述自定义的标签的名字、标签的实现类、是否有标签体、描述属性等;
一个tld文件其实类似于一个标签库,里面能够描述很多标签;每个标签都以

<tag>
    <name>hello</name>      <!--表示标签的名字 类似于<jsp:forward>中的forward-->
    <tag-class>org.tagext.HelloTag</tag-class>  <!--标签所在类 -->
    <body-content>empty</body-content>      <!--是否存在标签体-->
</tag>

的形式描述;
*.tld文件的模版如下:(因为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">
    <tlib-version>1.0</tlib-version>
    //插入tag描述
</taglib>

3.在web.xml中编写映射

编写映射的目的类似于给一个网页配置一个映射地址,为了方便访问;
比如本来需要访问/a/b/c/d/e/f/g/1.tld,这个路径非常麻烦,但是如果通过映射,我们可以只需要用”a”表示一长串的文件路径及名称;

<jsp-config>
    <taglib>
        <taglib-uri>xiazdong</taglib-uri>
        <taglib-location>/WEB-INF/classes/xiazdong.tld</taglib-location>
    </taglib>
</jsp-config>

4.编写JSP页面并使用自定义的标签

<%@ taglib prefix=”” uri=”“%> prefix类似于中的jsp,uri中使用前面所说的映射;

<%@ page contentType="text/html" pageEncoding="GBK"%>
<%@ taglib prefix="xiazdong" uri="xiazdong"%>
<html>
    <head>
        <title></title>
    </head>
    <body>
        <h1><xiazdong:hello/></h1>
    </body>
</html>

3.制作有属性标签

其实制作有属性和制作无属性没有太大区别,

1.需要在原来的HelloTag.java 中加入属性.

如果做形如:的属性,则需要在HelloTag.java中添加两个属性,id属性和format属性;
形如:

class HelloTag extends TagSupport
{
    private String id;
    private String format;
    //setter和getter
    public int doStartTag()throws JspException{}
}

在用户使用标签,并为id和format属性赋值时,会自动调用setter方法,将HelloTag类中的id和format赋值;

2.在tld文件中添加属性描述:

属性描述形如:

<body-content>empty</body-content>后面加上
<attribute>
    <name>name</name>       <!--属性姓名 -->
    <required>true</required>   <!--属性是否必要 -->
    <rtexprvalue>empty</rtexprvalue>    <!--是否支持表达式语言 -->
</attribute>

代码实例:

HelloTag.java

package org.tagext;
import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;

public class HelloTag extends TagSupport{
    private String name;
    private String age;
    public String getName(){
        return name;
    }
    public String getAge(){
        return age;
    }
    public void setName(String name){
        this.name = name;
    }
    public void setAge(String age){
        this.age = age;
    }
    public int doStartTag()throws JspException{
        JspWriter out = super.pageContext.getOut();
        try{
            out.println("<h3>姓名:"+name+"</h3>");
            out.println("<h3>年龄:"+age+"</h3>");
        }
        catch(Exception e){}
        return TagSupport.SKIP_BODY;
    }
}

xiazdong.tld
需要加上attribute的描述:名字、是否必须、是否支持表达式语言

<?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">
  <tlib-version>1.0</tlib-version>
  <short-name>x</short-name>
  <tag>
    <name>hello</name>
    <tag-class>org.tagext.HelloTag</tag-class>
    <body-content>empty</body-content>
    <attribute>
        <name>name</name>
        <required>true</required>
        <rtexprvalue>empty</rtexprvalue>
    </attribute>
    <attribute>
        <name>age</name>
        <required>true</required>
        <rtexprvalue>empty</rtexprvalue>
    </attribute>
  </tag>
</taglib>

HelloTag.jsp

<%@ page contentType="text/html" pageEncoding="GBK"%>
<%@ taglib prefix="xiazdong" uri="xiazdong"%>
<html>
    <head>
        <title></title>
    </head>
    <body>
        <h1><xiazdong:hello name="xiazdong" age="20"/></h1>
    </body>
</html>

三、IterationTag和Tag接口的区别

Tag接口只有一些最基本的标签编程方法,而IterationTag接口是用于迭代输出,比如EVAL_BODY_AGAIN等;

四、编写有标签体的标签

需要返回TagSupport.EVAL_BODY_INCLUDE,则可以进入标签体;
AttributeTag.java

package org.tagext;
import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;

public class AttributeTag extends TagSupport{
    private String name;
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    public int doStartTag()throws JspException{
        Object value = null;
        value = super.pageContext.getAttribute(name,PageContext.PAGE_SCOPE);
        if(value==null){
            return TagSupport.SKIP_BODY;
        }
        else{
            return TagSupport.EVAL_BODY_INCLUDE;
        }
    }
}

xiazdong.tld

    <tag>
    <name>attribute</name>
    <tag-class>org.tagext.AttributeTag</tag-class>
    <body-content>JSP</body-content>
    <attribute>
        <name>name</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
  </tag>

AttributeTag.jsp

<%@ page contentType="text/html" pageEncoding="GBK"%>
<%@ taglib prefix="xiazdong" uri="xiazdong"%>
<html>
    <head>
        <title></title>
    </head>
    <body>
    <%
        pageContext.setAttribute("xiazdong","12345");
    %>
        <h1>
            <xiazdong:attribute name="xiazdong">
                <h3>${pageScope.xiazdong}</h3>
            </xiazdong:attribute>
        </h1>
    </body>
</html>

五、迭代标签

迭代标签的定义就是重复执行标签体,经常用在输出集合;在MVC中经常使用;

代码实例:

一些变化不大的文件我就不写了;
IterateTag.java

package org.tagext;
import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;
import java.util.*;
public class IterateTag extends TagSupport{
    private String name;
    private String id;
    private Iterator<String> iter;
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    public String getId(){
        return id;
    }
    public void setId(String id){
        this.id = id;
    }
    public int doStartTag()throws JspException{
        Object value = null;
        value = super.pageContext.getAttribute(name,PageContext.PAGE_SCOPE);
        if(value==null||!(value instanceof List<?>)){
            return TagSupport.SKIP_BODY;
        }
        else{
            iter = ((List<String>)value).iterator();
            if(iter.hasNext()){
                super.pageContext.setAttribute(id,iter.next());
                return TagSupport.EVAL_BODY_INCLUDE;
            }
            else{
                return TagSupport.SKIP_BODY;
            }
        }
    }
    public int doAfterBody()throws JspException{
        if(iter.hasNext()){
            super.pageContext.setAttribute(id,iter.next());
            return TagSupport.EVAL_BODY_AGAIN;
        }
        else{
            return TagSupport.SKIP_BODY;
        }
    }
}

IterateTag.jsp

<%@ page contentType="text/html" pageEncoding="GBK" import="java.util.*"%>
<%@ taglib prefix="xiazdong" uri="xiazdong"%>
<html>
    <head>
        <title></title>
    </head>
    <body>
    <%
        List<String> list = new ArrayList<String>();
        list.add("A");
        list.add("B");
        list.add("C");

        pageContext.setAttribute("xiazdong",list);
    %>
        <h1>
            <xiazdong:iterate name="xiazdong" id="iter">
                <h3>${iter}</h3>
            </xiazdong:iterate>
        </h1>
    </body>
</html>

六、BodyTagSupport类

BodyTagSupport的特点就是可以将输出内容存在BodyContent中, 一次全部输出;
根据上面的迭代输出的代码,稍微变化即可,主要变化是在IterateTag.java中,需要extends BodyTagSupport;
BodyTagSupport的常用方法:
(1)BodyTagSupport.EVAL_BODY_BUFFERED;
(2)getPreviousOut(); //获得输出到网页的输出流
(3)bodyContent对象存储数据;

package org.tagext;
import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;
import java.util.*;
public class IterateTag extends BodyTagSupport{
    private String name;
    private String id;
    private Iterator<String> iter;
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    public String getId(){
        return id;
    }
    public void setId(String id){
        this.id = id;
    }
    public int doStartTag()throws JspException{
        Object value = null;
        value = super.pageContext.getAttribute(name,PageContext.PAGE_SCOPE);
        if(value==null||!(value instanceof List<?>)){
            return TagSupport.SKIP_BODY;
        }
        else{
            iter = ((List<String>)value).iterator();
            if(iter.hasNext()){
                super.pageContext.setAttribute(id,iter.next());
                return TagSupport.EVAL_BODY_BUFFERED;    //存在BodyContent中
            }
            else{
                return TagSupport.SKIP_BODY;
            }
        }
    }
    public int doAfterBody()throws JspException{
        if(iter.hasNext()){
            super.pageContext.setAttribute(id,iter.next());
            return TagSupport.EVAL_BODY_AGAIN;
        }
        else{
            return TagSupport.SKIP_BODY;
        }
    }
    public int doEndTag()throws JspException{
        if(super.bodyContent!=null){
            try{
                super.bodyContent.writeOut(super.getPreviousOut());    //输出到页面
            }
            catch(Exception e){}
        }
        return BodyTagSupport.EVAL_PAGE;
    }
}

七、TagExtraInfo和VariableInfo的使用

在中的id属性表示对象名称,并且可以通过这个名称调用方法;而如果要实现这种效果,必须继承TagExtraInfo类;
我们需要另外创建一个文件,用以表明属性的对象能够在脚本中使用;
MyExtraInfo.java

package org.tag;
import javax.servlet.jsp.tagext.*;
public class MyTagExtraInfo extends TagExtraInfo{
    public VariableInfo[] getVariableInfo(TagData data){
        return new VariableInfo[]{new VariableInfo(data.getId(),"java.lang.String",true,VariableInfo.NESTED)};
    }
}

写完后在xiazdong.tld中添加:

    <tei-class>         //注意
        org.tag.MyTagExtraInfo
    </tei-class>

最后xiazdong.tld中以:

<tag>
    <name>iterate</name>
    <tag-class>org.tagext.IterateTag</tag-class>
    <body-content>JSP</body-content>


    <tei-class><span style="white-space:pre">           </span>//注意
        org.tag.MyTagExtraInfo
    </tei-class>


    <attribute>
        <name>name</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
    <attribute>
        <name>id</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
  </tag>

这样在JSP中就能够在Scriptlet中使用;

<%@ page contentType="text/html" pageEncoding="GBK" import="java.util.*"%>
<%@ taglib prefix="xiazdong" uri="xiazdong"%>
<html>
    <head>
        <title></title>
    </head>
    <body>
    <%
        List<String> list = new ArrayList<String>();
        list.add("A");
        list.add("B");
        list.add("C");

        pageContext.setAttribute("xiazdong",list);
    %>
        <h1>
            <xiazdong:iterate name="xiazdong" id="iter">
                <h3><%=iter%></h3>     //注意,原本是${iter}输出;
            </xiazdong:iterate>
        </h1>
    </body>
</html>

八、SimpleTagSupport类

在JSP2.0后,为了简化标签开发的难度,就可以使用SimpleTagSupport进行开发;

1.开发一般标签

注意点:
(1)需要继承SimpleTagSupport类;
(2)实现public void doTag()throws JspException,IOException;
(3)super.getJspContext().getOut().write(“….”); 进行输出;
(4)在SimpleTagSupport中,tld中的内容不能为JSP,如果标签体不为空,则只能为scriptless;

代码:
SimpleTagSupportDemo.java

package org.tagext;
import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;
import java.io.*;
public class SimpleTagSupportDemo extends SimpleTagSupport{
    private String name;
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    public void doTag()throws JspException,IOException{
        super.getJspContext().getOut().write("<h3>"+name+"</h3>");
    }
}

2.开发迭代标签

通过super.getJspBody().invoke(null);能够执行标签体内容;

SimpleTagSupportDemo.java

package org.tagext;
import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;
import java.io.*;
import java.util.*;
public class SimpleTagSupportDemo extends SimpleTagSupport{
    private String name;
    private String id;
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    public String getId(){
        return id;
    }
    public void setId(String id){
                this.id = id;
    }
    public void doTag()throws JspException,IOException{
        Object value = super.getJspContext().getAttribute(name,PageContext.PAGE_SCOPE);
        Iterator<String> iter = ((List<String>)value).iterator();
        while(iter.hasNext()){
            super.getJspContext().setAttribute(id,iter.next());
            super.getJspBody().invoke(null);
        }

    }
}

综合看来,SimpleTagSupport比起前面的TagSupport,BodyTagSupport,简单了许多,不需要任何返回值;

九、常见问题

1.区分是否有标签体


是属于有标签体的,只是标签体为空;
为标签体为空;


本文转载:CSDN博客