您的位置:首页 > Web前端 > JavaScript

BodyContent揭秘及定制复杂的JSP标签

2016-01-18 09:45 627 查看

BodyContent揭秘及定制复杂的JSP标签

jsp中的定制标签功能可以帮助我们来更好地实现presentation layer。我在学习的时候,感觉最困难的就是BodyContent这个类,Sun在API和specification中对BodyContent介 绍的非常少,以至于很多程序员对这个类知之甚少。

本文的目的就是带领读者揭开这层面纱,直捣BodyContent的核心,帮助读者了解BodyContent背后的设计模式,本文还将带领读者写一个模拟while循环的标签,这个标签完全可以替代JSTL中的循环标签。

BodyContent揭秘

BodyContent类继承了JspWriter,同时又对JspWriter实现了包装模式(wrapper),实质就是JspWriter的层层包
装,你可以把这种结构想象成一个洋葱。图1表明了BodyContent和JspWriter的关系:

图1:BodyContent和JspWriter的关系

对于一个两层的嵌套的标签:

<c:parent>

parent1

<c:child>

child

</c:child>

parent2

</c:parent>

一开始,容器遇到<c:parent>起始标签。由容器创建了一个BodyContent对象,这个对象包装了真正的响应流
(即response.getWriter()得到的流),然后让隐含的out引用指向这个BodyContent对象。于是,就形成了两层的
JspWriter流包装层次:最内层是真正的响应流,最外层是<c:parent>标签的BodyContent对象。

然后,容器对<c:parent>标签的正文求值,把"parent1"字符串写入到out引用指向的JspWriter当中。这时,最外层的JspWriter中的内容是"parent1",最内层真正的响应流中没有内容。

接下来,容器又遇到了<c:child>起始标签。同样,容器创建一个BodyContent对象,这个对象包装了out引用指向的
JspWriter,然后让out引用指向这个新创建的BodyContent对象。于是,就有了一个三层的JspWriter包装层次:最内层是真正的
响应流,中间层是<c:parent>标签的BodyContent对象,最外层是<c:child>标签的
BodyContent对象。

然后,容器遇到了字符串"child",把它写入到out引用指向的JspWriter当中。这时,最内层中仍然没有内容,中间层中的内容是"parent1",最外层中的内容是"child"。此时的情景如图2所示:

图2:三层的流包装层次

到目前为止,3层的流包装层次已经形成:最内层是真正的响应流,中间层是<c:parent>标签的BodyContent对象,最外层就
是<c:child>标签的BodyContent对象。每一层流当中都有一些被输入的内容。out引用总是指向最外层的
JspWriter。注意,流包装嵌套的顺序和标签嵌套的顺序正好是反向的。

像不像是一颗洋葱呢?接下来,看看容器是怎样一层一层剥洋葱的。

接下来,容器遇到了</c:child>结束标签,把标签的JspWriter中的全部内容写入到内层的JspWriter中,然后该层
JspWriter就从流包装层次中剥离出来,然后容器重新设定out引用指向最外层的JspWriter。这时候,三层的流包装层次变成了两层的流包装
层次:最内层是真正的响应流,仍然没有内容,最外层是<c:parent>标签的JspWriter对象,内容是"parent1
child"。此时的情景如图3所示:

图3:<c:child>的JspWriter剥离后的两层流包装层次

然后,容器遇到了"parent2"字符串,并把它写入到out引用指向的JspWriter当中。这时,最内层流是真正的响应流,没有内容,最外层
是<c:parent>标签的JspWriter对象,内容是"parent1 child parent2"。此时的情景如图4所示:

图4:"parent2"被写入到<c:parent>标签的JspWriter之后的流包装层次

最后,容器遇到了</c:parent>结束标签,把标签的JspWriter中的全部内容写入到内层JspWriter当中,然后标签的
JspWriter就从流包装层次中剥离出来。这时,流包装层次中只剩下真正的响应流了,它当中的内容是刚被写进去的"parent1 child
parent2"。此时的情景如图5所示:

图5:最后只剩下真正的响应流JspWriter对象

这样,所有流包装层次中的内容最终都会汇集到真正的响应流当中。你可以把这个过程想象成剥洋葱。

while循环标签

了解了BodyContent,接下来就实践一下,做一个while循环的标签。标签的语法:

<while>

<condition value="<%=布尔表达式%>"/>

<do> JSP </do>

</while>

其中有三个标签:while标签、condition标签和do标签,因此需要设计三个标签处理器类。While标签和do标签的处理器要继承
BodyTagSupport这个类,因为它们要处理正文内容,而condition标签只需要继承TagSupport就可以了。

我所预期的处理流程是这样的:如果condition标签中的value属性值为true,则处理do标签的正文,并继续下一轮循环;如果condition标签中的value属性值为false,则不处理do标签的正文,并跳出循环。

这就需要do标签和condition标签之间有某种通讯机制。幸运的是,在BodyTagSupport中有setValue()和
getValue()方法,这样,condition标签和do标签的处理器就可以通过在while标签的处理器中设置一些值来进行通讯。

下面是while标签的处理器的源码:



package coreservlets.tags.loop;

import java.io.IOException;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.tagext.*;

public class WhileHandler extends BodyTagSupport {

public int doStartTag () {

// "conditionCount" 属性和 "doCount" 属性,

// 为保证 <condition> 和 <do> 的个数和顺序

setValue ("conditionCount", new Integer (0));

setValue ("doCount", new Integer (0));

// "condition" 属性,为保证不满足条件就退出循环

setValue ("condition", new Boolean (false));

return BodyTag.EVAL_BODY_BUFFERED;

}

public int doAfterBody () throws JspException {

// 取得 "doCount" "conditionCount" "condition" 的属性值

int doCount = ((Integer) getValue ("doCount")).intValue ();

int conditionCount = ((Integer) getValue ("conditionCount")).intValue ();

boolean condition = ((Boolean) getValue ("condition")).booleanValue ();

if ((doCount != 1) || (conditionCount != 1)) {

throw new JspException ("tag format error");

}

// 设置 "doCount" "conditionCount" "condition" 的属性值

setValue ("doCount", new Integer (0));

setValue ("conditionCount", new Integer (0));

setValue ("condition", new Boolean (false));

// 判断 condition 条件,决定是否继续循环

return (condition ? IterationTag.EVAL_BODY_AGAIN : Tag.SKIP_BODY);

}

public int doEndTag () throws JspException {

try {

BodyContent bc = getBodyContent ();

bc.getEnclosingWriter().write (bc.getString());

// 不推荐使用

// bc.writeOut (this.getPreviousOut ());

} catch (IOException ie) {

throw new JspException (ie.getMessage ());

}

return Tag.EVAL_PAGE;

}

}

在doEndTag()方法中需要把BodyContent中的内容写入它包装的内层的JspWriter中。
bc.getEnclosingWriter()用来得到BodyContent包装的JspWriter,bc.getString()用来得到
BodyContent中的内容。作者不推荐使用BodyTagSupport.getPreviousOut()和
BodyContent.writeOut()方法。到此,bc.getEnclosingWriter ().write (bc.getString
())这个语句的意思就十分清晰了。就跟剥洋葱一样。

BodyTagSupport中doStartTag()、doAfterBody()和doEndTag()这些方法的返回值的意义非常重大,应该重点理解。

WhileHandler在它的doAfterBody()方法判断condition属性是真还是假,由此来决定是否继续下一轮循环。你可能以为
condition属性是不是永远为false呢?不是的,在condition标签的处理器中会修改WhileHandler的condition属
性。

下面是condition标签的处理器的源码:

package coreservlets.tags.loop;

import java.io.IOException;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.tagext.*;

public class ConditionHandler extends TagSupport {

private boolean condition;

public void setValue (boolean condition) {

this.condition = condition;

}

public int doStartTag () throws JspException {

BodyTagSupport parent = (BodyTagSupport) getParent ();

if (parent == null) {

throw new JspException ("tag format error");

}

int conditionCount = ((Integer) parent.getValue ("conditionCount")).intValue ();

// 设置 "conditionCount" "condition" 属性值

parent.setValue ("conditionCount", new Integer (++conditionCount));

parent.setValue ("condition", new Boolean (condition));

return Tag.SKIP_BODY;

}

}

可以看到,condition标签的处理器修改了while标签处理器的condition属性值。因为condition标签没有正文,所以doStartTag()方法返回Tag.SKIP_BODY。

下面是do标签的处理器的源码:

package coreservlets.tags.loop;

import java.io.IOException;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.tagext.*;

public class DoHandler extends BodyTagSupport {

public int doStartTag () throws JspException {

BodyTagSupport parent = (BodyTagSupport) getParent ();

if (parent == null) {

throw new JspException ("tag format error");

}

// 取得 "doCount" "condition" "conditionCount" 的属性值

int doCount = ((Integer) parent.getValue ("doCount")).intValue ();

int conditionCount = ((Integer) parent.getValue ("conditionCount")).intValue ();

boolean condition = ((Boolean) parent.getValue ("condition")).booleanValue ();

if (conditionCount != 1) {

throw new JspException ("tag format error");

}

// "doCount" 属性值加1

parent.setValue ("doCount", new Integer (++doCount));

// 根据 "condition" 属性值决定是否对正文求值

return (condition ? BodyTag.EVAL_BODY_BUFFERED : Tag.SKIP_BODY);

}

public int doAfterBody () throws JspException {

try {

BodyContent bc = getBodyContent ();

bc.getEnclosingWriter ().write (bc.getString ());

} catch (IOException ie) {

throw new JspException (ie.getMessage ());

}

return Tag.SKIP_BODY;

}

}

在do标签的处理器的doStartTag()方法中,取得while标签的处理器的condition属性,并根据condition属性值来决定是否
对正文求值。如果condition为true,则对正文求值,然后就到了doAfterBody()方法,在这里需要把do标签的
BodyContent中的内容写入到它包装的内层JspWriter中(也就是while标签的BodyContent);如果condition为
false,则跳过do标签的正文。

转自http://hi.baidu.com/ta22/blog/item/1cf5993e87d26bfb838b1371.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: