一,自定义标签 :实现了特定接口的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="" xmlns:xsi="" xsi:schemaLocation=" 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="" xmlns:xsi="" xsi:schemaLocation=" "> <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。