JSP Tag Files 技术总结

简介: 最新完整的源码在: http://code.taobao.org/p/bigfoot_v2/src/tags/。首先声明 Tag File 是门老技术,好用之余知道的人却不多!简介 以前我们抽取一段JSP代码,整合到完整的页面中,一般使用 include 指令(例如),这比较简单的说。

最新完整的源码在: 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" %>
<myTag:Hello />

其中,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文件目录,当页面使用 <ui:xxxx> 会查找该目录下对应的 xxxx.tag 文件。
  • prefix:指定使用时标签前缀

使用:<test:xxxx />

强大的包含接口 attribute

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

<jsp:forward page="add.jsp">
    <jsp:param name="a" value="1" />
    <jsp:param name="b" value="2" />
</jsp:forward>

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

<%@tag description="header 內容" pageEncoding="UTF-8"%>
<%@attribute name="title" type="java.lang.String" description="标题" require="true"%>
<head>
    <title>${title}</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head> 

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

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="html" tagdir="/WEB-INF/tags" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
    <html:Header title="首页"/>
    <body>
         Foo...
    </body>
</html> 

像“title="首页"”这样就完成了 title “参数”的传递!当然准确说是 Attribute 属性。并且声明该项属性不能不填(require="true")。而且要求是 String 类型,别的不行哦。还带有 description 说明呢。另外请注意 type 这里不支持泛型,所以没有 Map<String, Object> 那样的写法——填 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"%>
<html>
    <head>
        <title>${title}</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <jsp:doBody/>
    </body>
</html>

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

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

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib  prefix="html" tagdir="/WEB-INF/tags" %>
<html:Html title="新增書籤">
    <form method="post" action="add.do">
        網址 http:// <input name="url" value="\${param.url}"><br>
        網頁名稱:<input name="title" value="\${param.title}"><br>
        分  類:<input type="text" name="category"
                           value="\${param.category}"><br>
        <input value="送出" type="submit"><br>
    </form>
</html:Html>

哈哈~不知你看到没有,<jsp:doBody /> 所指的就是 <html:Html title=...>(这里一大段的 HTML) </html:Html> 中间的内容,整段 Form 标签都传过去 Tag File 里面了。

更妙的是,<html:Html title=...>……</html:Html> 里面还可以是包含有 <%%> 的 incule 指令,注意是包含 <%%>!

<html:Html title=...>
   <%@include file="public/nav.jsp"%>
</html:Html>

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

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

<%@tag pageEncoding="UTF-8" description="呼叫客户端组件"%>
<%@attribute fragment="true" name="button" required="false" description="按钮"%>
<section class="openClient">
	<jsp:doBody />
</section>
<%@taglib prefix="dhtml" tagdir="/WEB-INF/tags/common/dhtml"%>
<dhtml:msgBox title="温馨提示:">
	<div align="center">
		<jsp:invoke fragment="button"/>
	</div>
</dhtml:msgBox>
<jsp:invoke fragment="button"/> 嵌入到 dhtml:msgBox 模板中去了。

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

<%@attribute name="frag1" fragment="true"%>
<%@attribute name="frag2" fragment="true"%>
<table border="1">
    <tr>
        <td><b>frag1</b></td>
        <td><jsp:invoke fragment="frag1" /></td>
    </tr>
    <tr>
        <td><b>frag2</b></td>
        <td><jsp:invoke fragment="frag2" /></td>
    </tr>
</table>

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

<%@taglib prefix="caterpillar" tagdir="/WEB-INF/tags/"%>
<html>
<body>
    <caterpillar:table>
        <jsp:attribute name="frag1">
            Fragment 1 here
        </jsp:attribute>
        <jsp:attribute name="frag2">
            Fragment 2 here
        </jsp:attribute>
    </caterpillar:table>
</body>
</html> 

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

<html>
<body>
	<table border="1">
		<tr>
			<td><b>frag1</b></td>
			<td>Fragment 1 here</td>
		</tr>
		<tr>
			<td><b>frag2</b></td>
			<td>Fragment 2 here</td>
		</tr>
	</table>
</body>
</html>

该小节小结如下:

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

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

返回变量给你:互通有无

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

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

<%@attribute name="preserve" fragment="true" %>
<%@variable name-given="code" scope="NESTED" %>
<jsp:doBody var="code" />
<table border="1">
    <tr>
        <td>
            <pre><jsp:invoke fragment="preserve"/></pre>
        </td>
    </tr>
</table> 

本体文件:

<%@taglib prefix="caterpillar" tagdir="/WEB-INF/tags"%>
<html>
<body>
    <caterpillar:precode>
        <jsp:attribute name="preserve">
            <b>${ code }</b>
        </jsp:attribute>
        <jsp:body>
            PROGRAM MAIN
            PRINT 'HELLO'
            END
        </jsp:body>
    </caterpillar:precode>
</body>
</html> 

也许大家会问,这个所谓“强大”的功能有什么用呢?——很简单,你想想,把逻辑封装起来里,让变化的只是标签,也就是本体里面的——实际上也是如此,通常变化的都是 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" %>
<ul>
	<%
		// 列出栏目
		if(Util.isNotNull(array)){
			for(Map item : array){
				jspContext.setAttribute("current", item);
	%>
			<li>
				<jsp:invoke fragment="itemTag" />
			</li>
	<%
			}
		}
	%>
</ul>

本体:

<menu class="sub">
	<%@taglib prefix="commonTag" tagdir="/WEB-INF/tags/common/html"%>
	<commonTag:iterator array="${results}">
		<jsp:attribute name="itemTag">
			<a href="${pageContext.request.contextPath}/blog/${current.uid}.sectionList">${current.name}</a>
		</jsp:attribute>
	</commonTag:iterator>
</menu>

设置 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? '' : '<ul>'}
	<%
		// 列出栏目
		if(Util.isNotNull(array)) {
			for(Map<?, ?> item : array) {
				jspContext.setAttribute("current", item);
	%>
	${isOutterItemInclude? '' : '<li>'}
		<jsp:invoke fragment="itemTag" />
	${isOutterItemInclude? '' : '</li>'}
<%
			}
		}else{
			System.out.println("iterator.tag 列表控件输出没有数据");
%>
			<span class="noRecord">没有记录</span>
			<%
		}
	%>${isOutterInclude? '' : '</ul>'}

参见资源:

目录
相关文章
|
7月前
|
存储 XML SQL
【JavaWeb】渲染技术Jsp
JSP 全称是 Java Server Pages,Java 的服务器页面 JSP 这门技术的最大的特点在于,写 JSP 就像在写 HTML 相比 html 而言,html 只能为用户提供静态数据,而 JSP 技术允许在页面中嵌套 java 代码, 为用户提供动态数据 相比 Servlet 而言,Servlet 很难对数据进行排版,而 jsp 除了可以用 java 代码产生动态数据的同时,也很容易对数据进行排版。
|
3月前
|
SQL 前端开发 Java
JSP技术详解及其在Web开发中的应用
【1月更文挑战第2天】本文将对JSP(Java Server Pages)技术进行详细的介绍和分析。JSP是一种基于Java的服务器端编程技术,它允许开发者在HTML或XML等文档中直接嵌入Java代码片段,从而动态地生成Web页面内容。本文将首先阐述JSP的基本原理和工作机制,然后讨论其在Web开发中的各种应用场景,包括表单处理、数据库访问、会话管理等,并通过实例代码展示JSP的实际应用。最后,本文将对JSP的优缺点进行评述,并对未来的发展趋势进行展望。
124 10
|
3月前
|
Java 数据库连接 数据库
通过JSP、JavaBean、JDBC、Servlet技术,实现用户登录功能
通过JSP、JavaBean、JDBC、Servlet技术,实现用户登录功能
|
8月前
|
存储 前端开发 Java
|
4月前
|
开发框架 前端开发 JavaScript
毕业设计论文--外文翻译:JSP技术简介
毕业设计论文--外文翻译:JSP技术简介
|
6月前
|
SQL 前端开发 Java
JSP个人信息管理系统myeclipse开发sql数据库BS模式java编程struts2技术mvc框架
JSP 个人信息管理系统是一套完善的web设计系统,对理解JSP java编程开发语言有帮助struts2技术mvc框架,系统具有完整的源代码和数据库,开发环境为TOMCAT7.0,Myeclipse8.5开发,数据库为Mysql5.0,使用java语言开发,系统主要采用B/S模式开发。
57 0
|
6月前
|
SQL 前端开发 Java
JSP网上订餐管理系统myeclipse开发sql数据库BS模式java编程servlet技术mvc框架
JSP 网上订餐管理系统是一套完善的web设计系统,对理解JSP java编程开发语言有帮助servlet技术mvc框架,系统具有完整的源代码和数据库,开发环境为TOMCAT7.0,Myeclipse8.5开发,数据库为Mysql5.0,使用java语言开发,系统主要采用B/S模式开发。
39 0
|
6月前
|
前端开发 Cloud Native Java
JSP 技术从问世到淘汰,它到底经历了什么?
JSP 技术从问世到淘汰,它到底经历了什么?
157 0
|
7月前
|
Java 应用服务中间件
jsp技术
jsp技术
117 0
|
8月前
|
SQL XML 前端开发
JavaWeb开发 JSP技术详解(一)
JavaWeb开发 JSP技术详解(一)