一、 自定义标签简介
自定义标签主要用于移除jsp页面中的java代码。工程(tag
)
使用自定义标签移除jsp页面中的java代码,只需要完成以下两个步骤:
1.编写一个实现tag接口的java类(标签处理类)。
2.编写标签库描述符(tld)文件,在tld文件中对标签处理器类进行描述。
二、快速入门:使用标签输出客户机IP
这里我们先编写一个tld文件(itcast.tld):
<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.1" 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 web-jsptaglibrary_2_1.xsd">
<tlib-version>1.0</tlib-version>
<short-name>itcast</short-name><!-- tld文件名字 -->
<uri>/itcast</uri><!-- 因为文件是放在WEB-INF下面 -->
<tag>
<name>viewIP</name><!-- 给标签起名 -->
<tag-class>cn.itcast.web.tag.ViewIPTag</tag-class><!-- 类的完整类名 -->
<body-content>empty</body-content><!-- 这里是操作标签体,如果不用,则使用empty -->
</tag>
</taglib>
注意:标签<body-content>
为empty表示标签起始和结束之间没有内容。如<a ..../>
,而不能是<a>...</a>
。
说明:我们可以在MyEclipse中直接新建tld文件,然后填入相应的信息:
其中Name是文件名,Version我们选择最高版本,Shortname一般填入文件名,由于文件放在WEB-INF 下面,所以uri地址是/itcast。
实现类(ViewIPTag.java)
package cn.itcast.web.tag;
import java.io.IOException;
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;
//自定义标签类,注意继承Tag接口
public class ViewIPTag implements Tag {
private PageContext pageContext;//用于保存页面对象
@Override
public int doStartTag() throws JspException {
HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
//得到out隐式对象,用于输出
JspWriter out = pageContext.getOut();
//得到服务器地址
String ip = request.getRemoteAddr();
try {
out.write(ip);
} catch (IOException e) {
//这里子类不能抛出比父类还多的异常
throw new RuntimeException(e);
}
return 0;
}
@Override
public void setPageContext(PageContext arg0) {
this.pageContext = arg0;
}
@Override
public int doEndTag() throws JspException {
return 0;
}
@Override
public Tag getParent() {
return null;
}
@Override
public void release() {}
@Override
public void setParent(Tag arg0) {}
}
使用(1.jsp)
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/itcast" prefix="itcast"%><!-- 和之前使用EL标签一样,需要导入 -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>客户机IP</title>
</head>
<body>
您的IP是:<itcast:viewIP/>
<%
/* 等同于 */
String ip = request.getRemoteAddr();
out.write(ip);
%>
</body>
</html>
三、标签的执行过程
jsp引擎遇到自定义标签时,首先创建标签处理类的实例对象,然后按照jsp规范定义的通信规则依次调用它的方法。
1.jsp引擎实例化标签处理类之后,将调用setPageContext方法将jsp页面的pageContext对象传递给标签处理器,标签处理器以后可以通过这个pageContext对象与jsp页面进行通信。
2.setPageContext方法执行完后,web容器接着调用setParent方法将当前标签的父标签传递给当前标签处理器,如果当前标签没有父标签,则传递给setParent方法的参数值为null。
3.调用了setPageContext方法和setParent方法之后,web容器执行到自定义标签的开始标签时,就会调用标签处理器的doStartTag方法。
4.web容器执行完自定义标签的标签体之后,就会接着去执行自定义标签的结束标签,此时,web容器就会调用doEndTag方法。
5.通常web容器执行完自定义标签后,标签处理器会驻留在内存中,为其它请求服务,直至停止web应用时,web容器才会调用release方法。
自定义标签功能扩展
开发人员在编写jsp页面时,经常还需要在页面中引入一些逻辑。例如:
(1)控制jsp页面某一部分内容是否执行
(2)控制整个jsp页面是否执行
(3)控制jsp页面内容重复执行
(4)修改jsp页面内容输出
自定义标签除了可以移除jsp页面的java代码,也可以实现以上功能。
例:控制jsp页面某一部分内容是否执行
TagDemo1.java
package cn.itcast.web.tag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TagSupport;;
public class TagDemo1 extends TagSupport {
@Override
public int doStartTag() throws JspException {
//return Tag.EVAL_BODY_INCLUDE;//返回此常量则会执行标签体
return Tag.SKIP_BODY;//如果要跳过标签体,则使用此常量
}
}
itcast.tld中
<tag>
<name>demo1</name>
<tag-class>cn.itcast.web.tag.TagDemo1</tag-class>
<body-content>JSP</body-content>
</tag>
注意:此处<body-content>
为JSP,必须是大写,表示标签之间有内容。内容接受所遇jsp语法,当然这里我们只是表示标签之间有内容,而内容执行不执行依据所定义的类。当然还可以是tagdependent和scriptless,但用的不多,此处不再详述。
注意:此处我们继承的是TagSupport类,因为每次继承Tag接口需要实现很多用不到的方法,于是这里我们继承此默认实现类可以简便一些。关于Tag接口的继承关系如下图:
使用(2.jsp)
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/itcast" prefix="itcast"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>控制jsp页面某一部分内容是否执行</title>
</head>
<body>
<itcast:demo1>
xxx
</itcast:demo1>
</body>
</html>
使用这个标签就表示xxx不会被执行。当然具体怎样控制我们可以在类中进行细化。
例:控制整个jsp页面是否执行
itcast.tld
<tag>
<name>demo2</name>
<tag-class>cn.itcast.web.tag.TagDemo2</tag-class>
<body-content>empty</body-content>
</tag>
TagDemo2.java
package cn.itcast.web.tag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TagSupport;
public class TagDemo2 extends TagSupport {
@Override
public int doEndTag() throws JspException {
return Tag.SKIP_PAGE;//此常量表示结束标签后买年余下的jsp页面不会执行
//return Tag.EVAL_PAGE;//此常量表示会执行
}
}
3.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/itcast" prefix="itcast"%>
<itcast:demo2/><!-- 即控制结束标签后面的jsp内容是否执行 -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>控制整个jsp页面是否执行</title>
</head>
<body>
</body>
</html>
例:控制jsp页面内容重复执行
itcast.tld
<tag>
<name>demo3</name>
<tag-class>cn.itcast.web.tag.TagDemo3</tag-class>
<body-content>JSP</body-content>
</tag>
TagDemo3
package cn.itcast.web.tag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.IterationTag;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TagSupport;
public class TagDemo3 extends TagSupport {
int x = 5;
@Override
public int doAfterBody() throws JspException {
x--;
//注意:控制标签体重复执行的时候需要的常量需要的不是Tag常量,而是IterationTag常量
if(x > 0){
return IterationTag.EVAL_BODY_AGAIN;
}else{
return IterationTag.SKIP_BODY;
}
}
@Override
public int doStartTag() throws JspException {
//注意:要想重复执行,则首先要让标签体执行才行
return Tag.EVAL_BODY_INCLUDE;
}
}
itcast.tld
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/itcast" prefix="itcast"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>控制页面重复执行</title>
</head>
<body>
<itcast:demo3>
这里的页面内容将重复执行
</itcast:demo3>
</body>
</html>
例:修改jsp页面内容输出
itcast.tld
<tag>
<name>demo4</name>
<tag-class>cn.itcast.web.tag.TagDemo4</tag-class>
<body-content>JSP</body-content>
</tag>
TagDemo4
package cn.itcast.web.tag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyTag;
import javax.servlet.jsp.tagext.BodyTagSupport;
import javax.servlet.jsp.tagext.Tag;
public class TagDemo4 extends BodyTagSupport {
@Override
public int doEndTag() throws JspException {
//得到标签体内容字符串
String content = this.getBodyContent().getString();
//将标签体内容改为大写
String result = content.toUpperCase();
//得到out输入对象
try {
this.pageContext.getOut().write(result);
} catch (IOException e) {
throw new RuntimeException(e);
}
return Tag.EVAL_PAGE;//表示标签体余下的内容还要输出
}
@Override
public int doStartTag() throws JspException {
//要想修改标签体,则下面必须返回此常量
return BodyTag.EVAL_BODY_BUFFERED;
}
}
5.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/itcast" prefix="itcast"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>修改标签体内容</title>
</head>
<body>
<itcast:demo4>
aaa
</itcast:demo4>
</body>
</html>
得到的输出结果是AAA。
最后,其实上面是一种很传统的方式,使用起来并不方便,一般我们很少使用。
四、简单标签
由于传统标签使用三个标签接口来完成不同的功能,显得过于繁琐,不利于标签技术的推广,sun公司为降低标签技术的学习难度,在jsp2.0中定义了一个更为简单、便于编写和调用的SimpleTag接口来实现标签的功能。实现SimpleTag接口的标签通常称为简单标签。简单标签定义了5个方法:
-
setJspContext
方法:用于把jsp页面的pageContext
对象传递给标签处理器对象; -
setParent
和getParent
方法:前者用于把父标签处理器对象传递给当前处理器对象。后者用于获得当前标签的父标签处理器对象。 -
setJspBody
:用于把代表标签体的JspFragment
对象传递给标签处理器对象。 -
doTag
方法:用于完成所有的标签逻辑,包括输出、迭代、修改标签体内容等。在doTag方法中可以抛出javax.servlet.jsp.SkipPageException
异常,用于通知web容器不再执行jsp页面中位于结束标记后面的内容,这等效于在传统标签的doEndTag
方法中返回Tag.SKIP_PAGE
的情况。
1.SimpleTag接口方法的执行顺序
(1)当web容器准备开始执行标签时,会调用如下方法完成标签的初始化:
- 1.web容器调用标签处理器对象的
setJspContext
方法,将代表jsp页面的pageContext
对象传递给标签处理器对象。 - 2.web容器调用标签处理器对象的
setParent
方法,将父标签处理器对象传递给这个标签处理器对象。注意:只有在标签存在父标签的情况下,web容器才会调用此方法。 - 3.如果调用标签时设置了属性,容器将调用每个属性对应的setter方法把属性值传递给标签处理器对象。如果标签的属性值是EL表达式或脚本表达式,则web容器首先计算表达式的值,然后把值传递给标签处理器对象。
- 4.如果简单标签有标签体,容器将调用
setJspBody
方法把代表标签体的JspFragment
对象传递进来。
(2)执行标签时:
容器调用标签处理器的doTag
方法,开发人员在方法体内通过操作JspFragment
对象,就可以实现是否执行、迭代、修改标签体的目的。当然是先得到此对象。
2.JspFragment类
javax.servlet.jsp.tagext.JspFragment
类是jsp2.0中定义的,它的实例对象代表jsp页面中的一段符合jsp语法规范的jsp片段,这段jsp片段中不能包含jsp脚本元素。web容器在处理简单标签的标签体时,会把标签体内容用一个
JspFragment
对象表示,并调用标签处理对象的setJspBody
方法把JspFragment
对象传递给标签处理器对象。JspFragment
类中定义了两个方法,如下所示:
getJspContext
方法:用于返回代表调用页面的JspContext
对象。
invoke
方法:用于执行JspFragment
对象所代表的jsp代码片段;参数out用于指定将JspFragment
对象的执行结果写入到哪个输出流对象中,如果传递给参数out值为null,则将执行结果写入到JspContext.getOut
方法返回的输出流对象中。(即写给浏览器)。
3.invoke方法详解
JspFragment.invoke方法是JspFragment
最重要的方法,利用这个方法可以控制是否执行和输出标签体的内容、是否迭代执行标签体的内容或对标签体的执行结果进行修改后再输出。例如:
- 1.在标签处理器中如果没有调用
JspFragment.invoke
方法,其结果就相当于忽略标签体的内容; - 2.在标签处理器中重复调用
JspFragment.invoke
方法,则标签体内容将会别重复执行; - 3.若想在标签处理器中修改标签体内容,只需在调用
invoke
方法时指定一个可取出结果数据的输出流对象(StringWriter
),让标签体的执行结果输出到该输出流对象中,然后从该输出流对象中取出数据进行修改后再输出到目标设备,即可达到修改标签体的目的。
4.使用简单标签实现之前传统标签完成的例子
例:使用简单标签实现控制标签体是否执行
WebRoot/WEB-INF/simpletag.tld
<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.1" 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 web-jsptaglibrary_2_1.xsd">
<tlib-version>1.0</tlib-version>
<short-name>simpleitcast</short-name>
<uri>/simpleitcast</uri>
<tag>
<name>demo1</name>
<tag-class>cn.itcast.web.simpletag.SimpleTagDemo1</tag-class>
<!-- 这里注意:在简单标签中不允许使用JSP,因为新版的标签技术不允许在标签体中使用
脚本代码,比如<%%>这样的,scriptless表示无脚本 -->
<body-content>scriptless</body-content>
</tag>
</taglib>
SimpleTagDemo1
package cn.itcast.web.simpletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
//使用简单标签控制标签体是否执行
public class SimpleTagDemo1 extends SimpleTagSupport {
//简单标签使用此方法完成所有的业务逻辑
@Override
public void doTag() throws JspException, IOException {
//得到JspFragment对象,即得到一段jsp片段
JspFragment jf = this.getJspBody();
//得到pageContext对象
PageContext pageContext = (PageContext) this.getJspContext();
jf.invoke(pageContext.getOut());
//jf.invoke(null);//此行代码表示默认执行上面两行代码,表示默认传递给浏览器
}
}
WebRoot/simpletag/1.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/simpleitcast" prefix="itcast"%><!-- 这里我们前缀还是使用itcast -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>使用简单标签实现控制标签体是否执行</title>
</head>
<body>
<itcast:demo1>
xxxx
</itcast:demo1>
</body>
</html>
这里我们是允许标签体中的内容进行输出执行。输出xxxx。当然如果我们不想让标签体执行,那就不写
PageContext pageContext = (PageContext) this.getJspContext();
jf.invoke(pageContext.getOut());
或不写jf.invoke(null);
。
例:控制标签体重复执行
simpletag.tld
<tag>
<name>demo2</name>
<tag-class>cn.itcast.web.simpletag.SimpleTagDemo2</tag-class>
<body-content>scriptless</body-content>
</tag>
SimpleTagDemo2
package cn.itcast.web.simpletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
//控制标签体重复执行多次
public class SimpleTagDemo2 extends SimpleTagSupport{
@Override
public void doTag() throws JspException, IOException {
JspFragment jf = this.getJspBody();
for(int i = 0; i < 10; i++){
jf.invoke(null);
}
}
}
2.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/simpleitcast" prefix="itcast"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>控制标签体重复执行多次</title>
</head>
<body>
<itcast:demo2>
xxxx
</itcast:demo2>
</body>
</html>
例:修改标签体中的内容
simpletag.tld
<tag>
<name>demo3</name>
<tag-class>cn.itcast.web.simpletag.SimpleTagDemo3</tag-class>
<body-content>scriptless</body-content>
</tag>
SimpleTagDemo3
package cn.itcast.web.simpletag;
import java.io.IOException;
import java.io.StringWriter;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
//修改标签体中的内容
public class SimpleTagDemo3 extends SimpleTagSupport{
@Override
public void doTag() throws JspException, IOException {
JspFragment jf = this.getJspBody();
StringWriter sw = new StringWriter();//得到一个输出流
jf.invoke(sw);//注意:要想修改标签体内容个必须先执行标签体
//得到标签体内容
String content = sw.getBuffer().toString();
content = content.toUpperCase();//将标签体中的字符串变为大写
PageContext pageContext = (PageContext) this.getJspContext();
pageContext.getOut().write(content);//输出
}
}
3.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/simpleitcast" prefix="itcast"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>使用简单标签修改标签体内容</title>
</head>
<body>
<itcast:demo3>
aaa
</itcast:demo3>
</body>
</html>
说明:这里执行是先让标签体执行,然后让内容输出到我们自己定义的一个流中,然后我们从流中拿到数据,修改之后再输出给浏览器。
例:控制余下jsp页面是否执行
simpletag.tld
<tag>
<name>demo4</name>
<tag-class>cn.itcast.web.simpletag.SimpleTagDemo4</tag-class>
<body-content>scriptless</body-content>
</tag>
SimpleTagDemo4
package cn.itcast.web.simpletag;
import java.io.IOException;
import java.io.StringWriter;
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;
//控制标签体之后余下页面是否执行
public class SimpleTagDemo4 extends SimpleTagSupport{
@Override
public void doTag() throws JspException, IOException {
//只要此方法抛出这个异常,那么余下的页面将不会执行
throw new SkipPageException();
}
}
4.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/simpleitcast" prefix="itcast"%>
<itcast:demo4/>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>控制余下标签体不再执行</title>
</head>
<body>
aaa
</body>
</html>
可以看到此功能很容易实现。
5.开发带属性的标签
- 要想让一个自定义标签具有属性,通常要完成两个任务:
在标签处理器中编写每个属性对应的setter方法
在tld文件中描述标签的属性 - 为自定义标签定义属性时,每个属性都必须按照javaBean的属性命名方式,在标签处理器中定义属性名对应的setter方法,用来接收jsp页面调用自定义标签时传递进来的属性值。例如,属性url,在标签处理器类中就要定义相应的setUrl(String url)方法。
- 在标签处理器中定义相应的set方法后,jsp引擎在解析执行开始标签前,也就是调用doStartTag方法前,会调用set属性方法为标签设置属性。
例:通过属性控制标签体执行的次数
simpletag.tld
<tag>
<name>demo5</name>
<tag-class>cn.itcast.web.simpletag.SimpleTagDemo5</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>count</name>
<required>true</required><!-- 这表示此属性是必须的 -->
<!-- 此属性是否是一个运行时表达式,若为false则只能是静态的值,一般都是true -->
<rtexprvalue>true</rtexprvalue>
<type>java.lang.Integer</type><!-- 这个属性是规定参数的java属性,如果不正确则报错 -->
</attribute>
</tag>
SimpleTagDemo5
package cn.itcast.web.simpletag;
import java.io.IOException;
import java.io.StringWriter;
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;
//通过属性控制标签体的执行
public class SimpleTagDemo5 extends SimpleTagSupport{
//这里注意:我们顶一个的是一个int类型的,但是标签中我们使用的是字符串类型,
//这里引擎帮我们进行了转换,但是只支持八种基本类型的转换
private int count;
public void setCount(int count ){
this.count = count;
}
@Override
public void doTag() throws JspException, IOException {
for(int i = 0; i < count; i++){
this.getJspBody().invoke(null);
}
}
}
5.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/simpleitcast" prefix="itcast"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>通过属性控制标签体执行次数</title>
</head>
<body>
<!-- 如果在配置文件中rtexprvalue为true,则这里可以是一个表达式 -->
<itcast:demo5 count="5">
xxx
</itcast:demo5>
</body>
</html>
**注意:引擎只能转换八种基本类型数据,而不能转换复杂类型数据,如日期类型。但入股我们一定要传入复杂类型的话一般有两种方式:
<%
Data date = new Date();
request.setAttribute("date", date);
%>
<itcast:demo6 date="${date}">
xxx
</itcast:demo6>
或者:
<itcast:demo6 date="<%=date %>">
xxx
</itcast:demo6>
最后我们给出属性配置中的一些标签: