第一章:taglib 原理和实现
1。问题:Tag究竟是什么?如何实现一个Tag?
一个tag就是一个普通的java类,它唯一特别之处是它必须继承TagSupport或者BodyTagSupport类。这两个类提供了一些方法,负责jsp页面和你编写的类之间的交互,例如输入,输出。而这两个类是由jsp容器提供的,无须开发人员自己实现。换句话说,你只需把实现了业务逻辑的类继承TagSupport或者BodyTagSupport,再做一些特别的工作,你的类就是一个Tag。并且它自己负责和jsp页面的交互,不用你多操心。
“特别的工作”通常有以下几个步骤:
1)提供属性的set方法,此后这个属性就可以在jsp页面设置。以jstl标签为例 ,这个value就是jsp数据到tag之间的入口。所以tag里面必须有一个setValue方法,具体的属性可以不叫value。例如
setValue(String data){this.data = data;}
这个“value”的名称是在tld里定义的。取什么名字都可以,只需tag里提供相应的set方法即可。
2)处理 doStartTag 或 doEndTag 。这两个方法是 TagSupport提供的。 还是以为例,当jsp解析这个标签的时候,在“<”处触发 doStartTag 事件,在“>”时触发 doEndTag 事件。通常在 doStartTag 里进行逻辑操作,在 doEndTag 里控制输出。
3)编写tld文件。
4)在jsp页面导入tld
这样,你的jsp页面就可以使用自己的tag了。
通常你会发现自己绝大多数活动都集中在 doStartTag 或 doEndTag 方法里。确实如此,熟悉一些接口和类之后,写taglib很容易。正如《jsp设计》的作者所言:里面的逻辑稍微有点复杂,但毕竟没有火箭上天那么难。
5)
标签中的方法与返回值
返回值:
EVAL_BODY_INCLUDE:把Body读入存在的输出流中,doStartTag()函数可用
EVAL_PAGE:继续处理页面,doEndTag()函数可用
SKIP_BODY:忽略对Body的处理,doStartTag()和doAfterBody()函数可用
SKIP_PAGE:忽略对余下页面的处理,doEndTag()函数可用
EVAL_BODY_TAG:已经废止,由EVAL_BODY_BUFFERED取代
EVAL_BODY_BUFFERED:申请缓冲区,由setBodyContent()函数得到的BodyContent对象来处理tag的body,如果类实现了BodyTag,那么doStartTag()可用,否则非法
方法:
下面是自定义tag的执行过程(由上至下),对于以上各常量的实际运用为:
注意其中的 doInitBody/setBodyContent 方法在自定义标签实现了 BodyTag 接口或继承BodyTagSupport才可以使用
Tag 方法 |
可返回的静态常量 |
doStartTag |
SKIP_BODY 、EVAL_BODY_INCLUDE、 EVAL_BODY_AGAIN/EVAL_BODY_BUFFERED |
doInitBody |
做标签一些初始化工作,无返回值 |
setBodyContent |
在 doInitBody 之后执行,使用setBodyContent得到JSP页面中标签体之间内容 |
doAfterBody |
最终必须返回SKIP_BODY ,否则可能导致OutOfMemoryError,可参考上面① |
doEndTag |
SKIP_PAGE/EVAL_PAGE
|
2。一个简单的例子:OutputTag
package diegoyun;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.TagSupport;
/**
* @author chenys
*/
public class OutputTag
extends TagSupport
{
private String name=null;
public void setName(String name)
{
this.name = name;
}
public int doStartTag() throws JspException{
try
{
JspWriter out = pageContext.getOut();
out.print("Hello! " + name);
}
catch (Exception e)
{
throw new JspException(e);
}
return EVAL_PAGE;
}
}
简要说明:
1。如何输出到jsp页面:
调用JspWriter JspWriter out = pageContext.getOut();out.print......
记住这个方法就可以了。
2。输出后如何作处理
函数会返回几个值之一。EVAL_PAGE 表示tag已处理完毕,返回jsp页面。还有几个值,例如 EVAL_BODY_AGAIN 和EVAL_BODY_INCLUDE等,后面我们会作讨论
编写tld
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
1.0
1.2
diego -->taglib名称--<c:>c:>
out --->tag名称 out>out>
diegoyun.OutputTag --->tag类。包名
empty --->tag之间的内容********
name --->方法中有set方法的方法名 value="123">
false --->是否必填属性
false --->支持表达式取值!!!
在WEB-INF下新建tlds文件夹,把这个文件取名为diego.tld,放到tlds文件夹下。路径应该这样:WEB-INF\tlds\diego.tld
关于tld的简单说明:
short-name:taglib的名称,也称为前缀。比如 里的“c”
name:tag的名字。例如 里的"out”,我们的类也取名为out,由于有前缀作区分,不会混淆
tag-class:具体的tag类。带包名
body-content:指tag之间的内容。例如 ...... 起始和关闭标签之间就是body-content。由于没有处理body-content,所以上面设为empty
里的name:属性名字。例如里的value。名字可任意取,只要类里提供相应的set方法即可。
required:是否必填属性。
rtexprvalue:
是否支持运行时表达式取值。这是tag的强大功能
。以后我们会讨论。暂时设为false
编写jsp页面
<%@ page language="java"%>
<%@ taglib uri="/WEB-INF/tlds/diego.tld" prefix="diego"%>
Test Tag:
我的编程环境是eclipse+tomcat.启动服务器,如果一切按照上面步骤的话,就能看到 Test Tag: Hello! diegoyun 字样
最简单的tag就这么出来了。并不难,是不是?
===================================================================================================
Taglib 原理和实现:第二章 让Tag支持El表达式
1.先看这么一个例子
<%@ page contentType="text/html; charset=gb2312" language="java"%>
<%@ taglib uri="/WEB-INF/tlds/c.tld" prefix="c"%>
<%
String tut = "tutorial";
request.setAttribute("tut",tut);
%>
The String in request is :
2.如何支持el表达式
在路径org.apache.taglibs.standard.lang.support下,有个叫 ExpressionEvaluatorManager.evaluate 的方法,当el表达式作为入参时,调用这个方法,在tag内即可自动把el表达式转化。例如,你想tag的value字段支持el表达式,那么只需在set方法里如下调用:
public void setValue(Object value)throws JspException
{
this.value = ExpressionEvaluatorManager.evaluate(
"value", value.toString(), Object.class, this, pageContext);
}
ExpressionEvaluatorManager.evaluate有四个参数。第一个表示tag的名字,在取el表达式出错时使用。一般和属性名字相同。第二个要求字符串,通常简单调用输入对象的toString方法。第三个是类,通常用Object.class。第四个用this即可,第五个是pageContext变量。
通常不用对这个方法思考太多。只需改改属性名字,其他照搬即可。
注意:当你的tag属性支持el表达式时,你必须把它声明为Object对象。如上述的value,应该声明为:
private Object value = null;
3.实例:让OutputTag支持El表达式
package diegoyun;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.TagSupport;
import org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager;
public class NewOutputTag extends TagSupport
{
private
Object
name = null;
public void setName(Object name) throws JspException
{
this.name =
ExpressionEvaluatorManager.evaluate
(
"name", name.toString(), Object.class, this, pageContext
);
}
public int doStartTag() throws JspException{
try
{
JspWriter out = pageContext.getOut();
out.print("Hello! " + name);
}
catch (Exception e)
{
throw new JspException(e);
}
return EVAL_PAGE;
}
}
在diego.tld里添加声明
newout
diegoyun.NewOutputTag
empty
name
false
true
编写jsp测试
<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/tlds/diego.tld" prefix="diego"%>
<%
String s = "diego";
request.setAttribute("name",s);
%>
Test El supported tag:
可以看到页面输出为:
Test El supported tag:
Hello! diego
==================================================================================================
Taglib 原理和实现:第三章 tag之间的嵌套和属性读取
1。问题:在request里有一个 Man 对象,它有两个属性:name和age。现在,我们想用一个嵌套的tag,父tag取得对象,子tag取得name属性并显示在页面上。例如,它的形式如下:
object 支持el表达式,表示取得 Man 对象。output的property表示从该对象取得名为name的属性。
2。如何支持tag之间的嵌套
在子tag里调用
getParent
方法,可以得到父tag对象。用 findAncestorWithClass 方法,则可以通过递归找到想要找的tag
。例如
对于最内层的outputTag来说,
调用getParent
,可以得到 withCollectionTag,
通过如findAncestorWithClass(this,WithTag.class)的方式
,可以得到withTag
得到Tag之后,就可以取得Tag的属性,进行业务逻辑处理,然后输出到jsp
3。如何支持类属性查找功能
显然,在上面的outputTag中,我们要
根据属性的名字,查找类中有没有这个属性。然后取出属性的值并显示。通常,这可以编写自己的反射函数来完成
。
更简单的办法,是通过 BeanUtil 的PropertyUtils方法来完成功能
。BeanUtil
是apache上的一个开源项目
。
示例如下:
import org.apache.commons.beanutils.PropertyUtils;
。。。。。。
property = PropertyUtils.getProperty(currentClass, propertyName);
propertyName是待查找属性的名字,例如上面的"name",currentClass是待查找的类,例如上面的People
记得把
commons-beanutils.jar
添加到WEB-INF\lib目录下
4。现在让我们实现开篇提出的问题,编写WithTag如下:
package diegoyun;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyTagSupport;
import org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager;
/**
* @author chenys
*/
public class WithTag extends BodyTagSupport
{
private Object value = null;
private Object output = null;
public void setOutput(Object output)
{
this.output = output;
}
public Object getValue()
{
return value;
}
public void setValue(Object value)throws JspException
{
this.value = ExpressionEvaluatorManager.evaluate(
"value", value.toString(), Object.class, this, pageContext);
}
public int doStartTag()
{
return
EVAL_BODY_INCLUDE
;
}
public int doEndTag()throws JspException
{
try
{
pageContext.getOut().print(output);
}
catch (IOException e)
{
throw new JspException(e);
}
return
EVAL_PAGE
;
}
}
编写 NestedOutputTag 如下:
package diegoyun;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyTagSupport;
import org.apache.commons.beanutils.PropertyUtils;
/**
* @author chenys
*/
public class NestedOutputTag extends BodyTagSupport
{
private String property = null;
public void setProperty(String property)
{
this.property = property;
}
public int doEndTag()throws JspException
{
WithTag parent =(WithTag)getParent();
if(parent==null)
throw new JspException("Can not find parent Tag ");
try
{
Object propertyValue = PropertyUtils.getProperty(parent.getValue(), property);
parent.setOutput(propertyValue);
}
catch (Exception e)
{
throw new JspException(e);
}
return EVAL_PAGE;
}
}
在包diegoyun下添加一个包vo,在vo下写一个Man类:
package diegoyun.vo;
/**
* @author chenys
*/
public class Man
{
private String name = null;
private int age = 0;
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
写tld
with
diegoyun.WithTag
JSP
value
false
true
nestedout
diegoyun.NestedOutputTag
empty
property
false
false
写jsp页面
<%@ page language="java" %>
<%@ page import="diegoyun.vo.*"%>
<%@ taglib uri="/WEB-INF/tlds/diego.tld" prefix="diego"%>
<%
Man man = new Man();
man.setName("diego");
request.setAttribute("man",man);
%>
Test nested tag:
运行页面,则可以看到:
Test nested tag:
diego
5。结束语:
上述例子简单描绘了嵌套的Tag之间如何交互。
通常子Tag负责取得数据,然后设置父Tag的属性,最后在父Tag里显示到jsp页面
。如上面的例子,父 Tag 的 output 表示待打印的对象,通过 nestedoutTag 取得name的值,设置output,然后打印出来。
通过支持El表达式和动态属性联结,Tag可以实现强大的处理功能。将逻辑都集中到Tag里,极大的简化页面的编写。
===================================================================================================
Taglib 原理和实现:第四章 循环的Tag
1。问题:在request里的 People 对象,有个属性叫 men ,men 是一个Collection ,有许多个man 。现在,把 collection里的man的名字都显示出来
显然,这是一个嵌套Tag的问题。有三个Tag互相作用:最外层的Tag找到People对象,中间的Tag取得Collection,子Tag负责打印。
例如:
思路如下:
1.编写WithObjectTag,负责从El表达式中取得对象
2.编写WithCollectionTag,负责从对象中取得 Collection ,遍历 Collection ,每遍历一次 Collection ,执行一次body
3.编写ElementoutTag ,把 Collection 中每个men对象的 name 打印出来
2. 完整程序如下:
在上例的diegoyun.vo包内,编写 People 类
package diegoyun.vo;
import java.util.Collection;
public class People
{
private Collection men = null;
public Collection getMen()
{
return men;
}
public void setMen(Collection men)
{
this.men = men;
}
}
编写 withObject ,这是从request里取得People对象的最外层Tag
package diegoyun;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyTagSupport;
import org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager;
public class WithObjectTag extends BodyTagSupport
{
private Object value = null;
public Object getValue()
{
return value;
}
public void setValue(Object value)throws JspException
{
this.value = ExpressionEvaluatorManager.evaluate(
"value", value.toString(), Object.class, this, pageContext);
}
public int doStartTag()
{
return EVAL_BODY_INCLUDE;
}
public int doEndTag()throws JspException
{
return EVAL_PAGE;
}
}
编写WithCollectionTag,该Tag负责取得Collection,并遍历执行子Tag
package diegoyun;
import java.util.Collection;
import java.util.Iterator;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyTagSupport;
import org.apache.commons.beanutils.PropertyUtils;
public class WithCollectionTag extends BodyTagSupport {
private Object element = null;
private Collection list = null;
private Iterator iterator = null;
public Object getElement() {
return element;
}
public void setProperty(String property) throws JspException {
//取得父Tag对象,并且得到Collection
WithObjectTag parent = (WithObjectTag) getParent();
if (parent == null)
throw new JspException("parent tag is null");
try {
Object propertyValue = PropertyUtils.getProperty(parent.getValue(),
property);
this.list = (Collection) propertyValue;
if (list == null)
throw new JspException("Collection is null");
} catch (Exception e) {
throw new JspException(e);
}
}
public int doStartTag() throws JspException {
//设置第一个元素,然后执行子Tag
iterator = list.iterator();
if (iterator.hasNext())
element = iterator.next();
return EVAL_BODY_INCLUDE;
}
public int doAfterBody() {
if (iterator.hasNext()) {
//如果还存在子元素,设置子元素,并且再次执行子Tag
//循环由此而来
//否则不再执行子Tag
element = iterator.next();
return EVAL_BODY_AGAIN;
}
else
return EVAL_PAGE;
}
}
编写 ElementOutputTag
package diegoyun;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
import org.apache.commons.beanutils.PropertyUtils;
public class ElementOutputTag extends TagSupport
{
private Object propertyValue = null;
public void setProperty(String property)throws JspException
{
WithCollectionTag parent = (WithCollectionTag)getParent();
if(parent == null)
throw new JspException("parent tag is null");
try
{
//判断上层tag中是否存在该属性名称,如果存在,取得属性值,否则报错
propertyValue = PropertyUtils.getProperty(parent.getElement(), property);
}
catch (Exception e)
{
throw new JspException(e);
}
}
public int doEndTag()throws JspException
{
try
{
//简单的把值打印到jsp页面
pageContext.getOut().print(propertyValue);
}
catch (IOException e)
{
throw new JspException(e);
}
return EVAL_PAGE;
}
}
编写tld
withObject
diegoyun.WithObjectTag
JSP
value
false
true
withCollection
diegoyun.WithCollectionTag
JSP
property
false
true
elementout
diegoyun.ElementOutputTag
empty
property
false
true
编写jsp
<%@ page language="java" %>
<%@ page import="diegoyun.vo.*"%>
<%@ page import="java.util.*"%>
<%@ taglib uri="/WEB-INF/tlds/diego.tld" prefix="diego"%>
<%
Collection c = new ArrayList();
Man man1 = new Man();
man1.setName("diego");
c.add(man1);
Man man2 = new Man();
man2.setName("Zidane");
c.add(man2);
Man man3 = new Man();
man3.setName("Rui");
c.add(man3);
People p =new People();
p.setMen(c);
request.setAttribute("people",p);
%>
Test loop tag:
运行,则可以看到:
Test loop tag:
diego
Zidane
Rui
===================================================================================================
Taglib原理和实现 第五章:再论支持El表达式和jstl标签
1。问题:你想和jstl共同工作。比如,在用自己的标签处理一些逻辑之后,让jstl处理余下的工作。
2。看这个jsp例子:
....
<%
String name="diego";
request.setAttribute("name",name);
%>
......
许多jstl标签支持El表达式,所以,只要你在自己的标签内部把值塞进request,其他jstl标签就能使用它们
3。下面这个例子,从request里面取得对象,找到它属性的值,塞到request里去。
package diegoyun;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager;
public class SetVarTag extends TagSupport
{
private Object value = null;
private String property = null;
private String var = null;
public void setVar(String var)
{
this.var = var;
}
public void setProperty(String property)
{
this.property = property;
}
public void setValue(Object value)throws JspException{
this.value = ExpressionEvaluatorManager.evaluate(
"value", value.toString(), Object.class, this, pageContext);
}
public int doEndTag() throws JspException{
Object propertyValue = null;
try{
propertyValue = PropertyUtils.getProperty(value, property);
}
catch (Exception e) {
throw new JspException(e);
}
pageContext.setAttribute(var,propertyValue);
return EVAL_PAGE;
}
}
编写tld
set
diegoyun.SetVarTag
empty
value
true
true
property
false
false
var
false
false
编写jsp
<%@ page language="java" %>
<%@ page import="diegoyun.vo.*"%>
<%@ taglib uri="/WEB-INF/tlds/diego.tld" prefix="diego"%>
<%@ taglib uri="/WEB-INF/tlds/c.tld" prefix="c"%>
<%
Man man = new Man();
man.setName("diego");
request.setAttribute("man",man);
%>
Get value from request and set it‘s property value into request:
now use OutTag of jstl taglib to get the name:
value is :
运行,效果如下:
Get value from request and set it‘s property value into request:
now use OutTag of jstl taglib to get the name:
value is : diego
4。结语。和jstl交互是非常有用的技术。在jstl里提供了许多完成基本功能的标签,如输出,循环,条件选择等。仅在处理自己特定逻辑的时候才实现自己的标签,并提供和jstl交互,能大大提高重用性和减少工作量
===================================================================================================
Taglib原理和实现 第六章:标签内常用方法总结
1。支持el表达式:
import org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager;
private Object value = null;
this.value = ExpressionEvaluatorManager.evaluate("value", value.toString(), Object.class, this, pageContext);
2.用BeanUtil取属性值
import org.apache.commons.beanutils.PropertyUtils;
private String property=null;
Object propertyValue = PropertyUtils.getProperty(value, property);
3.设置request里的值
pageContext.setAttribute("var",propertyValue);
4。打印
pageContext.getOut().print(outputString);
5。取得父标签,取得想要的标签,即使它非父
getParent()
findAncestorWithClass(this,ancestorTag.class);
6。标签自带方法和常量,方法按照容器的调用顺序排列。示例
doStartTag : 容器解析到c:if左尖括号(“<”)时调用
doInitBody : 容器解析到c:if右尖括号(“>”)和c:out左尖括号(“<”)时调用
doAfterBody : 容器解析到c:out结束标记(“/>”)时调用
doEndTag :容器解析到c:if结束标记(“/>”)时调用
EVAL_BODY_SKIP : 通常在 doStartTag 方法里调用,忽略标签包括的内容,假如返回这个值,上面的c:if忽略c:out
EVAL_BODY_INCLUDE :通常在 doAfterBody 方法里调用,再次执行body,假如返回这个值,上面的c:out被执行多次
EVAL_PAGE :可在任何方法里调用。返回jsp页面