一,自定义标签 :实现了特定接口的java类,封装了java代码编写的预定义行为。
* 在运行时,标签被替换成相应的预定义java代码。
* JSP 自定义标记提供了替代简单的 JavaBean 和 Java 脚本的方法。
更好的是在 JSTL 中已存在一组已定义的标准的自定义标记库。
* 目的在于将业务和表示逻辑分离,代码的可重用性,可移植性。
二,标签库:按照功能或实现进行分组的自定义标签的集合。
1,API : javax.servlet.jsp.tagext包中的三个接口和2个类
2,组成:
* 标签处理程序类
* 标签库描述符文件
* 应用程序部署描述符
* jsp文件
3,自定义标签步骤:
1,实现:实现tagext包定义的接口或类;
2,配置:配置TLD文件;
3,关联:配置web.xml文件;
三,标签处理程序类:一个自定义标签的实现类;
1,标签处理程序是在运行时期调用。必须实现或扩展javax.servlet.jsp.tagext包定义的三个Java接口中的一个。
* Tag接口 : 标签处理程序和JSP页面的基本协议。定义了所有标签处理程序的基本方法。
* IterationTag接口:扩展Tag接口,控制对标签体的重复处理。
* BodyTag接口:继承IterationTag接口,对标签体中内容进行处理。
2,接口的默认实现:TagSupport和BodyTagSupprt类:
* TagSupport类为Tag和IterationTag接口的默认实现。支持简单标签和带主体迭代的标签。
* BodyTagSupprt类为BodyTag接口的默认实现。支持需要访问和操作标签主体内容的标签。
3,实现Tag接口的标签处理程序类的生命周期:
1,setPageContext():在标签处理器之前被JSP容器调用。设置标签的页面上下文,调用 setParent()方法设置该标签的父标签,没有则设置为NULL;
setParent()
2, 设置标签的属性: 有属性,调用setXxx方法设置标签属性,没有属性则跳过。
3,doStartTag():返回EVAL_BODY_INCLUDE或SKIP_BODY
EVAL_BODY_INCLUDE :输出标签体到当前的输出流
SKIP_BODY:忽略标签体
4,doEndTag(): 返回EVAL_PAGE或SKIP_PAGE
EVAL_PAGE:执行页面余下部分
SKIP_PAGE:忽略页面余下部分
5,容器缓存标签处理器类实例,重复使用缓存的标签处理类实例
6,release():释放实例,
4,实现IterationTag接口的标签处理程序类的生命周期:
1,setPageContext()
setParent()
2, 设置标签的属性
3,doStartTag(): 返回EVAL_BODY_INCLUDE或SKIP_BODY
EVAL_BODY_INCLUDE:
执行标签体(被执行一遍);
调用doAfterBody()方法:返回EVAL_BODY_AFTER或SKIP_BODY;
EVAL_BODY_AFTER: 跳转到前面重新执行标签体
SKIP_BODY:
SKIP_BODY:
4,doEndTag() : EVAL_PAGE或SKIP_PAGE
EVAL_PAGE:执行页面余下部分
SKIP_PAGE:
5,从1开始
6,release()
5,实现BodyTag接口的标签处理程序类的生命周期:
1,setPageContext()
setParent()
2, 设置标签的属性
3,doStartTag(): 返回EVAL_BODY_BUFFERED或SKIP_BODY_INCLUDE,SKIP_BODY.
EVAL_BODY_BUFFERED且标签体不为null:
setBodyContent():
设置标签处理器类的bodyContent属性,提供了BodyContent对象,缓存标签体静态内容和动态内容。
调用doInitBody()为标签体执行做准备
跳转到 执行标签体:
SKIP_BODY_INCLUDE : 直接跳转到 执行标签体:
SKIP_BODY : 忽略标签体,直接跳到 5
4, 执行标签体(被执行一遍后);
调用doAfterBody()方法:返回EVAL_BODY_AFTER或SKIP_BODY;
EVAL_BODY_AFTER: 跳转到前面重新执行标签体
SKIP_BODY:
SKIP_BODY:不重复执行
5,doEndTag() : EVAL_PAGE或SKIP_PAGE
EVAL_PAGE:执行页面余下部分
SKIP_PAGE:
6,从1开始
7,release()
6,TagSupport类:支持简单标签和带主体迭代的标签。
7, BodyTagSupprt类:支持需要访问和操作标签主体内容的标签。
8,BodyContent类 :缓存输出内容。
* JSP容器处理页面签创建JspWriter类实例,把所有输出引入该实例中(静态和动态内容等行为),
如果JSP容器遇到的“自定义标签实现类”实现了BodyTag接口,就会临时将所以缓存在JspWriter实例中的所有输出重新定位到BodyContent类实例中缓存,直到自定义标签结束。
所以BodyContent也是JspWriter类的子类,而JSP标签处理器也就可以通过该对象的getString()方法读到标签体中的内容了。
public void flush()throws IOException 复写JspWrite.flush()方法以便它总是产生溢出。刷新写入已失效,因为它没有连接到将被写入的实际输出流中。
public void clearBody() 重置BodyContent缓存为空。
public Reader getReader() 返回Reader读取体内容。
public String getString() 返回标签体中的内容。
public void writeOut(Write w) 将体内容写入指定输出。
public JspWrite getEnclosingWrite() 返回栈中下一个更高的写入者对象(可能是另一个BodyContent对象)。
9,JSP标签处理器的生命周期
1,初始化实例:创建标签实例,调用所有的设置方法(setPageContext,setParent方法和所有属性的设置方法)来初始化实例。
2,调用doStartTag()方法将实例变量设为仅在当前调用的有效值。如果该方法正在处理元素的标签体,就会调用doEndTag方法。
3,标签实例被重用,如果属性有不同的值则调用对应的设置方法,重复2的操作。只有在具有相同属性集合时,标签处理器才将实例重用。
4,使用release方法让标签处理器释放内部占有资源。
10,注意:
* 为"属性"提供默认值。
* 由于容器缓存标签处理器类实例,重复使用缓存的标签处理类实例。
所以在改变类变量后,后面使用该类变量也会受影响。
所以,要注意并发访问的问题。
所以,要每次重设调用的状态。最佳地点是doStartTag方法中。
* 标签在调用期间绝不会调用release();
* 不要在setBodyContent方法和doInitBody方法中使用BodyContent对象,只用于获取该对象和做准备工作。
* 对于实现BodyTag接口的自定义标签,同时使用空标签和不为空的标签,那就会抛出异常。因为空标签不会调用一些方法。
四,标签库描述符(TLD)文件:TLD是由在JSP页面中使用的标记所组成的库,用于配置标签;
1,标签库描述符文件是描述一个标签库的XML文档,包含了标签的名字,属性,标签处理类和标签的属性等信息。
2,扩展名为.tld,必须放在WEB-INF目录下。
<taglib version="2.0"
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">
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<short-name>标签的简称</short-name>
<tag>
<name>标签的名字</name>
<tag-class>标签处理类完整名</tag-class>
<body-content>标记主体是否可以嵌入内容,以及内容如何处理。不可以使用empty</body-content>
</tag>
</taglib>
tag元素的配置说明:
<tag>
<name>tagNmae</name>-----这个Tag的名字
<tagclass>package.Class</tagclass>-----这个Tag是由那个类实现的(这个class可以在struts.jar包中找到)
<bodycontent>empty</bodycontent>-----这个Tag可以直接结尾,不需要填写内容
这里bodycontent有三个可选值
jsp :标签体由其他jsp元素组成
如果其有jsp元素,那么标签会先解释,然后将元素的实际值传入。比如标签体里含有<%=attributeName%>这样子的jsp元素,此时标签会按attributeName的实际值是什么就传入什么。这个是最常用的一个。
empty :标签体必须为空
在引用这个Tag的时候,可以<bean:write bundle="attributeName" />,而不必<bean:write bundle="attributeName" ></bean:write>
tagdependent : 由标签解释,不带jsp转换
<attribute> -----这里标识的是这个Tag的一个参数
<name>attrName</name>--这个参数的名字
<required>false</required>-----这个参数是否是必填,如果为true则必须写这个参数,否则会报错
<rtexprvalue>true</rtexprvalue>------是说这个属性的值是否可以接收运行时的表达式。rtexprvalue:"RUN-TIME EXPRESSION VALUE",是否可以动态赋值,在jsp中如value="<%=attributeName%>"
</attribute>
</tag>
六,建立 JSP 页面与标记库之间的引用 :静态引用与动态引用,关联标签;
1,可以通过 Web 应用程序描述符(web.xml)声明一个静态引用,
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
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-app_2_4.xsd">
<jsp-config>
<taglib>
<taglib-uri>/myTags</taglib-uri>
<taglib-location>/WEB-INF/MyTLD.tld</taglib-location>
</taglib>
</jsp-config>
</web-app>
然后,将 JSP 声明加入到所有需要使用自定义标记库的页面中:
<%@ taglib uri="myTags" prefix="abc" %>
注意指定的 uri 属性与在 web.xml 文件中指定的 taglib-uri 值相匹配。
2,可以直接在页面中声明一个动态引用。
只需在所有需要使用这个库的页面中加入一个 JSP 声明即可:
<%@ taglib uri="/WEB-INF/lib/DateTagLib.tld" prefix="abc" %>
3,静态引用与动态引用比较:
在进行标记库的静态引用时,JSP 声明必须查询 web.xml 文件以执行库查询。
这意味着如果移动或者重命名了库,或者希望在 web.xml 文件中加入更多的库,
就必须停止服务器、更新 web.xml 文件、然后重新启动服务器。
动态方法让 JSP 页直接指向 TLD 位置,因而是在解释 JSP 页面时进行处理。
静态方法提供了页面与库的实际名和位置之间一定程度的非直接性,
这可以为您提供一些改变这些属性而不修改页面的灵活性。另一方面,动态方法提供了更大的灵活性,
让您可以在运行时增加和移动标记声明。如果您对动态方法感兴趣,
但是又担心做了一些改变后、有可能要更新多个页面的维护负担,
那么您可以始终将 JSP 声明放到一个单独的 JSP 文件中,并在每一个要访问 Web 应用程序的自定义库的页面中加入这一页。
这使您具有在运行时只需要更新信息一次就可以增加库的灵活性。
七,开发协作的行为
1,使用显示的父子协作: 通过内层的子标签为父标签提供参数,以构成完整或补充的功能实现; 比方<jsp:include>动作元素中的子元素<jsp:param>.
* 使用TagSupport.findAncestorWithClass(this, ParamParent.class)方法在子标签中获取父标签的引用。
* 调用父标签的方法将子类的参数传递给父类。
* 父标签为实现了特定的接口,该接口中定义了子标签中的参数如何传递给父标签,供子标签调用。
2,通过变量使用隐式协作:
通过设置JSP作用域的变量而隐式进行协作。一个行为将其处理结果作为某个jsp作用域中的变量,而另外一个行为则使用变量作为其输入。例如迭代行为。
八,在标签库中使用监听器:
使用<Listener>元素放在<validator>后面在TLD中为自己的库定义监听器实现。
当容器载入WEB应用程序的时候,会遍历所有的TLD来查找监听器定义,并且注册所找到的监听器。
九,数据类型转换
1,JSP容器自动处理从文本值到属性值的基本数据类型的转换。
2,使用PropertyEditor进行转换:将文本值转换为任何java数据类型。
十,异常处理
如果JSP元素抛出一个异常,对它的默认处理是发送到一个错误页面。
但对于特殊标记(如文件操作)处理必须作出显示的处理。JSP1.2新接口TryCatchFinally接口专门来处理异常。
如果嵌套行为体中的元素doStartTag(),doEndTag(),doInitBody()或doAfterBody方法中的任何一个抛出异常。
容器会调用doCatch方法。我们可以在该方法中记录问题,抛出自己的异常。页面对其他部分的处理也会继续。
doFinally方法总是由容器调用。当在doStartTag方法抛出异常,就会无法创建PrintWriter对象输出信息,所以要在检查null。