JSP Tag Files 技术总结

最新完整的源码在: http://code.taobao.org/p/bigfoot_v2/src/tags/。

首先声明 Tag File 是门老技术,好用之余知道的人却不多!

简介

以前我们抽取一段JSP代码,整合到完整的页面中,一般使用 include 指令(例如<%@include file="public/nav.jsp"%>),这比较简单的说。而今天要介绍的是 include 的“高级版”——Tag Files。它着实十分强大,不仅可以完全替代 include,而且还可以创建高级的可复用标签库,使得快速开发和维护动态网页比以前更加容易,甚至网页作者无须学习 Java 程序语言本身,就能开发出全新的动态网页。学习过程中,发现以下两点比较有趣,而且都是往事:

  • 原来 Tag Files 与 Cold Fusion 颇有渊源——CFML!好生不熟悉啊,当年只在《电脑报》合订本附录上久闻其大名,得知其为 Web 开发先驱中的一名,未竟其标签功能如此强大!
  • 当年接触过的 Ext JS 标签库 Ext TLD,原来也是基于 Tag Files 的!

只恨俺当年有眼不识泰山、相见恨晚呀~呵呵~闲话休提,速速进入 Tag Files 之旅吧!

基本用法

首先说说怎么使用 Tag File。拿一个简单的例子。第一步创建被应用的 HTML 片段,假设当前是 Hello.tag,将它放置在 WEB-INF/tags/ 目录下。你可以在 Tag File 里直接使用 JSP 的语法来制作标签。标签文件的扩展名必须是“.tag”。

<%
 out.println("Hello from tag file.");
%>

然后在页面中使用自定义标签时,需要先导入标签库,再使用标签。具体在 JSP 网页使用 Hello.tag 的方法如下:

<%@ taglib prefix="myTag" tagdir="/WEB-INF/tags" %>

其中,prefix 用于确定标签前缀;而tagdir标签库路径下存放很多 Tag File,每 Tag File 对应一个标签。最后执行的结果如下:

Hello from tag file.

十分简单是吧?其实,再复杂的 Tag Files,也要比原生写 SimpleTag、写 Java 代码来得简单。所有 JSP 里面能做的事情,几乎在 Tag Files 里面都可以做的,包括模板语言 JSTL——不过我就没有推荐使用 JSTL,而是直接 Java if/for 来控制某些页面逻辑,也就是 <% ...Java code...%>。出于学习成本的原因,我不想再重复学习类似的东西。当然,EL 表达式我是推荐使用的,如果能够使用 EL 表达式的,尽量使用,能避免 Java Code <%%> 的尽量避免。

该小节小结如下:

导入格式为 <%@taglib prefix="test" tagdir="/WEB-INF/tags"%>

  • tagdir:用于指定tag文件目录,当页面使用 会查找该目录下对应的 xxxx.tag 文件。
  • prefix:指定使用时标签前缀

使用:

强大的包含接口 attribute

include 指令有个缺点,就是不能对被包含的页面片段进行参数的传递。你可能会想到使用 标签,如:


    
    

虽然可以传参数,但 的方式与接着要介绍的 Tag Files 之 attribute 相比,还是弱很多。attribute 支持依赖性是否可选,类型检查等等更复杂的功能,设置可传递一大段 HTML 过去!例如下面一个例子。

<%@tag description="header 內容" pageEncoding="UTF-8"%>
<%@attribute name="title" type="java.lang.String" description="标题" require="true"%>

    ${title}
    
 

本体(本体的描述乃相对于被包含的 Tag File 而言)调用方式:

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="html" tagdir="/WEB-INF/tags" %>


    
    
         Foo...
    
 

像“title="首页"”这样就完成了 title “参数”的传递!当然准确说是 Attribute 属性。并且声明该项属性不能不填(require="true")。而且要求是 String 类型,别的不行哦。还带有 description 说明呢。另外请注意 type 这里不支持泛型,所以没有 Map 那样的写法——填 type="Map" 即可;数组也没问题,填 type="Map[]" 即可。如果前面有 import="java.util.Map" 的话,这里直接填 type="Map" 即可,无须写全称 type="java.util.Map"。 

你可能会问,像字符串的类型就可以 title="首页" 这样传,但其他类型呢?你可以直接输入值,如 array="<%=homeService.getMsg()%>";也可以把其他类型保存在 request.setAttribute("foo", Object) 中,然后通过 title="${foo}" 传就可以了。另外对于 Tag File 里面的值获取,EL 表达式也是通用的。

该小节小结如下:

tag 指令如同 JSP 网页的 page 指令,用来设定标签文件 <%@tag display-name="" body-content="" dynamic-attributes="" small-icon="" large-icon="" description="" example=""language="" import="" pageEncoding="" isELIgnored="">

  • body-content 表示可能的值有三种,分别是 empty、scriptless、tagdependent、empty。empty 为标签中没有主体内容;scriptlet 为标签中的主体内容 EL、JSP 动作元素,但不可以为 JSP 脚本元素;tagdependent 表示标签中的主体内容交由 tag 自己去处理,默认值为 scriptless;
  • dynamic-attributes 表示设定标签文件动态属性的名称,当 dynamic- attributes 设定时,将会产生一个 Map 类型的集合对象,用来存放属性的名称和值;
  • description 表示用来说明此标签文件的相关信息;
  • example 表示用来增加更多的标签使用说明,包括标签应用时的范例;
  • language、import、pageEncoding、 isELIgnored 这些属性与 page 指令相对应的属性相同。

传递 HTML Fragment

支持 HTML 片段传递是 Tag Files 的一大特点,简直令笔者心灵神往。心想,模板机制能做到这样,非常不错!什么标签的继承,都不在话下!

我们把上面的例子改改,变成 Tag:

<%@tag description="HTML 懶人標籤" pageEncoding="UTF-8"%>
<%@attribute name="title"%>

    
        ${title}
        
    
    
        
    

注意这次 Tag 变成一个完整的 HTML 页面,而且里面有个 特殊标记哦~。

这样的话,那么本体是这样的:

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib  prefix="html" tagdir="/WEB-INF/tags" %>

    
網址 http://
網頁名稱:
分  類:

哈哈~不知你看到没有, 所指的就是 (这里一大段的 HTML) 中间的内容,整段 Form 标签都传过去 Tag File 里面了。

更妙的是,…… 里面还可以是包含有 <%%> 的 incule 指令,注意是包含 <%%>!


   <%@include file="public/nav.jsp"%>

如此一来即可规避 body-content="scriptless" 不可出现 <% %>、<%= %> 或 <%! %> 之问题。

更妙的是,两套模板之间还可以相互嵌套的,请看:

<%@tag pageEncoding="UTF-8" description="呼叫客户端组件"%>
<%@attribute fragment="true" name="button" required="false" description="按钮"%>
<%@taglib prefix="dhtml" tagdir="/WEB-INF/tags/common/dhtml"%>
嵌入到 dhtml:msgBox 模板中去了。

一个 doBoady 是不够的,——来多几个怎么样?回答是绝对肯定的!只要我们把 Attibute 声明为 fragment="true" 即可。例如下面撰写一个 table.tag:

<%@attribute name="frag1" fragment="true"%>
<%@attribute name="frag2" fragment="true"%>
frag1
frag2

在这个 Tag File 中,将 attribute 的属性设定为 Fragment,然后想取得指定的 Fragment 的话,就可以使用 动作元素,并指定 Fragment 的名称,使用下面这个 JSP 网页来测试:

<%@taglib prefix="caterpillar" tagdir="/WEB-INF/tags/"%>


    
        
            Fragment 1 here
        
        
            Fragment 2 here
        
    

 

 JSP 网页中,同样的是使用 来指定 Fragment 的文字内容,那么执行这个 JSP 网页的话会生成以下的内容:



	
frag1 Fragment 1 here
frag2 Fragment 2 here

该小节小结如下:

这个指令用来设定自定义标签的属性。其中 name 表示属性的名字:<%@attribute name="" required="" fragment="" rtexprvalue="" type="" description=""%>

  • required 表示是否为必要,默认为 false;
  • rtexprvalue 表示属性值是 否可以为 run-time 表达式。如为 true,表示属性可用动态的方式来指定,如:,如为 false,则一定要用静态的方式来指定属性值;
  • type 表示这个属性的类型,默认值为 java.lang.String;description用来说明此属性的相关信息

返回变量给你:互通有无

Tag File 运算过的结果,都可以返回给本体,灰常强大是吧!?没错哦~的确可以。我们通过 <%@variable%> 指令完成。

注意下面的例子, doBody 多了 var="code":

<%@attribute name="preserve" fragment="true" %>
<%@variable name-given="code" scope="NESTED" %>

本体文件:

<%@taglib prefix="caterpillar" tagdir="/WEB-INF/tags"%>


    
        
            ${ code }
        
        
            PROGRAM MAIN
            PRINT 'HELLO'
            END
        
    

 

也许大家会问,这个所谓“强大”的功能有什么用呢?——很简单,你想想,把逻辑封装起来里,让变化的只是标签,也就是本体里面的——实际上也是如此,通常变化的都是 HTML 标签。那么要输出的值便是这些变量了,返回给本体,让本体去控制显示。标签这个特性在制作“标签迭代器”的时候十分适用,且看例子:

<%@tag pageEncoding="UTF-8" description="文章功能模块" import="java.util.Map, com.ajaxjs.core.Util"%>
<%@attribute name="array" type="Map[]" required="true" description="要迭代的数据,是一个Map数组"%>
<%@attribute name="itemTag" fragment="true" required="true" description="文章列表"%>
<%@variable name-given="current" %>
    <% // 列出栏目 if(Util.isNotNull(array)){ for(Map item : array){ jspContext.setAttribute("current", item); %>
  • <% } } %>

本体:


	<%@taglib prefix="commonTag" tagdir="/WEB-INF/tags/common/html"%>
	
		
			${current.name}
		
	

设置 name-from-attribute 还可以指定传递变量的名称!由主体来定义而不是 Tag File 来定义,也就是说,把上述例子的 current 改为你喜欢的!

该小节小结如下:

这个指令用来设定标签文件的变量,其中 name-given 表示直接指定变量的名称: <%@variable name-given="" name-from-attribute="" alias="" variable-class="" declare="" scope="" desription="">

  • description 用来说明此变量的相关信息
  • name-from-attribute 表示以自定义标签的某个属性值 为变量名称;
  • alias 表示声明一个局部范围属性,用来接收变量的值;
  • variable-class 表示变量的类名称,默认值为 java.lang.String;
  • declare 表示此变量是否声明默认值为 true;
  • scope 表示此变量的范围,范围是:AT_BEGIN、 AT_END 和 NESTED,默认值为 NESTED;作用范围为"NESTED",也就是在起始卷标与结束卷标之间

如何编译页面的?

Tag File 是自定义标签的简化。事实上,就如同 JSP 文件会编译成 Servlet 一样, TagFile 也会编译成 Tag 处理类,自定义标签的后台依然由标签处理类完成,而这个过程由容器完成。关于编译,参见:

前面提過Tag File會被容器轉譯,實際上是轉譯為javax.servlet.jsp.tagext.SimpleTagSupport的子類別。以Tomcat為例,Errors.tag轉譯後的類別原始碼名稱是Errors_tag.java。在Tag File中可以使用out、config、request、response、session、application、jspContext等隱含物件,其中jspContext在轉譯之後,實際上則是javax.servlet.jsp.JspContext物件。

所以,Tag File在JSP中,並不是靜態包含或動態包含,在Tag File中撰寫Scriplet的話,其中的區域變數也不可能與JSP中Scriptlet溝通。

JspContext是PageContext的父類別,JspContext上定義的API不像PageContext有使用到Servlet API,原本在設計上希望JSP的相關實現可以不依賴特定技術(例如Servlet),所以才會有JspContext這個父類別的存在。

附加一个例子:用 TagFile 完成一个迭代器:

<%@tag pageEncoding="UTF-8" description="文章功能模块" import="java.util.Map, com.ajaxjs.util.Util"%>
<%@tag trimDirectiveWhitespaces="true"%>
<%@attribute name="array" type="Map[]" required="true" description="要迭代的数据,是一个Map数组"%>
<%@attribute name="itemTag" fragment="true" required="true" description="文章列表"%>
<%@attribute name="isOutterInclude" type="Boolean" required="false" description="是否不包括外层标签,外层标签通常指的是 ul"%>
<%@attribute name="isOutterItemInclude" type="Boolean" required="false" description="是否不包括外层 item 标签,外层标签通常指的是 li"%>
<%@variable name-given="current"%>
${isOutterInclude? '' : '
    '} <% // 列出栏目 if(Util.isNotNull(array)) { for(Map item : array) { jspContext.setAttribute("current", item); %> ${isOutterItemInclude? '' : '
  • '} ${isOutterItemInclude? '' : '
  • '} <% } }else{ System.out.println("iterator.tag 列表控件输出没有数据"); %> 没有记录 <% } %>${isOutterInclude? '' : '
'}

参见资源:

  • 我封装的标签库: http://code.taobao.org/p/bigfoot_v2/src/tags/
  • 《JSP2.0  技术手册》
  • Tag File 支持
  • 簡介 Tag File
  • jsp2.0 标记文件(tag)详解,告诉你怎么 include jsp 到 Tag Files
  • JSP2.0 的福利(标签文件)

你可能感兴趣的:(Java,AJAXJS,Framework)