原文地址:http://blog.csdn.net/jiangwei0910410003/article/details/23915373html
今天来看一下自定义标签的内容,自定义标签是JavaWeb的一部分很是重要的核心功能,咱们以前就说过,JSP规范说的很清楚,就是Jsp页面中禁止编写一行Java代码,就是最好不要有Java脚本片断,下面就来看一下自定义标签的简介:java
自定义标签主要用于移除Jsp页面中的java代码。
移除jsp页面中的java代码,只须要完成两个步骤:
编写一个实现Tag接口的Java类,并覆盖doStartTag方法,把jsp页面中的java代码写到doStartTag方法中。
编写标签库描述符(tld)文件,在tld文件中对自定义标签进行描述。
完成以上操做,便可在JSP页面中导入和使用自定义标签。
快速入门:使用自定义标签输出客户机IP
查看tag接口api文档,分析自定义标签的执行流程。
web
下面来看一下一个简单的Demo使用自定义标签打印客户机的IP地址数据库
首先咱们自定义标签类:ViewIpTagapache
[java] view plaincopyapi
package com.weijia.traditionaltag; 数组
import java.io.IOException; 浏览器
import javax.servlet.http.HttpServletRequest; 缓存
import javax.servlet.jsp.JspException; 服务器
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.TagSupport;
/**
* 自定义标签,而后将这个标签映射到这个类:mytag:viewIP
* 记得将自定义的标签绑定到一个url上面,这个url通常是公司的网址
*
*/
public class ViewIpTag extends TagSupport{
private static final long serialVersionUID = 1L;
@Override
public int doStartTag() throws JspException {
//内置一个pageContext对象,咱们以前说到pageContext对象,它里面是封装了9个隐式对象
HttpServletRequest request = (HttpServletRequest)this.pageContext.getRequest();
JspWriter out = this.pageContext.getOut();
String ip = request.getRemoteAddr();
try {
out.print(ip);
} catch (IOException e) {
throw new RuntimeException(e);
}
return super.doStartTag();
}
}
自定义tld文件,mytag.tld
[html] view plaincopy
<?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 web-jsptaglibrary_2_0.xsd"
version="2.0">
<description>JSTL 1.1 core library</description>
<display-name>JSTL core</display-name>
<tlib-version>1.1</tlib-version>
<short-name>weijia</short-name>
<uri>http://www.weijia.cn/mytag</uri>
<!-- 显示IP地址 -->
<tag>
<description>
Catches any Throwable that occurs in its body and optionally
exposes it.
</description>
<name>viewIP</name>
<tag-class>com.weijia.traditionaltag.ViewIpTag</tag-class>
<body-content>empty</body-content>
</tag>
</taglib>
这里咱们将就自定义的标签类就注册好了,下面解释一下这些字段的含义:
首先看一下:
<short-name>这个标签是指定咱们定义标签的简称,这个做用不大
<uri>这个标签是给这个标签文件指定一个访问路径,这个路径咱们在Jsp页面中引入这个标签的时候须要用到
<tag-class>这个标签就是指定咱们自定义的标签类的全称
<body-content>这个标签代表自定义标签是否有标签体内容(empty:没有,JSP:有)
咱们注册以后标签类了,下面就在Jsp页面中进行使用了,这时候就要用到咱们以前说到的Jsp的指令中的taglib了,格式以下:
[html] view plaincopy
<%@ taglib uri="http://www.weijia.cn/mytag" prefix="mytag" %>
这个就将咱们定义的标签引入到Jsp页面中了,其中咱们uri属性的值就是咱们在标签订义文件mytag.tld中指定的那个uri那个标签值,固然这里的uri也能够直接指定mytag.tld文件的路径即:/WEB-INF/mytag.tld 也是能够的,其实咱们查看翻译以后的Jsp代码能够看到,无论用那种方式,他其实加载的时候都是去找真是路径中文件:
其中prefix属性的值是标签前缀名,这个名称就是咱们在Jsp页面中使用的标签前缀,这个值通常和tld文件的文件名是保持一致的
下面就是在Jsp中使用标签:
[html] view plaincopy
客户机的IP地址是:<mytag:viewIP/>
这样就是打印了客户机的IP地址,这里咱们在Jsp页面中就没有Java代码了
上面咱们介绍了一个简单的例子,下面咱们来详细看一下这个自定义标签的执行原理:
JSP引擎将遇到自定义标签时,首先建立标签处理器类的实例对象,而后按照JSP规范定义的通讯规则依次调用它的方法。
一、public void setPageContext(PageContext pc), JSP引擎实例化标签处理器后,将调用setPageContext方法将JSP页面的pageContext对象传递给标签处理器,标签处理器之后能够经过这个pageContext对象与JSP页面进行通讯。
二、public void setParent(Tag t),setPageContext方法执行完后,WEB容器接着调用的setParent方法将当前标签的父标签传递给当前标签处理器,若是当前标签没有父标签,则传递给setParent方法的参数值为null。
三、public int doStartTag(),调用了setPageContext方法和setParent方法以后,WEB容器执行到自定义标签的开始标记时,就会调用标签处理器的doStartTag方法。
四、public int doEndTag(),WEB容器执行完自定义标签的标签体后,就会接着去执行自定义标签的结束标记,此时,WEB容器会去调用标签处理器的doEndTag方法。
五、public void release(),一般WEB容器执行完自定义标签后,标签处理器会驻留在内存中,为其它请求服务器,直至中止web应用时,web容器才会调用release方法。
我能够查看咱们上面的例子翻译后的Jsp代码:
[java] view plaincopy
out.write("<body> \r\n");
out.write("\t<!-- 显示客户机的IP地址 -->\r\n");
out.write("\t客户机的IP地址是:");
if (_jspx_meth_mytag_005fviewIP_005f0(_jspx_page_context))
return;
out.write("\r\n");
out.write("\t\r\n");
再来看一下那个if中的方法的代码:
[java] view plaincopy
private boolean _jspx_meth_mytag_005fviewIP_005f0(PageContext _jspx_page_context)
throws Throwable {
PageContext pageContext = _jspx_page_context;
JspWriter out = _jspx_page_context.getOut();
// mytag:viewIP
com.weijia.traditionaltag.ViewIpTag _jspx_th_mytag_005fviewIP_005f0 = (com.weijia.traditionaltag.ViewIpTag) _005fjspx_005ftagPoo l_005fmytag_005fviewIP_005fnobody.get(com.weijia.traditionaltag.ViewIpTag.class);
_jspx_th_mytag_005fviewIP_005f0.setPageContext(_jspx_page_context);
_jspx_th_mytag_005fviewIP_005f0.setParent(null);
int _jspx_eval_mytag_005fviewIP_005f0 = _jspx_th_mytag_005fviewIP_005f0.doStartTag();
if (_jspx_th_mytag_005fviewIP_005f0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {
_005fjspx_005ftagPool_005fmytag_005fviewIP_005fnobody.reuse(_jspx_th_mytag_005fviewIP_005f0);
return true;
}
_005fjspx_005ftagPool_005fmytag_005fviewIP_005fnobody.reuse(_jspx_th_mytag_005fviewIP_005f0);
return false;
}
咱们能够看到,首先这个方法接收的是一个pageContext变量对象,这个和咱们以前说的同样,自定义标签类中有一个pageContext变量对象就能够操做其余对象了,下面来看一下那个方法的代码,首先他会去加载那个标签类,同时注意到首先是执行setPageContext()方法的,将pageContext变量传递到标签类中,而后看setParent()方法传递的是null,由于咱们打印IP的标签没有父标签的,接下来执行doStartTag()方法,而后再执行doEndTag()方法,这里咱们看到是作个判断,若是doEndTag方法返回的值是Tag.SKIP_PAGE的话,就是说余下的jsp页面不执行了,因此返回一个true,那么咱们看到上面的if判断代码中,若是这个方法返回true的话,直接return,下面的代码就不执行了。大致流程就是这样的。
下面咱们用自定义标签来实现一些特定需求的功能:
一、不执行标签体内容
自定义标签类:
[java] view plaincopy
package com.weijia.traditionaltag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
/**
* 是否输出标签体内容
* @author weijiang204321
*
*/
public class TagDemo1 extends TagSupport{
@Override
public int doStartTag() throws JspException {
return TagSupport.EVAL_BODY_INCLUDE;//输出标签体内容
//return TagSupport.SKIP_BODY;//不输出标签体内容
}
}
咱们看到只要doStartTag方法返回TagSupport.EVAL_BODY_INCLUDE常量,就会执行标签体内容,若是返回的是TagSupport.SKIP_BODY常量,就不会执行标签体内容,代码很简单。
下面咱们再来注册这个标签类:
[html] view plaincopy
<!-- 是否显示标签体 -->
<tag>
<description>
Catches any Throwable that occurs in its body and optionally
exposes it.
</description>
<name>demo1</name>
<tag-class>com.weijia.traditionaltag.TagDemo1</tag-class>
<body-content>JSP</body-content>
</tag>
由于是有标签体内容的,因此<body-content>标签的值是JSP
在Jsp页面中使用:
[html] view plaincopy
<!-- 不执行标签体 -->
<simpletag:demo1>
aaaa
</simpletag:demo1>
二、控制JSP余下页面的内容不执行
自定义标签类:
[java] view plaincopy
package com.weijia.traditionaltag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
/**
* 控制整个JSP是否输出
* @author weijiang204321
*
*/
public class TagDemo2 extends TagSupport{
@Override
public int doStartTag() throws JspException {
return super.doStartTag();
}
@Override
public int doEndTag() throws JspException {
return TagSupport.EVAL_PAGE;
//return TagSupport.SKIP_PAGE;不执行余下的jsp内容
}
}
当doEndTag方法返回的是TagSupport.EVAL_PAGE常量的话就执行jsp余下的内容,若是返回的是TagSupport.SKIP_PAGE常量的话就不执行jsp余下的内容
在tld文件中注册这个自定义标签类:
[html] view plaincopy
<!-- 控制是否显示jsp页面 -->
<tag>
<description>
Catches any Throwable that occurs in its body and optionally
exposes it.
</description>
<name>demo2</name>
<tag-class>com.weijia.traditionaltag.TagDemo2</tag-class>
<body-content>empty</body-content>
</tag>
在JSP页面中使用:
[html] view plaincopy
<!-- 不执行余下的页面内容 -->
<simpletag:demo2/>
这样使用以后,在这个标签以后的内容就不会执行了,咱们在上面分析源代码的时候已经解析过了。页面都不会含有余下的内容了,若是咱们将这个标签放在页面的第一行,那么这个页面就是一片空白,咱们在浏览器中查看页面的源代码,也是发现一片空白的,由于out对象没有进行print了
三、重复执行标签体内容
自定义标签体类:
[java] view plaincopy
package com.weijia.traditionaltag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
/**
* 控制标签体重复执行
* @author weijiang204321
*
*/
public class TagDemo3 extends TagSupport{
private int count = 5;
@Override
public int doStartTag() throws JspException {
return TagSupport.EVAL_BODY_INCLUDE;
}
@Override
public int doAfterBody() throws JspException {
count--;
if(count > 0){
return TagSupport.EVAL_BODY_AGAIN;//执行完以后接着执行doAfterBody()方法
}else{
return TagSupport.SKIP_BODY;
}
}
@Override
public int doEndTag() throws JspException {
return TagSupport.SKIP_BODY;
}
}
这里咱们须要在doAfterBody方法中操做了,由于这个方法的返回值为TagSupport.EVAL_BODY_AGAIN常量的话,这个方法还会被调用,直到这个方法返回TagSupport.SKIP_BODY,因此咱们这里控制标签体内容执行5次,咱们定义一个变量就能够了,而后控制doAfterBody方法的返回值,这里还要注意的是,在doStartTag方法中返回值是TagSupport.EVAL_BODY_INCLUDE常量,由于咱们要执行标签体内容的。
注册自定义标签类:
[html] view plaincopy
<!-- 控制标签体重复输出 -->
<tag>
<description>
Catches any Throwable that occurs in its body and optionally
exposes it.
</description>
<name>demo3</name>
<tag-class>com.weijia.traditionaltag.TagDemo3</tag-class>
<body-content>JSP</body-content>
</tag>
在Jsp页面中使用:
[html] view plaincopy
<!-- 重复执行标签体内容 -->
<simpletag:demo3>
aaaa
</simpletag:demo3>
这时候在页面中就会输出5个aaaa
四、修改标签体内容
自定义标签类:
[java] view plaincopy
package com.weijia.traditionaltag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.BodyTagSupport;
/**
* 修改标签体内容
* @author weijiang204321
*
*/
public class TagDemo4 extends BodyTagSupport{
@Override
public int doEndTag() throws JspException {
BodyContent bc = this.getBodyContent();//获取标签体内容对象
String content = bc.getString();
content = content.toUpperCase();//将标签体内容转成大写
try {
this.pageContext.getOut().write(content);//在将转化以后的内容输出到浏览器中
} catch (IOException e) {
throw new RuntimeException(e);
}
return BodyTagSupport.EVAL_BODY_INCLUDE;
}
@Override
public int doStartTag() throws JspException {
return BodyTagSupport.EVAL_BODY_BUFFERED;//这里返回缓存标签体内容常量
}
}
这里咱们要注意的是,咱们继承的是BodyTagSupport类了,要在doStartTag方法中返回BodyTagSupport.EVAL_BODY_BUFFERED常量,才能够取出标签体内容缓存,而后再doEndTag方法中取出标签体内容而后进行操做以后再写到浏览器中。
注册咱们的自定义标签类:
[html] view plaincopy
<!-- 修改标签体内容 -->
<tag>
<description>
Catches any Throwable that occurs in its body and optionally
exposes it.
</description>
<name>demo4</name>
<tag-class>com.weijia.traditionaltag.TagDemo4</tag-class>
<body-content>JSP</body-content>
</tag>
在Jsp页面中使用:
[html] view plaincopy
<!-- 修改标签体内容 -->
<simpletag:demo4>
bbbb
</simpletag:demo4>
这时候在浏览器中输出的是:BBBB
上面说到的是Jsp2.0之前的自定义标签的方法,从Jsp2.0之后,咱们就开始使用了简单标签类SimpleTagSupport,由于咱们能够看到Jsp2.0以前的是传统标签类的话,要想实现不一样的功能,还须要继承不一样的类,好比:TagSupport,BodyTagSupport,这样会增长开发成本,因此Jsp2.0以后引入了简单标签类SimpleTagSupport了,那么下面咱们先来看一下简单标签的执行流程:
因为传统标签使用三个标签接口来完成不一样的功能,显得过于繁琐,不利于标签技术的推广, SUN公司为下降标签技术的学习难度,在JSP 2.0中定义了一个更为简单、便于编写和调用的SimpleTag接口来实现标签的功能。实现SimpleTag接口的标签一般称为简单标签。简单标签共定义了5个方法:
setJspContext方法
setParent和getParent方法
setJspBody方法
doTag方法
下面来看一下这些方法的解释
setJspContext方法
用于把JSP页面的pageContext对象传递给标签处理器对象
setParent方法
用于把父标签处理器对象传递给当前标签处理器对象
getParent方法
用于得到当前标签的父标签处理器对象
setJspBody方法
用于把表明标签体的JspFragment对象传递给标签处理器对象
doTag方法
用于完成全部的标签逻辑,包括输出、迭代、修改标签体内容等。在doTag方法中能够抛出javax.servlet.jsp.SkipPageException异常,用于通知WEB容器再也不执行JSP页面中位于结束标记后面的内容,这等效于在传统标签的doEndTag方法中返回Tag.SKIP_PAGE常量的状况。
当web容器开始执行标签时,会调用以下方法完成标签的初始化
WEB容器调用标签处理器对象的setJspContext方法,将表明JSP页面的pageContext对象传递给标签处理器对象。
WEB容器调用标签处理器对象的setParent方法,将父标签处理器对象传递给这个标签处理器对象。注意,只有在标签存在父标签的状况下,WEB容器才会调用这个方法。
若是调用标签时设置了属性,容器将调用每一个属性对应的setter方法把属性值传递给标签处理器对象。若是标签的属性值是EL表达式或脚本表达式,则WEB容器首先计算表达式的值,而后把值传递给标签处理器对象。
若是简单标签有标签体,容器将调用setJspBody方法把表明标签体的JspFragment对象传递进来。
执行标签时:
容器调用标签处理器的doTag()方法,开发人员在方法体内经过操做JspFragment对象,就能够实现是否执行、迭代、修改标签体的目的。
javax.servlet.jsp.tagext.JspFragment类是在JSP2.0中定义的,它的实例对象表明JSP页面中的一段符合JSP语法规范的JSP片断,这段JSP片断中不能包含JSP脚本元素。
WEB容器在处理简单标签的标签体时,会把标签体内容用一个JspFragment对象表示,并调用标签处理器对象的setJspBody方法把JspFragment对象传递给标签处理器对象。JspFragment类中只定义了两个方法,以下所示:
getJspContext方法
用于返回表明调用页面的JspContext对象.
public abstract void invoke(java.io.Writer out)
用于执行JspFragment对象所表明的JSP代码片断
参数out用于指定将JspFragment对象的执行结果写入到哪一个输出流对象中,若是传递给参数out的值为null,则将执行结果写入到JspContext.getOut()方法返回的输出流对象中。(简而言之,能够理解为写给浏览器)
JspFragment.invoke方法能够说是JspFragment最重要的方法,利用这个方法能够控制是否执行和输出标签体的内容、是否迭代执行标签体的内容或对标签体的执行结果进行修改后再输出。例如:
在标签处理器中若是没有调用JspFragment.invoke方法,其结果就至关于忽略标签体内容;
在标签处理器中重复调用JspFragment.invoke方法,则标签体内容将会被重复执行;
若想在标签处理器中修改标签体内容,只需在调用invoke方法时指定一个可取出结果数据的输出流对象(例如StringWriter),让标签体的执行结果输出到该输出流对象中,而后从该输出流对象中取出数据进行修改后再输出到目标设备,便可达到修改标签体的目的。
下面咱们在来使用简单标签来实现上面的四个案例:
一、是否输出标签体内容
自定义标签类:
[java] view plaincopy
package com.weijia.sampletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* 控制标签体是否执行
* @author weijiang204321
*
*/
public class SimpleTagDemo1 extends SimpleTagSupport{
@Override
public void doTag() throws JspException, IOException {
JspFragment jf = this.getJspBody();
//至关于jf.invoke(null);
jf.invoke(this.getJspContext().getOut());
//这里若是不想输出标签体内容的话,只须要不调用invoke方法便可
}
}
咱们看到这个和传统标签不同,这里只有一个doTag方法了,在这个方法中咱们经过是否调用jf.invoke方法来控制是否执行标签体内容,这里还有一个问题是若是咱们想输出标签体内容,只须要调用invoke方法便可,同时这个方法传递的参数是一个Writer对象,因此若是咱们想将标签体内容输出到浏览器中只须要传递out对象到这个方法便可,可是若是将这个方法的参数设置成null的话也是向浏览器中输出标签体内容的
注册标签体类:
[html] view plaincopy
<!-- 是否显示标签体 -->
<tag>
<description>
Catches any Throwable that occurs in its body and optionally
exposes it.
</description>
<name>demo1</name>
<tag-class>com.weijia.simpletag.SimpleTagDemo1</tag-class>
<body-content>scriptless</body-content>
</tag>
这里的注册和传统标签不同的就是<body-content>标签的值是scriptless而不是JSP了
在JSP页面使用:
[html] view plaincopy
<simpletag:demo1>
aaa
</simpletag:demo1>
二、控制Jsp余下内容是否输出
自定义标签类:
[java] view plaincopy
package com.weijia.sampletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.SkipPageException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* 控制不执行余下的jsp内容
* @author weijiang204321
*
*/
public class SimpleTagDemo2 extends SimpleTagSupport{
@Override
public void doTag() throws JspException, IOException {
//直接抛出异常就不会执行余下的jsp内容
throw new SkipPageException();
}
}
咱们只要在doTag方法中抛出一个SkipPageException异常就能够实现不执行余下的Jsp内容
注册标签类:
[html] view plaincopy
<!-- 控制是否显示jsp页面 -->
<tag>
<description>
Catches any Throwable that occurs in its body and optionally
exposes it.
</description>
<name>demo2</name>
<tag-class>com.weijia.simpletag.SimpleTagDemo2</tag-class>
<body-content>empty</body-content>
</tag>
在Jsp页面中使用:
[html] view plaincopy
<simpletag:demo2/>
在这个标签以后的jsp页面内容就不会输出了
三、标签体重复执行
自定义标签类:
[java] view plaincopy
package com.weijia.sampletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* 迭代标签体
* @author weijiang204321
*
*/
public class SimpleTagDemo3 extends SimpleTagSupport{
@Override
public void doTag() throws JspException, IOException {
JspFragment jf = this.getJspBody();
for(int i=0;i<5;i++){
jf.invoke(null);
}
}
}
这里就比传统标签的操做简单了,直接写在for循环中,在循环中调用invoke方法便可
注册标签类:
[html] view plaincopy
<!-- 控制标签体重复输出 -->
<tag>
<description>
Catches any Throwable that occurs in its body and optionally
exposes it.
</description>
<name>demo3</name>
<tag-class>com.weijia.simpletag.SimpleTagDemo3</tag-class>
<body-content>scriptless</body-content>
</tag>
在Jsp页面中使用:
[html] view plaincopy
<simpletag:demo3>
aaaa
</simpletag:demo3>
四、修改标签体内容
[java] view plaincopy
package com.weijia.sampletag;
import java.io.IOException;
import java.io.StringWriter;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* 修改标签体
* @author weijiang204321
*
*/
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.toString();
content = content.toUpperCase();
this.getJspContext().getOut().write(content);
}
}
咱们将StringWriter对象传递到invoke方法中,而后再经过StringWriter对象获得标签体内容,进行操做,而后再经过out对象输出到浏览器中。
注册标签类:
[html] view plaincopy
<!-- 修改标签体内容 -->
<tag>
<description>
Catches any Throwable that occurs in its body and optionally
exposes it.
</description>
<name>demo4</name>
<tag-class>com.weijia.simpletag.SimpleTagDemo4</tag-class>
<body-content>scriptless</body-content>
</tag>
在Jsp页面中使用:
[html] view plaincopy
<!-- 修改标签体内容 -->
<simpletag:demo4>
bbbb
</simpletag:demo4>
这样咱们就介绍了怎样使用传统标签和简单标签来编写本身的标签,这样咱们就能够将任何java代码移到标签类中,而后在jsp页面中使用标签便可。那么最后再来看一下传统标签和简单标签的继承关系:
1. JspTag接口
JspTag接口是全部自定义标签的父接口,它是JSP2.0中新定义的一个标记接口,没有任何属性和方法。JspTag接口有Tag和SimpleTag两个直接子接口,JSP2.0之前的版本中只有Tag接口,因此把实现Tag接口的自定义标签也叫作传统标签,把实现SimpleTag接口的自定义标签叫作简单标签。本书中若是没有特别说明,自定义标签泛指传统标签。
2. Tag接口
图6.5中的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页面中位于自定义标签的结束标记后面的内容。
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. 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接口将简化开发工做。
4. SimpleTag接口
SimpleTag接口是JSP2.0中新增的一个标签接口。因为传统标签使用三个标签接口来完成不一样的功能,显得过于繁琐,不利于标签技术的推广,所以,SUN公司为下降标签技术的学习难度,在JSP 2.0中定义了一个更为简单、便于编写和调用的SimpleTag接口。SimpleTag接口与传统标签接口最大的区别在于,SimpleTag接口只定义了一个用于处理标签逻辑的doTag方法,该方法在WEB容器执行自定义标签时调用,而且只被调用一次。那些使用传统标签接口所完成的功能,例如是否执行标签体、迭代标签体、对标签体内容进行修改等功能均可以在doTag方法中完成。关于SimpleTag接口的详细介绍本书将在第7章详细讲解。
在JSP API中也提供了SimpleTag接口的实现类SimpleTagSupport,读者在编写简单标签时,能够继承和扩展SimpleTagSupport类,这相比实现SimpleTag接口将简化开发工做。
为方便读者往后查询传统标签接口中的各个方法能够返回的返回值,笔者在表6.1列举了Tag接口、IterationTag接口和BodyTag接口中的主要方法及它们分别能够返回的返回值的说明。
下面咱们来看一下如何开发一个具备属性的自定义标签的内容:
要想让一个自定义标签具备属性,一般须要完成两个任务:
在标签处理器中编写每一个属性对应的setter方法
在TLD文件中描术标签的属性
为自定义标签订义属性时,每一个属性都必须按照JavaBean的属性命名方式,在标签处理器中定义属性名对应的setter方法,用来接收JSP页面调用自定义标签时传递进来的属性值。 例如属性url,在标签处理器类中就要定义相应的setUrl(String url)方法。
在标签处理器中定义相应的set方法后,JSP引擎在解析执行开始标签前,也就是调用doStartTag方法前,会调用set属性方法,为标签设置属性。
在TLD文件中的描述规格是为:
<tag>元素的<attribute>子元素用于描述自定义
标签的一个属性,自定义标签所具备的每一个属性
都要对应一个<attribute>元素 。
<attribute>
<description>description</description>
<name>aaaa</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>ObjectType</type>
</attribute>
其中的各个属性值的含义以下:
那么下面就来看一个实例,经过一个属性值来控制标签体的内容输出的次数:
自定义标签类:
[java] view plaincopy
package com.weijia.propertytag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class PropertyTag extends SimpleTagSupport{
private int count = 0;
public void setCount(int count){
this.count = count;
}
@Override
public void doTag() throws JspException, IOException {
JspFragment jf = this.getJspBody();
for(int i=0;i<count;i++){
jf.invoke(null);
}
}
}
这里须要定义一个变量来记录执行的次数,同时还须要提供set方法
注册这个标签:
[html] view plaincopy
<tag>
<description>
Catches any Throwable that occurs in its body and optionally
exposes it.
</description>
<name>demo</name>
<tag-class>com.weijia.propertytag.PropertyTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<description>
Name of the exported scoped variable for the
exception thrown from a nested action. The type of the
scoped variable is the type of the exception thrown.
</description>
<name>count</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
咱们这里设置这个属性的名称是count,并且这个属性在标签中是必须设置的,同时这个标签可使用表达式
在Jsp页面中使用:
[html] view plaincopy
<propertytag:demo count="9">
aaaaa
</propertytag:demo>
在页面中输出9次aaaaa
虽然咱们这里看到了输出的很简单,设置也很简单,可是这里面仍是有不少内容的
首先来看一下,咱们在Jsp页面中输入的是字符串,可是咱们定义的count是int类型,没有报错,因此这里他作了类型转换,固然这个不是可以转换全部的类型的,只能转化8中基本类型,好比咱们定义了一个属性是Date类型的,当咱们在Jsp页面中传递"1990-08-01"这样就会报错的,固然咱们可使用脚本表达式进行属性的赋值是能够的,好比:
[html] view plaincopy
<propertytag:demo count="<%=new Date()%>">
aaaaa
</propertytag:demo>
在来看一下,他是怎么定位到属性count的,这个其实在学习Java基础知识的时候就说过,在学习JavaBean的相关知识的时候,咱们知道一个Bean对象的属性的概念,好比这里咱们定义了一个count变量,同时设置了他的set方法,那么这个count就是一个属性,可是属性的概念不是经过变量名来定义的,而是经过set方法来定义的,好比咱们这里能够将count变量名改为counts,可是setCount方法名不变,咱们运行程序,仍然不会报错的,可是咱们将setCount方法名改为setCounts的时候,运行程序就报错了,缘由也很好理解,他在进行变量count进行设置值的时候,会经过set方法来进行设置,这时候就会经过setXXX来找到相对应的set方法,从而可以对每一个变量的值设置正确。这个相关内容其实咱们在以前介绍<jsp:setProperty>标签的时候讲到过,这个技术在JavaWeb中很经常使用的,专门用来操做Bean对象的(内省技术BeanUtils)
介绍完自定义标签的属性的相关知识后,接下来咱们就来看看JSTL给咱们提供的标签库,JSTL标签库能够分为如下几种:
1.核心标签库
2.国际化标签
3.数据库标签
4.XML标签
5.JSTL函数(EL函数)
如今用到最多的就是核心标签库和JSTL函数库了,其余的三种标签不是很经常使用(几乎抛弃),因此这里就不作太多的介绍。
下面就先来看一下JSTL的核心标签库了,咱们在使用JSTL标签库的时候须要导入两个jar:jstl.jar和standard.jar
咱们在导入包以后咱们能够查看他的tld标签描述文档的:
咱们看到他的核心库是c.tld,函数库是fn.tld,这样咱们就能够经过这些标签描述文档中查找到有哪些标签可使用,以及使用的方法
咱们看到了c.tld标签说明文件的uri是http://java.sun.com/jsp/jstl/core,因此咱们若是要使用这个标签的话就只要在jsp中引入便可:
[html] view plaincopy
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
前期工做搞定了,下面就来详细看一下每一个标签的具体使用方法:
一、c:out标签的使用:
<c:out> 标签用于输出一段文本内容到pageContext对象当前保存的“out”对象中
用法:
[html] view plaincopy
<!-- c:out标签 -->
<!-- 输出给浏览器,可是不必这样输出是没意义的,能够直接输出的aaaaaa -->
<c:out value="aaaa"></c:out>
<!-- 转义以后输出/默认值,这样c:out标签才有意义-->
<c:out value="<a href=''>点点</a>" default="aaa" escapeXml="true"></c:out>
<%
request.setAttribute("data","xxx");
%>
<c:out value="${data}" default="aaa"></c:out>
这个标签很简单的,就是就是向浏览器中直接输出内容的,那么这个和使用EL表达式输出有什么区别呢,他有什么特别的好处呢?
他的好处就在于default和escapeXml这两个属性的使用,default属性能够设置输出的默认值,咱们知道EL表达式在各个域中若是找不到属性值就会输出空字符串,可是咱们经过这个属性就能够设置当从全部的域中找不到相应的属性值,就会输出默认值,同时还有一个escapeXml这个属性值,这个属性进行输出内容进行html转义,咱们以前都是经过一个方法进行转义的:
[java] view plaincopy
private String htmlFilter(String message){
if(message == null){
return null;
}
char[] content = new char[message.length()];
message.getChars(0, message.length(), content, 0);
StringBuffer result = new StringBuffer(message.length()+50);
for(int i=0;i<content.length;i++){
switch(content[i]){
case '<':
result.append("<");
break;
case '>':
result.append(">");
break;
case '&':
result.append("&");
break;
case '"':
result.append(""");
break;
default:
result.append(content[i]);
}
}
return result.toString();
}
只要设置这个属性值为true的话,咱们就不须要手动的进行转义了,因此说这个标签仍是有他特定的功能的,可不能忘记他呀!
二、c:set标签
<c:set>标签用于把某一个对象存在指定的域范围内,或者设置Web域中的java.util.Map类型的属性对象或JavaBean类型的属性对象的属性
用法:
[html] view plaincopy
<!-- c:set标签 -->
<!-- 向page域中存入到xxx -->
<c:set var="data" value="xxx" scope="page"/>
${data}
<!-- 向map中存入数据 -->
<%
Map map = new HashMap();
request.setAttribute("map",map);
%>
<c:set property="name" value="uuu" target="${map}"/>
${map.name}
<!-- 向javabean中存入数据 -->
<%
Person p = new Person();
request.setAttribute("p",p);
%>
<c:set property="name" value="uuu" target="${p}"/>
${p.name}
这个标签能够指定在四个域中设置属性值,同时设置的对象不只只有基本类型,还能够设置对象类型,集合类型
三、c:remove标签
这个标签能够在四个域中删除指定的属性
其语法格式以下:
<c:remove var="varName"
[scope="{page|request|session|application}"] />
用法:
[html] view plaincopy
<!-- c:remove标签 -->
<!-- 删除属性 -->
<%
request.setAttribute("data","xxx");
%>
<c:remove var="data" scope="request"/>
四、c:catch标签
<c:catch>标签用于捕获嵌套在标签体中的内容抛出的异常,
其语法格式以下:
<c:catch [var="varName"]>nested actions</c:catch>
var属性用于标识<c:catch>标签捕获的异常对象,它将保存在page这个Web域中
用法:
[html] view plaincopy
<!-- c:catch异常捕获 -->
<!--var是存入异常对象的关键字 -->
<c:catch var="myex">
<%
int x = 1/0;
%>
</c:catch>
<!-- 异常对象必需要有message属性 -->
${myex.message}
五、c:if标签
这个标签是控制标签内容的输出
用法:
[html] view plaincopy
<!-- c:if -->
<!-- 将判断结果以aaa为关键字存入到域中 -->
<%
request.setAttribute("user",null);
%>
${user}
<c:if var="aaa" test="${user == null}",scope="page"/>
${aaa}
同时能够将判断条件的值使用变量存起来
六、c:choose/c:when/c:other标签
这三个标签是一块儿使用的,实现效果和if...else是同样的
用法:
[html] view plaincopy
<!-- c:choose标签 -->
<c:choose>
<c:when test="${true}">
aaaa
</c:when>
<c:otherwise>
bbb
</c:otherwise>
</c:choose>
七、c:forEach标签
这个标签是用来迭代数据的,以前用过这个标签,可是他还有不少强大的功能:
用法:
[html] view plaincopy
<!-- c:forEach -->
<%
List list = new ArrayList();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
request.setAttribute("list",list);
%>
<c:forEach var="str" items="${list}">
${str}
</c:forEach>
<!-- 设置步长,分页功能 -->
<c:forEach var="num" items="${list}" begin="1" end="9" step="1">
${num}
</c:forEach>
<!-- 记录迭代变量的值 -->
<c:forEach var="str" items="${list}" varStatus="status">
${status.count}
</c:forEach>
这里咱们经过这个标签迭代输出list集合中的数据,这个功能是最基础的,也是最简单的
他还有一些属性能够设置迭代的开始位置和结束位置以及迭代的步长信息
同时还有一个属性varStatus能够记录当前迭代信息,他保存的是一个对象,可是这个对象有如下的属性值:
这个属性的功能咱们能够实现表格中的奇数和偶数行的不一样显示
八、c:param标签
这个标签是设置参数值的,这个标签是不能单独使用的,他是结合c:url或者c:redirect标签使用
九、c:url标签
这个标签能够实现url重写(在介绍Session的时候)和url编码
用法:
[html] view plaincopy
<!-- c:url标签 -->
<!-- url重写 -->
<c:url var="url" value="JspDemo/1.jsp"/>
<a href='${url}'>购买</a>
</c:url>
<!-- url标签直接输出url -->
<a href='<c:url value="/1.jsp"'>点点</a>
<!-- c:url构建参数(自动url编码) -->
<c:url var="index" value="/1.jsp">
<c:param name="name" value="中国"/>
</c:url>
10.c:redirect标签
这个标签是用来实现重定向的
用法:
[html] view plaincopy
<c:redirect url="JspDemo/1.jsp">
<c:param name="name" value="jiangwei"></c:param>
</c:redirect>
咱们在以前介绍的jsp标签中只有<jsp:forword>转发标签,而没有重定向的标签,那么这个就有重定向的标签了。
以上咱们介绍了JSTL中的核心标签库的相关知识,下面再来看一下JSTL的函数库,其实这部份内容在咱们以前的EL表达式一篇文章中的最后部分做了详细讲解:http://blog.csdn.net/jiangwei0910410003/article/details/23748131
下面还有一个重要的内容就是咱们要经过咱们上面学习到的自定义标签的知识来开发一套相似于JSTL的标签库,并将其进行打包,给其余项目使用,这里咱们就是用简单标签了,而不是用传统标签。
一、开发if标签
[java] view plaincopy
package com.weijia.iftag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class IfTag extends SimpleTagSupport{
private boolean test;
public void setTest(boolean test){
this.test = test;
}
@Override
public void doTag() throws JspException, IOException {
if(test){
this.getJspBody().invoke(null);
}
}
}
咱们定义一个boolean类型的test变量来保存判断值,而后经过这个判断值来控制是否输出标签体内容
二、开发choose/when/otherwise标签,在开发这套标签的时候,咱们会发现遇到一个难处就是多个标签之间须要进行通讯,好比说一个标签执行了标签体内容,那么其余标签体的内容就不能执行了,因此这里须要给多个标签体外面在套一个父标签(这也是一个父标签开发的案例),经过那么每一个子标签能够经过父标签中的一个变量来判断是否执行本身的标签体,原理就是这样的,下面是实现类,其中ChooseTag是父标签了,WhenTag和OtherWiseTag是子标签,同时给WhenTag标签订义一个boolean属性来接收外界的判断条件
ChooseTag:
[java] view plaincopy
package com.weijia.choosetag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class ChooseTag extends SimpleTagSupport{
private boolean isDo;
public boolean isDo() {
return isDo;
}
public void setDo(boolean isDo) {
this.isDo = isDo;
}
@Override
public void doTag() throws JspException, IOException {
this.getJspBody().invoke(null);
}
}
WhenTag:
[java] view plaincopy
package com.weijia.choosetag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class WhenTag extends SimpleTagSupport {
private boolean test;
public void setTest(boolean test){
this.test = test;
}
@Override
public void doTag() throws JspException, IOException {
//获取到父标签
ChooseTag parentTag = (ChooseTag)this.getParent();
if(test && !parentTag.isDo()){
this.getJspBody().invoke(null);
parentTag.setDo(true);
}
}
}
OtherWiseTag:
[java] view plaincopy
package com.weijia.choosetag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class OtherWiseTag extends SimpleTagSupport{
@Override
public void doTag() throws JspException, IOException {
ChooseTag parentTag = (ChooseTag)this.getParent();
if(parentTag.isDo()){
this.getJspBody().invoke(null);
}
}
}
同时须要在tld文件中对这三个标签类进行描述:
[html] view plaincopy
<tag>
<name>choose</name>
<tag-class>com.weijia.choosetag.ChooseTag</tag-class>
<body-content>scriptless</body-content>
</tag>
<tag>
<name>when</name>
<tag-class>com.weijia.choosetag.WhenTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>test</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>otherwise</name>
<tag-class>com.weijia.choosetag.OtherWiseTag</tag-class>
<body-content>scriptless</body-content>
</tag>
三、for:Each标签:
[java] view plaincopy
package com.weijia.foreach;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import sun.text.CompactShortArray.Iterator;
/**
* ForEach标签
* @author weijiang204321
*
*/
public class ForEachTag extends SimpleTagSupport{
private Object items;
private String var;
private Collection collection;
public void setItems(Object items) {
this.items = items;
if(items instanceof Collection){
collection = (Collection)items;
}
if(items instanceof Map){
Map map = (Map) items;
collection = map.entrySet();
}
//这种判断数组的方式是不适合基本类型数组的,
//同时Arrays.asList方法接收的是可变参数Object...因此对于基本类型的数组是没有效果的
/*if(items instanceof Object[]){
Object[] obj = (Object[])items;
collection = Arrays.asList(obj);
}*/
//使用反射技术能够判断基本类型的数据类型
if(items.getClass().isArray()){
int len = Array.getLength(items);
collection = new ArrayList();
for(int i=0;i<len;i++){
collection.add(Array.get(items, i));
}
}
}
public void setVar(String var) {
this.var = var;
}
@Override
public void doTag() throws JspException, IOException {
Iterator it = (Iterator) collection.iterator();
while(it.hasNext()){
Object value = it.next();
this.getJspContext().setAttribute(var, value);
}
this.getJspBody().invoke(null);
}
}
这个是实现了forEach标签的,在tld文件中进行描述一下:
[html] view plaincopy
<tag>
<name>forEach</name>
<tag-class>com.weijia.foreach.ForEachTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>var</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>items</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
关于这个forEach标签的知识咱们要好好的解释一下,由于这里面有不少须要注意的地方, 这里咱们定义了一个Object类型的items,这个变量是用来接收迭代对象的,还定义了一个String类型的var,这个是用来将每次迭代以后的值存入到域中的key名称。还定义了一个Collection类型的变量,这个变量只是一个辅助的变量,用来将迭代对象转化成集合类型(map类型能够转换、数组也能够转化)。这样咱们就能够统一进行处理了。
咱们看到setItems方法中,咱们首先判断这个迭代对象是否是集合类型的,是的话,直接赋值到collections变量,若是不是,在判断是否是Map类型的,若是是的话,就将Map类型的变量转化成collections,若是不是,在判断是否是数组对象Object[],是的话,就进行转化,这里使用了Arrays.asList(T...)这个方法,关于这个方法,咱们看到他的参数是一个可变的对象类型参数,看着这个样的判断是能够了,涵盖了全部的迭代对象类型,可是咱们其实发现了一个问题,那就是在最后一次判断数组的时候,咱们发现这个是对象类型的数组,那么咱们若是传递基本类型数组的话,会是什么样的状况呢?其实咱们知道基本类型数组其实就是一个Object对象,因此不是Object[],那么这里的涵盖的范围就有问题了,咱们这里还须要单独的判断基本类型的数组,而后进行操做,那么咱们来看一下jstl中的forEach标签的定义吧:
咱们将standard.jar进行解压,而后找到forEach标签的定义类:咱们能够从c.tld文件中找到forEach对应的类:
org.apache.taglibs.standard.tag.rt.core.ForEachTag,这时候咱们须要去下载一个反编译工具,可以查看class文件的,叫作:jd-gui.exe
而后经过这个工具打开ForEachTag.class:
[java] view plaincopy
package org.apache.taglibs.standard.tag.rt.core;
import java.util.ArrayList;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.jstl.core.LoopTag;
import javax.servlet.jsp.tagext.IterationTag;
import org.apache.taglibs.standard.tag.common.core.ForEachSupport;
public class ForEachTag extends ForEachSupport
implements LoopTag, IterationTag
{
public void setBegin(int paramInt)
throws JspTagException
{
this.beginSpecified = true;
this.begin = paramInt;
validateBegin();
}
public void setEnd(int paramInt)
throws JspTagException
{
this.endSpecified = true;
this.end = paramInt;
validateEnd();
}
public void setStep(int paramInt)
throws JspTagException
{
this.stepSpecified = true;
this.step = paramInt;
validateStep();
}
public void setItems(Object paramObject)
throws JspTagException
{
if (paramObject == null)
this.rawItems = new ArrayList();
else
this.rawItems = paramObject;
}
}
咱们发现ForEach实现了IterationTag接口,咱们在前面看到传统标签和简单标签类结构系统图中看到,这个适用于迭代输出标签体内容的接口,并且这个是传统标签,可见jstl中的ForEach标签是使用传统标签来实现的,咱们知道若是是迭代的话,会实现相应的迭代方法,可是咱们发现ForEachTag类中只有get/set方法,因此咱们这时候能够查看他的父类ForEachSupport中核心的方法:
[java] view plaincopy
protected ForEachIterator supportedTypeForEachIterator(Object paramObject)
throws JspTagException
{
ForEachIterator localForEachIterator;
if ((paramObject instanceof Object[]))
localForEachIterator = toForEachIterator((Object[])paramObject);
else if ((paramObject instanceof boolean[]))
localForEachIterator = toForEachIterator((boolean[])paramObject);
else if ((paramObject instanceof byte[]))
localForEachIterator = toForEachIterator((byte[])paramObject);
else if ((paramObject instanceof char[]))
localForEachIterator = toForEachIterator((char[])paramObject);
else if ((paramObject instanceof short[]))
localForEachIterator = toForEachIterator((short[])paramObject);
else if ((paramObject instanceof int[]))
localForEachIterator = toForEachIterator((int[])paramObject);
else if ((paramObject instanceof long[]))
localForEachIterator = toForEachIterator((long[])paramObject);
else if ((paramObject instanceof float[]))
localForEachIterator = toForEachIterator((float[])paramObject);
else if ((paramObject instanceof double[]))
localForEachIterator = toForEachIterator((double[])paramObject);
else if ((paramObject instanceof Collection))
localForEachIterator = toForEachIterator((Collection)paramObject);
else if ((paramObject instanceof Iterator))
localForEachIterator = toForEachIterator((Iterator)paramObject);
else if ((paramObject instanceof Enumeration))
localForEachIterator = toForEachIterator((Enumeration)paramObject);
else if ((paramObject instanceof Map))
localForEachIterator = toForEachIterator((Map)paramObject);
else if ((paramObject instanceof String))
localForEachIterator = toForEachIterator((String)paramObject);
else
localForEachIterator = toForEachIterator(paramObject);
return localForEachIterator;
}
咱们发现他会对每一个传递进来的对象进行判断,而后进行一些操做。同时对基本类型进行判断,可是咱们发现这样的代码是有点很差看,咱们为了体现出咱们的技术,咱们这里能够将代码改一下,而且是实现的比他还要好,就是如下的代码段:
[java] view plaincopy
//使用反射技术能够判断基本类型的数据类型
if(items.getClass().isArray()){
int len = Array.getLength(items);
collection = new ArrayList();
for(int i=0;i<len;i++){
collection.add(Array.get(items, i));
}
}
这里咱们使用反射技术来判断是否是数组类型,这里能够判断是基本类型数组仍是对象类型数组,而后再使用Array这个工具类进行操做数组中的元素,这样咱们看到这样的代码就比jstl中的代码简介明了,并且技术上也体现出一点高超。
下面咱们在tld文件中进行描述一下:
[html] view plaincopy
<tag>
<name>forEach</name>
<tag-class>com.weijia.foreach.ForEachTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>var</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>items</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
在Jsp页面中使用:
[html] view plaincopy
<span> </span><%
List list = new ArrayList();
list.add("aaa");
list.add("bbb");
String[] strAry = new String[]{"aaa","bbb","ccc"};
int[] intAry = new int[]{1,2,3,6};
%>
<c:forEach var="item" items="<%=list%>">
${item}
</c:forEach>
<br>
<c:forEach var="item" items="<%=strAry%>">
${item}
</c:forEach>
<br>
<c:forEach var="item" items="<%=intAry%>">
${item}
</c:forEach>
咱们使用Java代码模拟一个集合,对象类型的数组,基本类型数组,而后进行显示,显示结果
在这里额外的插一句:我在作这个实验的时候犯了一个很低级的错误,就是在使用标签的时候,给items赋值的时候我已开始使用的是EL表达式(${list}),而后老是报空指针异常,纠结了好长时间,发现items是null,那么就是没有传递对象给他,后来发现EL表达式是从域中取数据的,咱们没有将list存入到任何域中,因此确定拿不到了,这时候改用脚本表达式就能够了,由于脚本表达式就是能够去取页面中脚本片断中定义的变量值的,因此最后发现这个错误真的很低级的!!!
以上就是咱们实现了相似于jstl中的标签库的一些标签的功能,这里咱们能够联系一下咱们以前在开始介绍自定标签的时候实现的四个案例:
一、控制标签体是否输出
二、控制标签体重复输出
三、控制余下的Jsp页面是否显示
四、修改标签体内容
其实咱们会发现,上面实现的If标签其实就是第一个案例的体现,choose/when/otherwise标签就是第一个案例的体现,可是这里面还有一个功能就是父标签的编写,forEach标签是第二个案例的实现以及第四个案例的实现。因此说咱们为何一开始要介绍那四个案例,实际上是为这部份内容作铺垫的。
下面咱们来进行打包操做了,咱们须要将咱们定义的tld文件一块儿打包,这个打包也是很简单的,咱们只须要新建一个Java项目,将咱们定义好的标签类都拷贝过去,同时在项目中新建一个META-INF文件夹,在将咱们定义的的tld文件拷贝进去,虽然会提示不少错误(由于是Java项目,不是Web项目不少类是找不到的)可是咱们不理会,由于咱们知道咱们的类的逻辑和语法是没有错误的,只是找不到相应的类,这时候咱们进行打包,必定要将META-INF文件夹一块儿打包进去,这时候咱们就可使用这个咱们本身定义的标签库包了。
总结:好了,JSTL和自定义标签的相关知识就介绍到这里的,同时咱们JavaWeb学习篇也到这里就结束了,这一系列的文章写了半个月吧,从中学习到了不少的知识,在此将每一个知识点整理出来供你们分享,若是有什么不正确的地方还请提出,我当即作出修改。谢谢!