首页 > WEB开发 > 后台开发 > EL-自定义标签-JSTL
2014
08-17

EL-自定义标签-JSTL

10. EL-JSTL8

EL表达式

EL表达式,实际开发中用于在JSP页面中获取并显示数据,以替代JSP中的脚本表达式部分(<%= %>),Since JSP2.0。

语法:${EL表达式}

1、获取数据

1)获取四大域对象中的属性

原理:如${user},等价于先用pageContext.findAttribute(“user”)找到”user”对应的Object,再将结果输出到浏览器。即EL表达式在执行时,依次从四大域对象中查找相应的对象,找到则返回该对象,找不到则返回””(注意,不是null,而是空字符串。EL表达式中没有空指针异常,也没有数组越界,不支持字符串连接。)

注意:EL表达式只能获取四大域对象中的数据。

2)EL表达式还可以很轻松获取JavaBean的属性,或获取数组、Collection、Map类型集合的数据,例如:

  • ${user.address.city}:address是user中的关联对象
  • ${user.list[0]}:访问有序集合某个位置的元素
  • ${map.key} : 获得map集合中指定key的值

2、EL运算符

EL表达式中除了常见的算术、逻辑、关系运算符外,还需注意如下运算符:

  • empty运算符:检查对象是否为null或“空”,很好用!!!
  • 三目运算符:${user!=null?user.name : “”} ,很好用!!!
  • [ ] 和 . 号运算符:获取数据。

3、EL中的隐含对象

eleven implicit object

注意:

不要把EL中的隐含对象和JSP中的隐含对象搞混,用法也不一样:

${EL隐含对象} 和 <%JSP隐含对象%>

获取消息头时,如果头名称里有“-”,如:Accept-Encoding,必须这样获取:header[“Accept-Encoding”],不能用header.Accept-Encoding。

${cookie.key}取的是cookie对象,如访问cookie的名称和值,用${cookie.key.name}和 ${cookie.key.value}

4、定义和使用EL函数

EL表达式语法允许开发人员开发自定义函数,以调用Java类的方法。

语法:${prefix:method(params)}

在EL表达式中只能调用Java类的静态方法,这个Java类的静态方法需要在TLD(Tag Library Definition)文件中描述,才可以被EL表达式调用。定义EL函数有两个步骤(以字符串小写转大写为例):

1)定义一个普通类,提供实现功能的静态方法

package org.flyne.fn;

public class MyFunctions {
	public static String toUppercase(String s){
		return s.toUpperCase();
	}
}

2)配置EL函数:在WEB-INF目录下建立名为fn.tldXML文件。

<!-- 可从Tomcat的webapps/examples目录下拷贝.tld文件中的部分 -->

<tlib-version>1.0</tlib-version>
<short-name>fn</short-name>
<uri>http://www.flyne.org/jsp/functions</uri>

<function>
	<name>toUpperCase</name>
	<function-class>org.flyne.fn.MyFunctions</function-class>
	<function-signature>java.lang.String toUppercase(java.lang.String)</function-signature>
</function>

说明:

① fn.tld文件存放位置:可放在WEB-INF目录下除了classes和lib目录之外的任何地方。

② <function-signature>元素用于指定Java类中的静态方法的签名,方法签名必须指明方法的返回值类型及各个参数的类型,各个参数之间用逗号分隔。

这样就定义好了一个EL函数,在JSP页面中引入fn.tld文件就可以使用fn.tld中定义的EL函数了:

<!-- 引入.tld:用taglib命令引入tld文件 -->
<%@ taglib uri="http://www.flyne.org/jsp/functions" prefix="fn"%>

<%-- 使用:${prefix:method(params)} --%>
<%
	pageContext.setAttribute("s", "abcdefg");
%>
${fn:toUpperCase(s) }

5、常用EL函数(针对字符串操作)

EL不支持字符串操作,但在JSP页面中显示数据时,经常需要对显示的字符串进行处理,SUN公司针对于一些常见处理定义了一套EL函数库供开发者使用。

这些EL函数在JSTL开发包中进行描述,使用时需在JSP页面导入:<%@taglib uri=”http://java.sun.com/jsp/jstl/functions” prefix=”fn”%>,下面列举部分常用EL函数:

fn:toLowerCase、fn:toUpperCase

fn:trim

fn:length:返回一个集合或数组大小,或返回一个字符串长度,返回值为int类型。

fn:split 、fn:join

fn:indexOf

fn:contains

fn:startsWith

fn:replace

fn:substring

fn:escapeXml

都是些针对字符串操作的函数,且见名知意,不再赘述。

自定义标签(简单标签)

标签用于JSP页面中的逻辑处理,以替代JSP中的Java脚本片段。自定义一个标签有两个步骤:编写标签处理器类(Tag handler)、编写.tld文件并描述标签信息。

1、相关API

10. EL-JSTL2911

上图是Servlet/JSP规范中定义的与标签有关的接口和抽象类,JSPTag接口是所有标签类有关的根接口,本文主要讨论简单标签,因此只用到与简单标签有关的API。

1)SimpleTag接口

该接口中定义了如下方法:

void doTag():遇到自定义标签时由服务器调用执行,用于完成所有的标签逻辑(☆)。

JspTag getParent():得到父标签处理器对象

void setJspBody(JspFragment body):由服务器调用,传入代表标签体的JspFragment对象

void setJspContext(JspContext jc):由服务器调用,传入当前页面的pageContext对象

void setParent(JspTag parent):由服务器调用,传入当前标签的父标签处理器对象

2)SimpleTagSupport类

该类实现了SimpleTag接口,简化了自定义标签的开发。

2、编写一个自定义简单标签

本节编写一个输出客户端访问IP的标签来说明自定义标签的步骤:

1)编写标签处理器,实现SimpleTag接口,或者继承SimpleTagSupport类(简化标签开发)

public class ShowRemoteIpSimpleTag extends SimpleTagSupport {
	public void doTag() throws JspException, IOException {
		PageContext pageContext = (PageContext)getJspContext();
		String ip = pageContext.getRequest().getRemoteAddr();
		pageContext.getOut().write(ip);
	}
}

2)配置标签:WEB-INF目录下建立名为ft.tldXML文件。

<!-- 可从Tomcat的webapps/examples目录下拷贝.tld文件中的部分 -->

<tlib-version>1.0</tlib-version>
<short-name>ft</short-name>
<uri>http://www.flyne.org/jsp/tag</uri>

<tag>
	<name>showRemoteIp</name>
	<tag-class>org.flyne.tag.ShowRemoteIpSimpleTag</tag-class>
	<body-content>empty</body-content>
</tag>

说明:<body-content>元素中指定了标签体类型,有如下四种:

empty:没有主体内容。简单和传统标签都能用。

scriptless:给简单标签用的,说明主体内容不能出现JSP脚本。

tagdependent:把主体内容的EL表达式当做普通字符串对待。

JSP:给传统标签用的,简单标签一用就报错。该类型说明JSP中能出现啥,标签主体内容就能出现啥。

这样就定义好了一个标签,在JSP页面中引入ft.tld文件就可以使用ft.tld中定义的标签了。

<!-- 引入.tld:用taglib命令引入tld文件 -->
<%@ taglib uri="http://www.flyne.org/jsp/tag" prefix="ft" %>

<!-- 使用自定义标签,输出127.0.0.1 -->
<ft:showRemoteIp/>

3、简单标签的执行过程

1) 遇到自定义的简单标签 –> 找到对应的.tld文件 –> 根据<tag>元素找到标签处理器 –> 实例化标签处理器

2)完成上述过程后,接下来就是SimpleTag接口中方法的执行顺序问题(☆)

① web容器调用setJspContext()传入当前页面的pageContext对象

② 如果当前标签存在父标签,web容器调用setParent()传入父标签实例

③ 如果使用标签时设置了属性,容器将调用每个属性对应的setter方法把属性值传递给标签处理器。

④ 如果有标签体,容器将调用setJspBody方法传入代表标签体的JspFragment对象

⑤ 服务器调用调用doTag完成实际的标签处理。

10. EL-JSTL4727

注:在doTag() 方法体内通过操作JspFragment对象,就可以实现是否执行、迭代、修改标签体的目的(☆)。

4、其他问题(☆)

1)JSPFragment类(☆)

WEB容器在处理简单标签的标签体时,会把标签体内容用一个JspFragment对象表示,并调用标签处理器对象的setJspBody方法把JspFragment对象传递给标签处理器对象。在JspFragment类中有个invoke()方法很重要:abstract void invoke(java.io.Writer out)

invoke方法用于执行JspFragment对象所代表的JSP代码片段,参数out用于指定将JspFragment对象的执行结果写入到哪个输出流对象中,如果传递给参数out的值为null,则将执行结果写入到JspContext.getOut()方法返回的输出流对象中。(即直接输出到浏览器)

(☆) 利用invoke方法可以完成如下标签功能:

① 控制主体内容是否显示:在标签处理器中如果没有调用JspFragment.invoke方法,其结果就相当于忽略标签体内容;

② 控制主体内容重复执行:在标签处理器中重复调用JspFragment.invoke方法,则标签体内容将会被重复执行;

③ 改变主体内容后再输出(☆):由于JspFragment并没有提供方法获取主体内容,对此:先将JspFragment的内容写入到一个外部输出流,再从这个外部输出流中拿到主体内容。所以此时在invoke方法中指定的输出流必须能够取出数据,StringWriter可以完成这一过程。标签处理器的doTag()方法如下:

public void doTag() throws JspException, IOException {

	StringWriter sw = new StringWriter();
	//获得标签主体内容
	JspFragment jf = (JspFragment)getJspBody();
	jf.invoke(sw);
	String content = sw.toString();
	//将主体内容变为大写
	content = content.toUpperCase();
	//输出
	PageContext pageContext = (PageContext)getJspContext();
	pageContext.getOut().write(content);
}

再扩展一个和invoke无关的标签功能:控制结束标签后的JSP内容不执行:在标签处理器中throw new SkipPageException(),则结束标签后的内容不输出。

2)开发带属性的标签(☆)

要想让一个自定义标签具有属性,通常需要完成两个任务:① 在标签处理器中编写每个属性对应的setter方法 ② 在TLD文件中描述标签的属性

标签的属性定义通过<tag>的子元素<attribute>完成,相关设置如下:

10. EL-JSTL6003

name:指定标签的属性名

required:是否必须设置属性

rtexprvalue:rtexprvalue是runtime expression value(运行时表达式值)的英文简写,用于指定属性值是一个静态值或动态值。默认为false,表示该属性只能指定一个静态文本值;为true时表示可以为该属性指定一个动态值,如JSP脚本表达式或EL表达式。

type:用于指定属性值的Java类型(基本类型和String类型之外的类型需要指定)

3)打包发行自己的标签库

打包时选中所有标签处理器所在的包,其他目录不用选,打包完成之后,将.tld文件拖到jar包中的META-INF文件夹中即可。

5、案例(仅给出标签处理器代码)

1)实现if语句功能:<ft:if test=”true|false”>

private boolean test;

public void setTest(boolean test) {
	this.test = test;
}

public void doTag() throws JspException, IOException {
	if(test){
		getJspBody().invoke(null);
	}
}

2)实现if…else语句功能:<ft:choose>、<ft:when test=””>、<ft:otherwise>

要实现if…else语句功能,需提供三个标签,对应着三个标签处理器:ChooseSimpleTag、WhenSimpleTag、OtherwiseSimpleTag。

/*
 * ChooseSimpleTag
 * 用flag标记if分支是否执行了
 */
private boolean flag;

public boolean isFlag() {
	return flag;
}

public void setFlag(boolean flag) {
	this.flag = flag;
}

public void doTag() throws JspException, IOException {
	getJspBody().invoke(null);
}

/*
 * WhenSimpleTag
 */
private boolean test;

public void setTest(boolean test) {
	this.test = test;
}

public void doTag() throws JspException, IOException {
	if(test){
		getJspBody().invoke(null);
		ChooseSimpleTag choose = (ChooseSimpleTag)getParent();
		choose.setFlag(true);
	}
}

/*
 * OtherwiseSimpleTag
 */
public void doTag() throws JspException, IOException {
	ChooseSimpleTag choose = (ChooseSimpleTag)getParent();
	if(!choose.isFlag()){
		getJspBody().invoke(null);
	}
}

3)实现for…each语句功能:<ft:forEach items=”” var=”” >

主要注意当items属性值为Map类型和数组类型时,标签处理器是如何处理的。

private Object items;
private String var;//类型写String

//不管是集合、映射、数组,都先用collection存起来(注意中间的处理步骤)
private Collection collection = new ArrayList();;

public void setItems(Object items) {
	if(items instanceof List){
		collection = (List)items;
	}else if(items instanceof Set){
		collection = (Set)items;
	}else if(items instanceof Map){
		//items为Map的处理方式(☆)
		collection = ((Map)items).entrySet();
	}else if(items.getClass().isArray()){
		//items为数组的处理方式(☆)
		int len = Array.getLength(items);//items数组的长度
		for(int i = 0;i < len;i ++){
			collection.add(Array.get(items,i));
		}
	}else{
		throw new RuntimeException("不支持的类型");
	}
}
public void setVar(String var) {
	this.var = var;
}
public void doTag() throws JspException, IOException {
	PageContext pageContext = (PageContext)getJspContext();
	for(Object obj : collection){
		pageContext.setAttribute(var, obj);
		//标签主体内容被重复执行
		getJspBody().invoke(null);
	}
}

JSTL

JSP Standard Tag Libary:JSP标准标签库,Apache提供。JSTL包含了四大标签库和一个函数库:

  • core:核心标签库,包含一些通用逻辑处理
  • format:国际化相关
  • sql:操作数据库。不用了
  • xml:操作XML。不用了
  • functions:即EL函数(见EL表达式部分)

1、核心标签库

1)if标签:if语句

用法一:<c:if test=”” />xxx</c:if>,test支持EL表达式。

用法二:<c:if test=”” var=”” scope=””>xxx</c:if>,var用于将test属性的执行结果保存到某个域对象中。

2)choose标签:if…else if…else语句

<c:choose>
	<c:when test="${count == 0}">
		对不起,没有符合您要求的记录。
	</c:when>
	<c:otherwise>
		符合您要求的记录共有${count}条.
	</c:otherwise>
</c:choose>

3)forEach标签:遍历数组、集合、映射。

用法一:items、var

<c:forEach items="${map}" var="me">
	${me.key}=${me.value}<br/>
</c:forEach>

用法二:begin、end、step

<c:forEach items="${strs}" begin="1" end="5" step="2" var="str">
	${str}<br />
</c:forEach>

用法三:varStatus(高级技巧)

varStatus:指定将代表当前迭代状态信息的对象保存到pageContext域对象中的名称,该对象中的方法如下:

int getIndex()、int getCount() 当前元素的索引、编号

boolean isFirst()、boolean isLast():当前元素是否是第一个、最后一个

案例:用表格的形式输出所有用户信息,实现:① 自动编号 ② 隔行变色 ③ 替换:如遇到male,输出“男”……

<%
	List<Person> list = new ArrayList<Person>();
	list.add(new Person("张三","male","1333333"));
	list.add(new Person("李四","female","13444444"));
	list.add(new Person("王五","male","1355555"));
	pageContext.setAttribute("list",list);
%>

<table border=1 width="50%">
	<tr>
		<th>编号</th>
		<th>姓名</th>
		<th>性别</th>
		<th>联系方式</th>
	</tr>
	<c:forEach items="${list }" var="l" step="1" varStatus="vs">
 	<tr class="${vs.count%2 == 0?'even':'odd' }">
 		<td>${vs.count }</td>
 		<td>${l.name }</td>
 		<td>${l.gender=='male'?'男':'女' }</td>
 		<td>${l.tel }</td>
 	</tr>
	</c:forEach>
</table>

4urlparam标签

<c:url>标签:用于在JSP页面中构造一个URL地址,其主要目的是实现URL重写(将会话标识号以参数形式附加在URL地址后面)。

<c:param>标签:可以嵌套在<c:url>标签中,为该标签所使用的URL地址附加参数,且自动对该参数值进行URL编码

<c:url value="/jstl-demo2.jsp" var="jstlDemoUrl">
	<c:param name="username" value="东风化宇"></c:param>
	<c:param name="password" value="123"></c:param>
</c:url>
<a href="${jstlDemoUrl}">var属性保存构造出来的URL地址</a><br/>

2、国际化标签
暂不更新!


留下一个回复

你的email不会被公开。