前面几章基本上将基础都讲完了,现在这一部分开始慢慢深入到JSP的高级运用上。
目录
EL表达式
EL表达式概述
EL表达式的初步运用
全域查找
指定域查找
JavaBean导航
EL内置对象
param¶mValues
header&headerValues
initParam
cookie
pageContext
EL函数库
自定义EL函数库
JSP标准标签库
JSTL概述
core核心标签库常用标签
out && set
remove
url
if && choose
foreach
fmt标签库常用标签
formatDate
formatNumber
标签源码分析
自定义标签
自定义标签概述
标签处理类
标签库描述文件
自定义标签案例
空标签体
有标签体
中断执行
附有属性标签
后话
EL表达式
EL表达式概述
EL表达式,全称Expression Language,其使得访问存储在JavaBean中的数据变得非常简单。从JSP2.0开始,使用EL表达式和动态标签来替代Java脚本,而EL表达式替代的正是<%=...%>的Java脚本。
EL表达式的初步运用
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 } <br/>
${requestScope.aaa} <br/>
${sessionScope.aaa } <br/>
${applicationScope.aaa } <br/>
在这里必须要注意的一点是,表达式中的Scope不能忘记。
JavaBean导航
EL表达式可以对Bean中的属性进行输出显示,用例代码如下:
需注意的是,只要是满足JavaBean规范的属性都可以这样书写。
<%
Student stu = new Student();
stu.setName("张三");
stu.setAge(18);
stu.setSex("男");
request.setAttribute("stu", stu);
%>
${ } <br/>
${requestScope.stu.age } <br/>
${requestScope.stu.sex } <br/>
/*
== request.getAttribute("stu").getName();
*/
找到JSP页面生成的.java文件,查看源码如下所示(只以其中一条为例):
查看PageContextImpl源码发现其实现PageContext抽象类,其中,返回值为Object类型的proprietaryEvaluate()是专门用于输出EL表达式的方法,其源码在org.apache.jasper.runtime.PageContextImpl。由于源码过于复杂化,在这里不过多的讲解源码。
EL内置对象
EL内置对象有11个,内置对象中有10个是Map类型,剩下一个是pageContext类型。在前面已经了解到了前四个,即pageScope、requestScope、SessionScope、applicationScope。剩下的七个内置对象分别为:param、paramValues、header、headerValues、initParam、cookie、pageContext。接下来分别一一演示其用法。
param¶mValues
param和paramValues这两个内置对象是用来获取请求参数的。
param:Map<String, String>类型,等同于request.getParameter()方法。其中key为参数名,value为参数值,适用于单值的参数。
paramValues:Map<String, String[]>类型,一个参数名对应多个参数值时可以使用它。其中key为参数名,value为多个参数值,适用于多值的参数。
用例代码如下所示:
${param.username }
// 等价于 request.getParameter("username");
${paramValues.ways[0] }
// 等价于 request.getParameterValues("ways")[0];
通过如下地址访问:demo03.jsp?username=zhangsan&ways=phone&ways=telephone,结果如下所示:
header&headerValues
header和headerValues这两个内置对象用于获取请求头中的内容。
header:Map类型,key代表头名称,value是单个头值,适用于单值请求头。
headerValues:Map类型,key代表头名称,value是多个头值,适用于多值请求头。
用例代码如下:
${header['User-Agent'] }
// 如果属性名中有带有如斜杠或者横杠等特殊字符,则需要用另一种方式去访问
// 即map['key']
// 等价于 request.getHeader("User-Agent");
结果如下所示:
initParam
initParam这个内置对象用于获取web.xml中的<context-param>内的参数。
web.xml配置如下:
<context-param>
<param-name>context-param</param-name>
<param-value>context-value</param-value>
</context-param>
用例代码如下所示;
${initParam['context-param'] }
结果如下所示:
cookie
cookie:Map<String,Cookie>类型,其中key是Cookie名,而value是Cookie对象本身。
用例代码如下所示:
${cookie.JSESSIONID.value }
/* 等价于
*
* Cookie[] cookies = request.getCookies();
* String name = cookie.getName();
* if(“JSESSIONID”.equals(name)) {
* String value = cookie.getValue();
* }
*
*/
结果如下所示:
pageContext
pageContext内置对象是EL表达式最为独特的一个,依旧是“一个顶九个”的作用。
用例代码如下所示:
<a href="${pageContext.request.contextPath }/EL/demo02.jsp">click here</a>
<!--
等价于pageContext.getRequest().getContextPath();
-->
结果如下所示:
从结果来看,不难发现,以后用这样的格式去代替固定项目名路径即可达到便利性。即复制源文件至另一个项目不需要更改所有源文件内中的项目路径。
EL函数库
EL函数库是由第三方对EL的扩展,其实际上就是定义一些有返回值的静态方法,通过EL语言来调用它们。
首先得说明一下,如果是MyEclipse的话会自带一个名为jstl-imp的jar包,而标签库就在jar包里。
打开这个jar包,在META-INF里会有一个fn.tld文件,用记事本打开,可以看到其内部构造。
这一块是对EL函数库的描述。需要注意的地方就是<uri></uri>这个标签,其中的值将会在taglib中用到。
这一大块的function即是EL函数库中定义的众多函数的其中之一。其中,
- 子标签<description></description>用来描述函数的作用;
- 子标签<name></name>为函数名称;
- 子标签<function-class></function-class>为函数所在的类文件编译的class;
- 子标签<function-signature></function-signature>为函数具体的声明;
- 子标签<example></exam>为函数的使用示范。
在之前有说过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) } <br/>
${fn:length(strs) } <br/>
${fn:toLowerCase(str) } <br/>
${fn:toUpperCase(str) } <br/>
${fn:indexOf(str, "o") } <br/>
${fn:contains(str, "Wor") } <br/>
${fn:containsIgnoreCase(str, "Wor") } <br/>
${fn:startsWith(str, "hell") } <br/>
${fn:endsWith(str, "ld") } <br/>
${fn:join(strs, "") } <br/>
${fn:join(fn:split(str, " "), "-") } <br/>
${fn:replace(str, "!", "!!!") } <br/>
${fn:substring(str, 1, 7) } <br/>
${fn:substring(str, 1, -1) } <br/>
${fn:substringAfter(str, " ") } <br/>
${fn:substringBefore(str, " ") } <br/>
${fn:trim(" x X x ") } <br/>
${fn:escapeXml("<script></script>") } <br/>
其中需要注意的几个地方:
pageContext.setAttribute()这个方法是关键,对于EL表达式中的数据访问,其等价于getAttribute(),并且根据优先级:page域、request域、session域、application域来查找数据。
substring的范围是[beginIndex, endIndex),当endIndex为-1时,即从beginIndex索引开始到最后的串都获取。
escapeXml根据其作用,可以防止JavaScript攻击。
自定义EL函数库
如何自定义EL函数库呢?在上面就说过,EL函数库实际上就是定义一些有返回值的静态方法。那么只要新建一个类,在这个类中写有返回值的静态方法即可。并且参照fn.tld文件中的格式来编写自定义EL函数库。
那么以下为用例代码:
MyFunction.java
package it.test
public class MyFunction {
pulbic static String test() {
return "自定义EL函数库";
}
}
test.tld(/WEB-INF/tlds/)
<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<description>testFunction</description>
<display-name>My Function</display-name>
<tlib-version>1.0</tlib-version>
<short-name>myfn</short-name>
<uri>http://www.xxxxxx.com/jsp/functions</uri>
<function>
<description>
description.
</description>
<name>test</name>
<function-class>it.test.MyFunction</function-class>
<function-signature>java.lang.String test()</function-signature>
</function>
</taglib>
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="myfn" uri="/WEB-INF/tlds/test.tld" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
${myfn:test() }
</body>
</html>
如果需要在支持表达式语言的页面中正常输出“$”符号,则在“$”符号前加转义字符“\”即可。
JSP标准标签库
JSTL概述
JSP标准标签库(JSTL)是apache对EL表达式的扩展,即一个JSP标签集合。
- JSTL共包含了以下四大标签库:
- core:核心标签库
- fmt:格式化标签库
- sql:数据库标签库
- xml:xml标签库
其中,sql标签库和xml标签库已经过时,只需要学习前面两种即可。
core核心标签库常用标签
out && set
value、default、escapeXml。分别解释下上述三个属性,
value:输出的值,可以是字符串常量或者EL表达式
default:默认值,若输出的内容为空时,就会输出default的值
escapeXml:和EL函数库中的escapeXml作用一样,表示转义,默认值为true。
以下为用例代码:
// value为字符串常量
<c:out value="hello" /> <br/>
// value为EL表达式
<c:out value="${code }" default="???"/> <br/>
// JavaScript攻击
<%
request.setAttribute("code", "<script>alert('hello');</script>");
%>
<c:out value="${code }" default="null" escapeXml="true" />
显示结果如下:
var、value、scope,分别代表着属性名、属性值、所属域。
用例代码如下所示:
// scope默认为page域,在pageContext中添加值为zhangsan的name属性
<c:set var="name" value="zhangsan" />
// 指定scope是session域
<c:set var="sex" value="male" scope="session" />
显示结果如下所示:
remove
remove标签用于删除域变量。既然是删除域变量,就得指定要删除的变量名var和所属的域scope。
其是删除所有域中var的数据。
用例代码如下所示:
<%
pageContext.setAttribute("loc", "pageContext");
request.setAttribute("loc", "request");
session.setAttribute("loc", "session");
application.setAttribute("loc", "application");
%>
// 通过以下两段代码可以得知remove是删除所有域中的loc属性
<c:remove var="loc" />
<c:out value="${loc }" default="null" />
url
value、var、scope和一个子标签param构成。其中,
value:指定一个路径,会在路径前自动添加项目名
var:指定变量名,若添加了这个属性,则url标签不会输出至页面而会将生成的url保存到域中。
scope:和var同时存在,用来指定url保存在哪个域中。
param标签会对参数自动进行url编码。
用例代码如下所示:
<!-- 自动添加项目名 等同于pageContext.getRequest().getContextPath(); -->
<c:url value="/index.jsp" /> // 输出 /项目名/index.jsp
<!-- 把本该输出的URL赋给request域的indexUrl属性 -->
<c:url value="/index.jsp" var="indexUrl" scope="request" />
<!-- 子标签的使用 如果参数中包含中文,则会自动使用URL编码 -->
<c:url value="/index.jsp">
<c:param name="username" value="张三" />
</c:url>
结果如图所示:
if && choose
if标签对应java中的if语句,其只有一个属性test,而且必须是boolean类型的值,如果test的值为true,则执行if标签内的内容。反之,则不执行。
用例代码如下所示:
<!-- 设置一个page域属性name的值为zhangsan -->
<c:set var="name" value="zhangsan" />
<c:if test="${not empty name}" >
<c:out value="${name }" />
</c:if>
choose标签对应java中的if-else if...else语句。在choose标签内还有一个子标签when和子标签otherwise。子标签的when中也只有一个属性test,用于判断条件是true或者false。当所有when标签的test都为false时,才会执行otherwise的内容。
用例代码如下所示:
<!-- choose演示 -->
<c:choose >
<c:when test="${empty param.username }" >
无username参数
</c:when>
<c:otherwise>
存在参数username,值为:${param.username}
</c:otherwise>
</c:choose>
以demo01.jsp?username="zhangsan"访问,结果如下所示:
foreach
foreach为循环标签,用于循环遍历数组、集合,其有两种使用方式。
var、begin、end、step。
var:循环变量
begin:设置循环变量从几开始
end:设置循环变量到几结束
step:设置循环变量递增的幅度,默认值为1
看到上面这四种属性的解释,已经可以知道其格式,以下用代码演示:
<c:forEach var="i" begin="1" end="10" step="2" >
${i}
</c:forEach>
<!--
这就等同于Java中的
for(int i = 1; i < 10; i+2) {}
-->
items、var。
items:指定被循环的对象,可以是个数组或是个集合
var:用于数组或者集合的每个元素依次赋值
用例代码如下所示:
<%
String[] strs = {"hello", "world"};
request.setAttribute("strs", strs);
%>
<c:forEach items="${strs}" var="str" >
${str } <br>
</c:forEach>
forEach标签还有一个独特的属性:varStatus,这个属性用于指定接收“循环状态”的变量名,其可以获取以下五种状态:
count:int类型,当前已遍历的元素个数
index:int类型,当前元素的索引值
first:boolean类型,判断是否为第一个元素
last:boolean类型,判断是否为最后一个元素
current:Object类型,表示当前元素
用例代码如下所示:
<%
ArrayList<String> list = new ArrayList<String>();
list.add("one");
list.add("two");
list.add("three");
pageContext.setAttribute("list", list);
%>
<c:forEach items="${list}" var="ele" varStatus="vs" >
${vs.index } ${vs.count } ${vs.first } ${vs.last } ${vs.current}
</c:forEach>
结果如下所示:
fmt标签库常用标签
fmt标签库最常用的两种就是对日期的格式化和对数字的格式化,用法简单不过多叙述,记得导入对应的标签库即可。
formatDate
formatDate标签只有两个属性,一个是需要格式化的数据属性value,另一个则是用于给定格式的属性pattern
格式化时间代码如下所示:
<%@ taglib prefix = "fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%
Date date = new Date();
pageContext.setAttribute("date", date);
%>
<fmt:formateDate value="${date }" pattern="yyyy-MM-dd" />
formatNumber
直接上代码:
<%
double d = 3.1415926;
pageContext.setAttribute("d", d);
%>
<fmt:formatNumber value="${d }" pattern="0.000" /> <br/>
<fmt:formatNumber value="${d }" pattern="#.###" /> <br/>
代码中两种不同格式结果是不一样的,两者一样都能四舍五入。
第一个格式的处理是:保留小数点后两位,如果不足两位,以0补位。
而第二个格式的处理则是:保留小数点后两位,如果不足两位,不补位。
标签源码分析
以下列代码为例:
// value为字符串常量
<c:out value="hello" /> <br/>
打开tomcat生成的jsp的java文件,不难发现,使用标签就是在调用方法:
if (_jspx_meth_c_005fout_005f0(_jspx_page_context))
return;
out.write(" <br/>\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:
<tag>
<name></name>
<tag-class></tag-class>
<body-content></body-content>
</tag>
name:指定当前标签的名称
tag-class:指定当前标签的标签处理类
body-content:指定标签体的类型
- body-content元素的可选值:
- empty:无标签体
- scriptless:标签体内容不能是java脚本,但可以是EL、JSTL等
- JSP:标签体内可以是任何东西,但是SimpleTag已不再支持使用这个参数
- tagdependent:意思就是自行处理标签,几乎不会用。
自定义标签案例
空标签体
即类似于HTML中<br/>,非成对标签。用例代码如下所示:
标签处理类:
public class MyTag1 extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
this.getJspContext().getOut().print("Hello Tag");
}
}
tld文件:
<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
version="2.1">
<tlib-version>1.0</tlib-version>
<short-name>ittest</short-name>
<uri>http://www.baidu.com/tags/it-1.0</uri>
<tag>
<name>mytag1</name>
<tag-class>it.test.tag.MyTag1</tag-class>
<body-content>empty</body-content>
</tag>
</taglib>
demo.jsp:
<ittest:mytag1/>
结果如图所示:
有标签体
有标签体即有内容的标签。那么用例代码如下所示:
标签处理类:
public class MyTag2 extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
Writer out = this.getJspContext().getOut();
out.write("***************<br/>");
// 执行标签体内容,把结果写到指定的流中
this.getJspBody().invoke(out);
out.write("<br/>***************");
}
}
tld文件:
<tag>
<name>mytag2</name>
<tag-class>it.test.tag.MyTag2</tag-class>
<body-content>scriptless</body-content>
</tag>
demo.jsp:
<ittest:mytag2>
Hello ${ }
</ittest:mytag2>
结果如图所示:
中断执行
在Java中有一个异常专门用于跳过页面显示内容,即SkipPageException。Tomcat当得到这个异常时,将会跳过页面其他的内容。用例代码如下所示:
标签处理类:
public class MyTag3 extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
this.getJspContext().getOut().print("阻断显示");
throw new SkipPageException();
}
}
tld文件:
<tag>
<name>mytag3</name>
<tag-class>it.test.tag.MyTag3</tag-class>
<body-content>empty</body-content>
</tag>
demo.jsp:
<ittest:mytag3/>
<ittest:mytag1/><br/>
<ittest:mytag2>
Hello ${ }
</ittest:mytag2>
结果如图所示:
附有属性标签
类似于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文件:
<tag>
<name>mytag4</name>
<tag-class>it.test.tag.MyTag4</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>flag</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
其中,required指定属性是否必须。rtexprvalue,指定属性是否能接受请求时表达式的值,默认为false,表示不能接受请求时表达式的值。
demo.jsp:
<ittest:mytag4 flag="${empty }">
没有name参数,执行阻断显示<br/>
<ittest:mytag3/>
</ittest:mytag4>
结果如图所示:
后话
'''
EL表达式还有一些运算符的东西并没有拿出来讲,这些是可以自行测试的也过于简单
这一章最主要还是学会使用EL表达式和JSTL以及理解JSP中所谓的标签本质即可
'''