Servlet&JSP 第八章 自定义标签

一、Tag自定义标签

1、Tag File简介

(1)Tag File的*.tag文件放在/WEB-INF/tags文件夹或子文件夹,并在JSP中使用taglib指示元素的tagdir属性指定*.tag的位置,就可以使用这个Tag File了。

(2)tag指示元素用来告知容器如何转化这个Tag File。description属性只是一段文字描述,用来说明这个Tag File的作用。pageEncoding属性告知容器在转译Tag File时使用的编码。Tag File中可以使用taglib指示元素引用其他自定义标签库,可以在Tag File中使用JSTL。基本上JSP文件中可以使用的EL或Scriptlet在Tag File中也可以使用。

例1、<%@tag description=”显示错误信息的标签” pageEncoding=”UTF-8” %>

(3)在需要这个Tag File的JSP页面中,可以使用taglib指示元素的prefix属性定义前置名称。tagdir属性定义Tag File的位置,tagdir只能指定/WEB-INF/tags的子文件夹,Tag File就只能放在/WEB-INF/tags或子文件夹中。

例2、<%@taglib prefix=”html” tagdir=”/WEB-INF/tags” %>

(4)Tag File中可以使用out、config、request、response、session、application、jspContext等隐式对象。Tag File在JSP中并不是静态包含或动态包含,若在Tag File中编写Scriptlet,其中的隐式对象其实好就是转译后.java中的doTag()方法中的局部变量。在Tag File中的Scriptlet定义的局部变量,也会是doTag()中的局部变量,所以也不可能与JSP的Scriptlet沟通。

2、处理标签属性与Body

(1)在创建Tag File时,可以通过attribute指示元素来指定使用某些属性。Attribute只是原属定义使用Tag File时可以设置的属性名称,设置名称之后,若有人使用Tag File时指定属性值,则这个值在*.tag文件中。

(2)<jsp:doBody/>标签可以取得使用Tag File标签时的Body内容。

(3)<html>与</html>的Body中,可以编写想要的HTML、EL或自定义标签,Body的内容会在<jsp:doBody/>位置与其他内容结合在一起。

(4)Tag File标签在使用时若有Body,默认是不允许有Scriptlet的,因为定义Tag File时,tag指示元素的body-content属性默认是scriptless,也就是不可以出现<% %>、<%= %>或<%! %>元素。body-content属性还可以设置empty或tagdependent,empty表示一定没有Body内容,也就是只能以<html:Header/>这样的方式来使用标签(非empty设置时,可以用<html:Header/>,或者是<html:Header>Body</html:Header>的方式),tagdependent表示将Body中的内容当作纯文字处理,也就是如果Body中出现了Scriptlet、EL或自定义标签,也就是当作纯文字输出,不会做任何的运算与转译。

3、TLD文件

(1)Tag File包装成JAR文件,需要注意:

*.tag文件必须放在META-INF/tags文件夹或子文件夹下;

要定义TLD文件;

TLD文件必须放在JAR文件的META-INF/TLDS文件夹下;

(2)每个<tag-file>中使用<name>定义了自定义标签的名称,使用<path>定义了*.tag在JAR文件中的位置。

二、Simple Tag自定义标签

1、Simple Tag简介

所有要实现的内容都在doTag()中进行。

(1)开发Simple Tag:首先要编写标签处理器,这是一个Java类,可以继承javax.servlet.jsp.tagext.SimpleTagSupport来实现标签处理器,并重新定义doTag()方法来进行标签处理。

(2)<f:if>标签:<f:if>标签如果有test属性,标签处理器必须有个接受test属性的设值方法,再重新定义的doTag()方法中,如果test属性为true则调用SimpleTagSupport的getJspBody()方法,这会返回一个JspFragment对象,代表<f:if>与</f:if>间的Body内容。如果调用JspFragment的invoke()方法并传入一个null,表示执行<f:if>与</f:if>间的Body内容,如果内有调用invoke(),则<f:if>与</f:if>间的Body内容不会被执行,也就不会有结果输出至用户的浏览器。

(3)为了让Web容器了解<f:if>标签与IfTag标签处理器之间的关系,要定义一个标签程序库描述文件,也就是一个后缀为*.tld的文件。

(4)每个<tag>标签中使用<name>定义了自定义标签的名称,使用<tag-class>定义标签处理器类,而<body-content>设置为scriptless,表示标签Body中不允许使用Scriptlet等元素。

(5)如果标签上有属性,则使用<attribute>来设置,<name>设置属性名称,<required>表示是否一定要设置这个属性。<rtexprvalue>表示属性是否接受运行时的运算的结果(如EL表达式的结果),如果设置为false或不设置<rtexprvalue>,表示在JSP上设置属性时仅接受字符串形式。<type>设置属性类型。

(6)可以将TLD文件放在WEB-INF文件夹下,这样容器会自动加载它,如果要使用这个标签,必须在JSP页面上使用taglib指示元素。

2、了解API架构与生命周期

(1)JSP自定义Tag都实现了JspTag接口,JspTga接口只是个标示接口,本身没有定义任何方法。SimpleTag接口继承了JspTag,定义了SimpleTag开发时所需的基本行为,开发Simple Tag标签处理器时必须实现SimpleTag接口。

(2)JSP网页中包括Simple Tag自定义标签,若用户请求该网页,在遇到自定义标签时,会按照以下步骤来处理:

创建自定义标签处理器实例;

调用标签处理器的setJspContext()方法设置PageContext实例;

如果是嵌套标签中的内层标签,则还会调用标签处理器的setParent()方法,并传入外层标签处理器的实例;

设置标签处理器实例;

调用标签处理器的setJspBody()方法设置JspFragment实例;

调用标签处理器的doTag()方法;

销毁标签处理器实例;

(3)每一次的请求都会创建新的标签处理器实例,而在执行dotag()后就销毁实例,在SimpleTag的实现中,建议不要有一些耗资源的动作。

(4)由于标签处理器中被设置了PageContext,所以可以用它来取得JSP页面的所有对象,进行所有在JSP页面中可以执行的操作,之后就可以用自定义标签来取得JSP页面上的Scriptlet。

(5)JspFregment是个JSP页面中的片段内容,在JSP中使用自定义标签时若包括Body,将会转译为一个JspFragment实现类,而Body内容将会在invoke()方法进行处理。

(6)如果doTag()的过程在某些条件下,必须中断接下来页面的处理或输出,则可以抛出javax.servlet.jsp.SkipPageException,这个异常对象会在JSP转译后的_jspService()中进行处理。

3、处理标签属性与Body

(1)<f:forEach>标签可以设置var属性来决定每次从Collection取得对象时,应该用哪个名称在标签Body中取得该对象,var只接受字符串方式来设置名称。

<f:forEach>标签Body内容必须执行多次,则是通过多次调用invoke()方法来达成,也就是在doTag()中每调用一次invoke()则会执行依次Body内容。

通过SimpleTagSupport的getJspBody()取得JspFragment,并在调用invoke时传入null,表示将使用PageContext取得默认的JspWriter对象来做输出响应,也就是默认会输出响应至用户浏览器。

(2)如果调用invoke()设置了一个Writer对象,则会调用pageContext的pushBody()方法并传入该对象,这会将pageContext的getOut()方法所取得的对象设置为该Writer对象,并在堆栈中记录先前的JspWriter对象。若标签Body内容中还有内层标签,通过getOut()取得的就是所设置的Writer对象(除非内层标签在调用invoke()时,也设置了自己的Writer对象)。pushBody()返回的是BodyContent对象,为JspWriter的子类,封装了所传入的Writer对象。因为BadyContent实例被out引用,而运行结果都通过out所引用的对象输出,所以最后BadyContent将会包括所有标签Bady的运行结果(包括内层标签),而这些结果,将再写入BadyContent所封装的Writer对象。

4、与父标签沟通

(1)<f:when>标签:test属性查看是否执行Body内容。在测试开始前,必须先尝试取得parent,如果无法取得(也就是为null的情况),表示不在任何标签之中;或是parent部位ChooseTag类型时,表示不是置于<f:choose>中,这是个错误的使用方式,所以必须抛出异常。如果确实是置于<f:choose>标签中,接着尝试取得parent的matched状态,如果已经被设置为true,表示先前有<f:when>已经通过设测试并执行了其Body内容,那么目前这个<f:when>就不用再做测试了。如果是置于<f:choose>标签中,而且先前没有<f:when>通过测试,接着就可以进行目前这个<f:when>测试,如果测试成功,则设置parent的matched为true,并执行标签Body。

(2)<f:otherwise>标签:必须确认是否置于<f:choose>标签中,必须确认先前是否有<f:when>测试成功,如果先前没有<f:when>测试成功,就直接执行标签Body内容。

(3)如果在一个数个嵌套的标签中,想要直接取得某个指定类型的外层标签,则可以通过SimpleTagSupport的findAncestorWithClass()静态方法,findAncestorWithClass()方法会在目前标签的外层标签中寻找,直到找到指定的类型的外层标签对象后返回。

5、TLD文件

(1)TLD文件可以放在JAR文件的META-INF文件夹或子文件夹,也就是:

JAR文件根目录下放置编译好的类(包含对应包的文件夹);

JAR文件META-INF文件夹或子文件夹中放置TLD文件;

接着在文字模式中进入fake文件夹,运行命令:jar cvf../fake.jar *;

这样在fake文件夹上一层目录中,就回产生fake.jar文件,若想使用这个文件,只要将之置入WEB-INF/lib中,就可以开始使用自定义的标签库;

三、Tag自定义标签

1、Tag简介

(1)JSP中开始处理标签时,会调用doStartTag()方法,后续是否执行Body则是根据doStartTag()的返回值决定,如果doStartTag()方法返回EVAL_BODY_INCLUDE常数(定义在Tag接口中),则会执行Body内容,若返回SKIP_BODY常数(定义在Tag接口中),则不执行Body内容。

(2)<body-content>可以设置的值有empty、JSP、tagdependent,JSP的设置值表示Body中若包括动态内容,如Scriptlet元素、EL或自定义标签都会被执行。

(3)实现Tag接口相关的类时,按不同的时机,要定义不同的doXXXTag()方法,并按需求返回不同的值。

2、了解架构与生命周期

(1)doXXXTag()方法实际上是分别定义在Tag与IterationTag接口上的方法。

(2)Tag接口继承自JapTag接口,它定义了基本的Tag行为,如设置PageContext实例的setPageContext()、设置外层父标签对象的setParent()方法、标签对象销毁前调用的release()方法等。

(3)单是使用Tag接口的话,无法重复执行Body内容,而必须使用子接口IterationTag接口的doAfterTag()。TagSupport类实现了IterationTag接口,对接口上所有方法做了基本实现,所以只需要在继承TagSupport之后,针对必要的方法重新定义即可。

(4)JSP中遇到TagSupport自定义标签时,会进行以下动作:

尝试从标签池找到可用的标签对象,如果找到就直接使用,如果没找到就创建新的标签对象;

调用标签处理器的setPageContext()方法设置PageContext实例;

如果是嵌套标签中的内层标签,则还会调用标签处理器的setParent()方法,并传入外层标签处理器的实例;

设置标签处理器实例;

调用标签处理器的doStartTag()方法,并依不同的返回值决定是否执行Body或调用doAfterBody()、doEndTag()方法;

将标签处理器实例置入标签池中以便再次使用;

(5)Tag实例是可以重复使用的,所以自定义Tag类时,要注意对象状态是否会保留下来,必要的时候,在doStartTag()方法中,可以进行状态重置的动作,release()方法只会在标签实例真正被销毁回收前被调用。

(6)JSP页面会根据标签处理器各方法调用的不同返回值,来决定要调用哪一个方法或进行哪一个动作。

(4)doStartTag()可以回传EVAL_BODY_INCLUDE或SKIP_BODY。如果返回EVAL_BODY_INCLUDE则会执行Body内容,而后调用doAfterTag()方法;如果返回SKIP_BODY则不执行Body内容,此时就会调用doEndTag()方法。

(5)doAfterTag()默认返回值是SKIP_BODY,如果不重新定义doAfterTag()方法,无论有无执行Body,流程最后都会来到doEndTag()。在doEndTag()中,可返回EVAL_PAGE或SKIP_PAGE,如果返回EVAL_PAGE,则自定义标签后续的JSP页面才会继续执行,如果返回SKIP_PAGE就不会执行后续的JSP页面。

(6)由于TagSupport类对IterationTag接口做了基本实现,doStartTag()、doAfterBody()、doEndTag()都有默认的返回值,依序分别是SKIP_BODY、SKIP_BODY以及EVAL_PAGE,也就是默认不处理Body,标签处理结束后会执行后续的JSP页面。

3、重复执行标签的Body

(1)doAfterBody()方法执行后,如果返回EVAL_BODY_AGAIN,则会重复执行一次Body内容,而后再次调用doAfterBody()方法,除非在doAfterBody()中返回SKIP_BODY才会调用doEndTag()。当doStartTag()返回EVAL_BODY_INCLUDE后,会先执行Body内容再调用doAfterBody()方法,也就是说,实际上Body已经被执行过一次了,所以正确的做法是doStartTag()和doAfterBody()都要实现,doStartTag()实现第一次的处理,doAfterBody()实现后续的重复处理。

(2)<f:forEach>标签处理器的实现中,必须先为第一次的Body执行做属性设置,这样返回EVAL_BODY_INCLUDE后第一次执行Body内容时,才可以有var所设置的属性名称可以访问。接着调用doAfterBody(),其中再为第二次之后的Body处理做属性设置,如果需要再执行一次Body,则返回EVAL_BODY_AGAIN,再次执行完Body后又会调用doAfterBody()方法。如果不想执行Body了,z则返回SKIP_PAGE,流程会来到doEndTag()的执行。

4、处理Body运行结果

(1)BodyTag接口继承自IterationTag接口,BodyTagSupport继承自TagSupport类,将doStartTag()的默认返回值改为EVAL_BODY_BUFFERED,并针对BodyTag接口做了简单的实现。在继承BodyTagSupport类实现自定义标签时,如果doStartTag()返回了EVAL_BODY_BUFFERED,则会调用setBodyContent()方法而后调用doInitBody()方法,接着再执行标签Body。

(2)基本上,BodyTagSupport实现自定义标签时, 并不需要去重新定义setBodyContent()与doInitBody()方法,只需要知道这两个方法执行后,在doAfterBody()或doEndTag()方法中,就可以通过getBodyContent()取得一个BodyContent对象(Writer的子对象),这个对象中包括Body内容执行后的结果。

(3)如果要加工后的Body内容输出用户的浏览器,通常会在doEndTag()中使用pageContext的getOut()取得JspWriter对象,然后利用它来输出内容至用户的浏览器。如果一定要在doAfterBody()中取得JspWriter对象,则必须通过BodyContent的getEnclosingWriter()方法。

5、与父标签沟通

(1)Tag实例会在部使用时放回标签池,所以若标签上一次执行过后有状态存在,下次再从标签池中取出时,必须考虑进行状态重置的动作,这个动作放在doStartTag()中完成。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值