上一章链接;JavaBean
前面几章基本上将基础都讲完了,现在这一部分开始慢慢深入到JSP的高级运用上。
目录
EL表达式
EL表达式概述
EL表达式的初步运用
全域查找
指定域查找
JavaBean导航
EL内置对象
param¶mValues
header&headerValues
initParam
pageContext
EL函数库
自定义EL函数库
JSP标准标签库
JSTL概述
core核心标签库常用标签
out && set
remove
url
if && choose
foreach
fmt标签库常用标签
formatDate
formatNumber
标签源码分析
自定义标签
自定义标签概述
标签处理类
标签库描述文件
自定义标签案例
空标签体
有标签体
中断执行
附有属性标签
后话
EL表达式,全称Expression Language,其使得访问存储在JavaBean中的数据变得非常简单。从JSP2.0开始,使用EL表达式和动态标签来替代Java脚本,而EL表达式替代的正是<%=...%>的Java脚本。
EL表达式的语法很简单,${expr},其中,expr指的是表达式。
EL表达式可以全域查找某个属性,当查找的属性不存在时,则自动输出空字符串而不是null。用例代码如下:
<%
// 域优先级从上至下
pageContext.setAttribute("aaa", "pageContext_AAA");
request.setAttribute("aaa", "request_AAA");
session.setAttribute("aaa", "session_AAA");
application.setAttribute("aaa", "application_AAA");
%>
${aaa}
结果如下所示:
分别对每一个设置属性的代码进行注释,发现在获取application的域的属性的值的时候,仍然输出的是SESSION域的属性的值,这是由于SESSION没有被销毁的缘故。因此更换一个浏览器访问同页面时,结果显示如下:
EL表达式可以根据以下四种方式来查找指定域中的属性:
<%
// 域优先级从上至下
pageContext.setAttribute("aaa", "pageContext_AAA");
request.setAttribute("aaa", "request_AAA");
session.setAttribute("aaa", "session_AAA");
application.setAttribute("aaa", "application_AAA");
%>
${pageScope.aaa }
${requestScope.aaa}
${sessionScope.aaa }
${applicationScope.aaa }
在这里必须要注意的一点是,表达式中的Scope不能忘记。
EL表达式可以对Bean中的属性进行输出显示,用例代码如下:
需注意的是,只要是满足JavaBean规范的属性都可以这样书写。
<%
Student stu = new Student();
stu.setName("张三");
stu.setAge(18);
stu.setSex("男");
request.setAttribute("stu", stu);
%>
${requestScope.stu.name }
${requestScope.stu.age }
${requestScope.stu.sex }
/*
requestScope.stu.name == request.getAttribute("stu").getName();
*/
找到JSP页面生成的.java文件,查看源码如下所示(只以其中一条为例):
查看PageContextImpl源码发现其实现PageContext抽象类,其中,返回值为Object类型的proprietaryEvaluate()是专门用于输出EL表达式的方法,其源码在org.apache.jasper.runtime.PageContextImpl。由于源码过于复杂化,在这里不过多的讲解源码。
EL内置对象有11个,内置对象中有10个是Map类型,剩下一个是pageContext类型。在前面已经了解到了前四个,即pageScope、requestScope、SessionScope、applicationScope。剩下的七个内置对象分别为:param、paramValues、header、headerValues、initParam、cookie、pageContext。接下来分别一一演示其用法。
param和paramValues这两个内置对象是用来获取请求参数的。
param:Map
paramValues:Map
用例代码如下所示:
${param.username }
// 等价于 request.getParameter("username");
${paramValues.ways[0] }
// 等价于 request.getParameterValues("ways")[0];
通过如下地址访问:demo03.jsp?username=zhangsan&ways=phone&ways=telephone,结果如下所示:
header和headerValues这两个内置对象用于获取请求头中的内容。
header:Map类型,key代表头名称,value是单个头值,适用于单值请求头。
headerValues:Map类型,key代表头名称,value是多个头值,适用于多值请求头。
用例代码如下:
${header['User-Agent'] }
// 如果属性名中有带有如斜杠或者横杠等特殊字符,则需要用另一种方式去访问
// 即map['key']
// 等价于 request.getHeader("User-Agent");
结果如下所示:
initParam这个内置对象用于获取web.xml中的
web.xml配置如下:
context-param
context-value
用例代码如下所示;
${initParam['context-param'] }
结果如下所示:
cookie:Map
用例代码如下所示:
${cookie.JSESSIONID.value }
/* 等价于
*
* Cookie[] cookies = request.getCookies();
* String name = cookie.getName();
* if(“JSESSIONID”.equals(name)) {
* String value = cookie.getValue();
* }
*
*/
结果如下所示:
pageContext内置对象是EL表达式最为独特的一个,依旧是“一个顶九个”的作用。
用例代码如下所示:
click here
结果如下所示:
从结果来看,不难发现,以后用这样的格式去代替固定项目名路径即可达到便利性。即复制源文件至另一个项目不需要更改所有源文件内中的项目路径。
EL函数库是由第三方对EL的扩展,其实际上就是定义一些有返回值的静态方法,通过EL语言来调用它们。
首先得说明一下,如果是MyEclipse的话会自带一个名为jstl-imp的jar包,而标签库就在jar包里。
打开这个jar包,在META-INF里会有一个fn.tld文件,用记事本打开,可以看到其内部构造。
这一块是对EL函数库的描述。需要注意的地方就是
这一大块的function即是EL函数库中定义的众多函数的其中之一。其中,
在之前有说过JSP指令中有一个taglib指令,其用于导入标签库的,现在就可以用上了,即
<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
其中,prefix代表了标签库的前缀名,uri指定标签库的位置。
EL函数库的函数如下所示:
函数 |
说明 |
String toUpperCase(String input) |
参数转换成大写 |
String toLowerCase(String input) |
参数转换成小写 |
int indexOf(String input, String substring) |
大串输出小串的位置 |
boolean contains(String input, String substring) |
查看大串中是否包含小串 |
boolean containsIgnoreCase(String input, String substring) |
忽略大小写的是否包含 |
boolean startsWith(String input, String substring) |
是否以小串为前缀 |
boolean endsWith(String input, String substring) |
是否以小串为后缀 |
String subString(String input, int beginIndex, int endIndex) |
截取子串 |
String subStringAfter(String input, String substring) |
获取大串中小串所在位置的后面的字符串 |
String subStringBefore(String input, String substring) |
获取大串中小串所在位置的前面的字符串 |
String escapeXml(String input) |
把字符串中"<", ">", "&"," ' "," " "的部分进行转义 |
String trim(String input) |
去除前后空格 |
String replace(String input, String substringBefore, String subStringAfter) |
替换 |
String[] split(String input, String delimiters) |
分割字符串,得到字符串数组 |
int length(Object obj) |
可获取字符串、数组、集合的长度 |
String join(String array[], String separator) |
联合字符串数组 |
用例代码如下所示:
<%
String str = "hello world!";
String[] strs = {"h", "e", "l", "l", "o"};
pageContext.setAttribute("str", str);
pageContext.setAttribute("strs", strs);
%>
${fn:length(str) }
${fn:length(strs) }
${fn:toLowerCase(str) }
${fn:toUpperCase(str) }
${fn:indexOf(str, "o") }
${fn:contains(str, "Wor") }
${fn:containsIgnoreCase(str, "Wor") }
${fn:startsWith(str, "hell") }
${fn:endsWith(str, "ld") }
${fn:join(strs, "") }
${fn:join(fn:split(str, " "), "-") }
${fn:replace(str, "!", "!!!") }
${fn:substring(str, 1, 7) }
${fn:substring(str, 1, -1) }
${fn:substringAfter(str, " ") }
${fn:substringBefore(str, " ") }
${fn:trim(" x X x ") }
${fn:escapeXml("") }
其中需要注意的几个地方:
pageContext.setAttribute()这个方法是关键,对于EL表达式中的数据访问,其等价于getAttribute(),并且根据优先级:page域、request域、session域、application域来查找数据。
substring的范围是[beginIndex, endIndex),当endIndex为-1时,即从beginIndex索引开始到最后的串都获取。
escapeXml根据其作用,可以防止JavaScript攻击。
如何自定义EL函数库呢?在上面就说过,EL函数库实际上就是定义一些有返回值的静态方法。那么只要新建一个类,在这个类中写有返回值的静态方法即可。并且参照fn.tld文件中的格式来编写自定义EL函数库。
那么以下为用例代码:
MyFunction.java
package it.test
public class MyFunction {
pulbic static String test() {
return "自定义EL函数库";
}
}
test.tld(/WEB-INF/tlds/)
testFunction
My Function
1.0
myfn
http://www.xxxxxx.com/jsp/functions
description.
test
it.test.MyFunction
java.lang.String test()
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="myfn" uri="/WEB-INF/tlds/test.tld" %>
Insert title here
${myfn:test() }
如果需要在支持表达式语言的页面中正常输出“$”符号,则在“$”符号前加转义字符“\”即可。
JSP标准标签库(JSTL)是apache对EL表达式的扩展,即一个JSP标签集合。
其中,sql标签库和xml标签库已经过时,只需要学习前面两种即可。
out标签用于输出数据,其中有三个属性:value、default、escapeXml。分别解释下上述三个属性,
value:输出的值,可以是字符串常量或者EL表达式
default:默认值,若输出的内容为空时,就会输出default的值
escapeXml:和EL函数库中的escapeXml作用一样,表示转义,默认值为true。
以下为用例代码:
// value为字符串常量
// value为EL表达式
// JavaScript攻击
<%
request.setAttribute("code", "");
%>
显示结果如下:
set标签用于创建域的属性,其中附有三个属性:var、value、scope,分别代表着属性名、属性值、所属域。
用例代码如下所示:
// scope默认为page域,在pageContext中添加值为zhangsan的name属性
// 指定scope是session域
显示结果如下所示:
remove标签用于删除域变量。既然是删除域变量,就得指定要删除的变量名var和所属的域scope。
需要注意的是,其是删除所有域中var的数据。
用例代码如下所示:
<%
pageContext.setAttribute("loc", "pageContext");
request.setAttribute("loc", "request");
session.setAttribute("loc", "session");
application.setAttribute("loc", "application");
%>
// 通过以下两段代码可以得知remove是删除所有域中的loc属性
url标签由三种属性value、var、scope和一个子标签param构成。其中,
value:指定一个路径,会在路径前自动添加项目名
var:指定变量名,若添加了这个属性,则url标签不会输出至页面而会将生成的url保存到域中。
scope:和var同时存在,用来指定url保存在哪个域中。
子标签param有两种属性:name、value,分别代表参数名和参数值。在这里必须提及的一点是:param标签会对参数自动进行url编码。
用例代码如下所示:
// 输出 /项目名/index.jsp
结果如图所示:
if标签对应java中的if语句,其只有一个属性test,而且必须是boolean类型的值,如果test的值为true,则执行if标签内的内容。反之,则不执行。
用例代码如下所示:
choose标签对应java中的if-else if...else语句。在choose标签内还有一个子标签when和子标签otherwise。子标签的when中也只有一个属性test,用于判断条件是true或者false。当所有when标签的test都为false时,才会执行otherwise的内容。
用例代码如下所示:
无username参数
存在参数username,值为:${param.username}
以demo01.jsp?username="zhangsan"访问,结果如下所示:
foreach为循环标签,用于循环遍历数组、集合,其有两种使用方式。
第一种是计数方式,何为计数方式呢?这里先说一下其包含的属性:var、begin、end、step。
var:循环变量
begin:设置循环变量从几开始
end:设置循环变量到几结束
step:设置循环变量递增的幅度,默认值为1
看到上面这四种属性的解释,已经可以知道其格式,以下用代码演示:
${i}
第二种是循环遍历集合,而属性只有两个:items、var。
items:指定被循环的对象,可以是个数组或是个集合
var:用于数组或者集合的每个元素依次赋值
用例代码如下所示:
<%
String[] strs = {"hello", "world"};
request.setAttribute("strs", strs);
%>
${str }
forEach标签还有一个独特的属性:varStatus,这个属性用于指定接收“循环状态”的变量名,其可以获取以下五种状态:
count:int类型,当前已遍历的元素个数
index:int类型,当前元素的索引值
first:boolean类型,判断是否为第一个元素
last:boolean类型,判断是否为最后一个元素
current:Object类型,表示当前元素
用例代码如下所示:
<%
ArrayList list = new ArrayList();
list.add("one");
list.add("two");
list.add("three");
pageContext.setAttribute("list", list);
%>
${vs.index } ${vs.count } ${vs.first } ${vs.last } ${vs.current}
结果如下所示:
fmt标签库最常用的两种就是对日期的格式化和对数字的格式化,用法简单不过多叙述,记得导入对应的标签库即可。
formatDate标签只有两个属性,一个是需要格式化的数据属性value,另一个则是用于给定格式的属性pattern
格式化时间代码如下所示:
<%@ taglib prefix = "fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%
Date date = new Date();
pageContext.setAttribute("date", date);
%>
直接上代码:
<%
double d = 3.1415926;
pageContext.setAttribute("d", d);
%>
代码中两种不同格式结果是不一样的,两者一样都能四舍五入。
第一个格式的处理是:保留小数点后两位,如果不足两位,以0补位。
而第二个格式的处理则是:保留小数点后两位,如果不足两位,不补位。
以下列代码为例:
// value为字符串常量
打开tomcat生成的jsp的java文件,不难发现,使用标签就是在调用方法:
if (_jspx_meth_c_005fout_005f0(_jspx_page_context))
return;
out.write("
\r\n");
这里的_jspx_page_context和pageContext都是同一个对象的引用。在这个奇怪名字的函数里面,做了以下事情:
javax.servlet.jsp.PageContext pageContext = _jspx_page_context;
javax.servlet.jsp.JspWriter out = _jspx_page_context.getOut();
// 这里有一个_jspx_th_c_005fout_005f0对象的定义
try {
_jspx_th_c_005fout_005f0.setPageContext(_jspx_page_context);
_jspx_th_c_005fout_005f0.setParent(null);
_jspx_th_c_005fout_005f0.setValue("hello");
int _jspx_eval_c_005fout_005f0 = _jspx_th_c_005fout_005f0.doStartTag();
...
}
其中setParent方法为设置父标签,setValue是指标签的属性值,而Value是这个标签类的成员变量。至于doStartTag是标签执行方法。
仅仅有JSTL标签库并不能满足过多的要求,那么自定义标签就产生了。在JSP中,所谓的标签使用其实就等同于调用某个对象的某个方法。因此自定义标签只需要做两件事:定义一个标签处理类,编写标签库描述文件(即TLD文件)。标签对应的类称之为“标签处理类”。
这里稍微提及一下历史:早期Java提供了一个Tag接口,然后后来又提供了一个SimpleTag接口,而此时Tag和SimpleTag变成一个体系却是两个互不相关的个体。后来在Jsp2.0又创建了一个JspTag接口,为这两个接口的父接口。这些变迁都是为了简化自定义标签。
那么现在来看一下SimpleTag接口:
void doTag() // 标签执行方法
JspTag getParent() // 获取父标签 (非生命周期方法)
void setJspBody(JspFragment jspBody) // 设置标签体内容
void setJspContext(JspContext pc) // 设置pageContext
void setParent(JspTag parent) // 设置父标签
其实除了getParent()方法以外其他方法是Tomcat调用的,无需编程人员调用,只需要知道标签的生命周期。标签的生命周期是会先调用其余三个方法,最后调用doTag()方法。
这里需要注意的是:父标签是动态标签,HTML标签并不属于这一类。
那么自定义标签只需要实现SimpleTag接口即可。但是实际上,只有doTag()方法需要重写,因此Java又提供了一个SimpleTagSupport类。
SimpleTagSupport类实现了SimpleTag接口,也就是说,只需要继承该类并且重写doTag()方法即可创建新的标签。
在前面的时候就已经介绍过tld,现在介绍一下其中的子标签tag:
name:指定当前标签的名称
tag-class:指定当前标签的标签处理类
body-content:指定标签体的类型
即类似于HTML中
,非成对标签。用例代码如下所示:
标签处理类:
public class MyTag1 extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
this.getJspContext().getOut().print("Hello Tag");
}
}
tld文件:
1.0
ittest
http://www.baidu.com/tags/it-1.0
mytag1
it.test.tag.MyTag1
empty
demo.jsp:
结果如图所示:
有标签体即有内容的标签。那么用例代码如下所示:
标签处理类:
public class MyTag2 extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
Writer out = this.getJspContext().getOut();
out.write("***************
");
// 执行标签体内容,把结果写到指定的流中
this.getJspBody().invoke(out);
out.write("
***************");
}
}
tld文件:
mytag2
it.test.tag.MyTag2
scriptless
demo.jsp:
Hello ${param.name }
结果如图所示:
在Java中有一个异常专门用于跳过页面显示内容,即SkipPageException。Tomcat当得到这个异常时,将会跳过页面其他的内容。用例代码如下所示:
标签处理类:
public class MyTag3 extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
this.getJspContext().getOut().print("阻断显示");
throw new SkipPageException();
}
}
tld文件:
mytag3
it.test.tag.MyTag3
empty
demo.jsp:
Hello ${param.name }
结果如图所示:
类似于c标签中的choose的子标签有一个boolean类型的test属性一样,在标签中的属性其实就是标签处理类的成员变量。
用例代码如下所示:
标签处理类:
public class MyTag4 extends SimpleTagSupport {
private boolean flag;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void doTag() throws JspException, IOException {
if(flag) {
//传递null,则表示使用的就是当前页面的out
this.getJspBody().invoke(null);
}
}
}
tld文件:
mytag4
it.test.tag.MyTag4
scriptless
flag
true
true
其中,required指定属性是否必须。rtexprvalue,指定属性是否能接受请求时表达式的值,默认为false,表示不能接受请求时表达式的值。
demo.jsp:
没有name参数,执行阻断显示
结果如图所示:
'''
EL表达式还有一些运算符的东西并没有拿出来讲,这些是可以自行测试的也过于简单
这一章最主要还是学会使用EL表达式和JSTL以及理解JSP中所谓的标签本质即可
'''