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

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

时间: 2024-08-03 14:02:46

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

自定义JSP标签示例

我们以一个例子来讲解如何自定义JSP标签,假如我们需要在页面中输出当前的时间,按照最简单的JSP脚本,需要在JSP里面写很多Java代码,那么如何来使用自定义标签实现这个功能呢? 首先,我们要先创建一个类,继承TagSupport类: 1 import java.io.IOException; 2 import java.text.SimpleDateFormat; 3 import java.util.Date; 4 import javax.servlet.jsp.JspException;

笔记 - EL表达式 和 JSP标签

一.EL表达式 1 EL作用 jsp的核心语法: jsp表达式 <%=%>和 jsp脚本<%  %>. 以后开发jsp的原则: 尽量在jsp页面中少写甚至不写java代码. 使用EL表达式替换掉jsp表达式                      EL表达式作用: 向浏览器输出域对象中的变量值或表达式计算的结果!!!                      语法: ${变量或表达式}          2 EL语法 1)输出基本数据类型变量 1.1 从四个域获取 ${name}

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

客户化jsp标签技术是在jsp1.1版本中才出现的,他支持用户在jsp文件中自定义标签,这样可以使jsp代码更加简单,这些可重用的标签能够处理复杂的逻辑运算和事物或定义jsp网页的输出内容和格式. 创建客户化jsp标签的步骤: (1)创建标签的处理类 (2)创建标签的描述文件 (3)在jsp文件中引入标签库,然后插入标签.例如:<mm:hello />其中 mm叫做标签前缀,hello叫做标签名 jsp tag API Servlet容器编译jsp网页时,如果遇到自定义标签,就会调用这个标签的

Jsp标签字典开发_基于Spring+Hibernate

目录 1. Jsp标签字典开发_基于Spring+Hibernate 1.1. 简述 1.2. 定义DictItem实体 1.3. 定义字典的@interface 1.4. 定义字典缓存类 1.5. 定义tld标签 1.6. 持久层实体使用注解 1.7. 页面调用jsp标签 2. 补充点 2.1. Hibernate设置属性成功后扫描字典 2.2. Annotation注解 2.2.1. 简述 2.2.2. 元注解 2.2.3. 自定义注解 1. Jsp标签字典开发_基于Spring+Hiber

自定义jsp标签

1.新建一个类继承自TagSupport.BodyTagSupport或实现Tag接口 //对应一个jsp标签public class MyTag extends TagSupport { private JspWriter writer = null; //对应到jsp标签的属性 private String showMsg; //遇到<调用 //合法返回值EVAL_BODY_INCLUDE(显示标签体内容)与SKIP_BODY(不显示标签体内容) @Override public int d

JSP系列:(5)JSP进阶-自定义JSP标签

3.自定义标签 3.1.开发自定义标签的步骤 1)编写一个普通的java类,继承SimpleTagSupport类,叫标签处理器类 2)在web项目的WEB-INF目录下建立rk.tld文件,这个.tld文件叫标签库的声明文件.(参考核心标签库的tld文件) 3) 在jsp页面的头部导入自定义标签库:<%@taglib uri="http://www.lsieun.com/rk" prefix="rk"%> 4) 在jsp中使用自定义标签:<rk:

javaWeb 使用jsp标签进行防盗链

/** * 1.新建类继承SimpleTagSupport * 新建2个属性, 添加对应的set方法 * 覆盖doTag()方法 */ import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.jsp.JspException; import javax.servlet.j

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

一.概述 在我们的系统中,很多时候都用到了权限.最简单的权限就是登录.登录了,我就可以自己的相关信息:没有登录,就不能看到. 目前比较流行的权限框架就是apache shiro和spring security,大家在选择时比较青睐apache shiro,因为spring security的拦截器过多,导致性能下降. 笔者在搭建系统时也是选择了Apache shiro.在权限框架中,最常用的两个地方是: 1.在controller层,使用@RequiresPermissions注解,标识这个链接

jsp标签&amp;EL表达式

1.jsp标签和el表达式     (1)什么是jsp标签?         jsp标签用来替换jsp文件中的java代码,容器遇到jsp标签之后,会依据标签找到标签类然后执行.         注: 因为直接在jsp当中写java代码,不利于jsp文件的维护(比如,将包含有java代码的jsp交给美工去修改就很不方便),所以,               sun才制订了jsp标签技术规范.             使用jsp标签技术,有两大优点:             a.jsp文件维护方便