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

时间:2022-09-01 17:23:39
2010-08-30 11:30 4555人阅读 评论(0) 收藏 举报
BodyContent揭秘及定制复杂的JSP标签 分类:
HTML/JSP(8) 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

BodyContent揭秘及定制复杂的JSP标签的更多相关文章

  1. 自定义JSP标签库及Properties使用

    自定义JSP标签库及Properties使用 自定义JSP标签 自定义JSP标签技术是在JSP 1.1版本中才出现的,它支持用户在JSP文件中自定义标签,这样可以使JSP代码更加简洁. 这些可重用的标 ...

  2. javaWeb 使用jsp标签进行防盗链

    /** * 1.新建类继承SimpleTagSupport * 新建2个属性, 添加对应的set方法 * 覆盖doTag()方法 */ import java.io.IOException; impo ...

  3. 自制权限框架(一)jsp标签

    一.概述 在我们的系统中,很多时候都用到了权限.最简单的权限就是登录.登录了,我就可以自己的相关信息:没有登录,就不能看到. 目前比较流行的权限框架就是apache shiro和spring secu ...

  4. 一、JSP标签介绍&comma;自定义标签

    一.JSP标签介绍 1. 标签库有什么作用 自定义标签库是一种优秀的表现层技术,之前介绍的MVC模式,我们使用jsp作为表现层,但是jsp语法嵌套在html页面,美工还是很难直接参与开发,并且jsp脚 ...

  5. jsp标签jstl和el表达式

    1.el表达式的使用 1)访问bean的属性 方式一: ${user.name},容器会依次从pageContext,request,session,application中查找(getAttribu ...

  6. Java基础83 JSP标签及jsp自定义标签(网页知识)

    1.JSP标签 替代jsp脚本,用于jsp中执行java代码1.1.内置标签:  <jsp:forward></jsp:forward>  相当于:request.getReu ...

  7. 自定义JSP标签示例

    我们以一个例子来讲解如何自定义JSP标签,假如我们需要在页面中输出当前的时间,按照最简单的JSP脚本,需要在JSP里面写很多Java代码,那么如何来使用自定义标签实现这个功能呢? 首先,我们要先创建一 ...

  8. 自定义标签(客户化jsp标签)

    客户化jsp标签技术是在jsp1.1版本中才出现的,他支持用户在jsp文件中自定义标签,这样可以使jsp代码更加简单,这些可重用的标签能够处理复杂的逻辑运算和事物或定义jsp网页的输出内容和格式. 创 ...

  9. java web 学习笔记 - JSP标签编程

    1.JSP标签编程简介 标签编程在开发中并不常见,主要是为了更好的理解struts等框架的标签而打基础,完善相关知识体系. 标签编程分为: 一个继承自TagSupport的标签类,一个在WEB-INF ...

随机推荐

  1. button点击ajax异步无效的处理办法&comma;以及实现&OpenCurlyDoubleQuote;关注”&OpenCurlyDoubleQuote;已关注”切换

    button并不是在只等于submit时草有提交功能,如果你用它触发ajax事件,你的ajax会失去他最大的优势:刷新局部数据! 但是你如果设置了他的return false;属性小伙伴你的ajax才 ...

  2. DAC重置max server memory

    15:44 2014-01-24 08R2,一次通过GUI更改'最大服务器内存(MB)'为16MB,errorlog显示信息如下 :: . Run the RECONFIGURE statement ...

  3. HDU 4638-Group(线段树&plus;离线处理)

    题意: 给n个编号,m个查询每个查询l,r,求下标区间[l,r]中能分成标号连续的组数(一组内的标号是连续的) 分析: 我们认为初始,每个标号为一个组(线段树维护区间组数),从左向右扫序列,当前标号, ...

  4. mac下的home键、end键以及insert键的替代

    最近用android模拟器模拟东西,发现模拟器的home快捷键是键盘上的home键,这让我在windows下很好找,换到mac下找了老半天也没找到,后来才查到是有替代键的,放到这里做备份 home键f ...

  5. cocos2dx-2&period;x CCFileUtils文件管理分析(2)

    于1于,我只是对整体结构进行了分析,然后,2于,我会在一些我们经常使用的分析功能. //获取给定文件名称的全路径 //以下这非常长一段凝视.通过举样例,像我们说明cocos2dx获取文件全路径的规则. ...

  6. 在 Emacs 中如何退出 Slime Mode

    1.在 Slime 的 Buffer 中按逗号“,”: 2.在 Command 后输入:sayoonara 3.回车,确认. ================ 退出 SBCL 输入:(sb-ext:q ...

  7. Debian下VIM的安装和配置

    1.安装 apt-get install vim 2.配置 这是我的vim 配饰文件,基本的功能都能实现,在这里做一个备份,省的以后重装系统还要到处找这个配置文件(/etc/vim/vimrc) : ...

  8. noi&period;ac&num;309 Mas的童年&lpar;子集乱搞&rpar;

    题意 题目链接 Sol 记\(s_i\)表示前\(i\)个数的前缀异或和,我们每次相当于要找一个\(j\)满足\(0 < j < i\)且\((s_i \oplus s_j) + s_j\ ...

  9. Github只下载某一目录的文件

    比如要下载: https://github.com/xubo245/SparkLearning/tree/master/docs 将“tree/master”改成“trunk https://gith ...

  10. python之CMDB

    浅谈ITIL TIL即IT基础架构库(Information Technology Infrastructure Library, ITIL,信息技术基础架构库)由英国*部门CCTA(Central ...