5.3 一般行为

下面介绍core库中用来操作有界变量的3个一般行为:outsetremove

5.3.1 out标签

out标签表示将表达式的结果输出到当前的JspWriterout的语法有两种形式,即有body content和没有body content这两种形式:

1
2
3
4
5
6
<!--  没有没有`body content`:  -->
<c:out value="value" [escapeXml="{true|false}"] [default="defaultValue"]/>
<!-- 有`body content`形式: -->
<c:out value="value" [escapeXml="{true|false}"]>
default value
</c:out>

这两种形式只是默认值存放的位置不同而已,一个是放在<c:out>标签的default属性中,而一个是放在c:out标签体中.

标签语法

在标签的语法中,[]表示该属性是可选的。如果值带下划线,则表示为默认值。

out属性说明

属性 类型 描述 可选性 默认值
value*+ 对象 要计算的表达式 必须要有该属性
escapeXml+ 布尔 表示结果中的字符<,>,&,'"将被转化成相应的实体码,如<会被转义成&lt;等等。 可选 true
default+ 对象 默认值 可选 主体中的内容

属性后面的星号与加号的含义

  • 属性名称后面的星号(*)表示该属性是必需的。没有星号表示该属性是可选的.
  • 加号(+)表示该属性的rtexprvalue值为true,没有加号表示这意味着该属性的rtexprvalue值为false
    • rtexprvalue值为true表示该属性可以赋静态字符串或者动态值Java表达式、EL表达式或者通过<jsp:attribute>设置的值)。
    • rtexprvalue值为false时,表示该属性只能赋静态字符串的值
    • 总结:有加号表示该属性可以设置为静态字符串或者动态生成的字符串

例如,下列的out标签将输出有界变量X的值:
默认情况下,out会将特殊字符<>'"&分别编写成它们相应的字符实体码 &lt;&gt;&#039;&#034;&amp;
JSP 2.0版本前,out标签是用于输出有界对象值的最容易的方法。在JSP 2.0及其更高的版本中,除非需要对某个值进行XML转义,否则可以放心地使用EL表达式:

1
${x}

警告

如果包含一个或多个特殊字符的字符串没有进行XML转义,它的值就无法在浏览器中正常显示。此外,没有通过转义的特殊字符,会使网站易于遭受交叉网站的脚本攻击。例如,别人可以对它post一个能够自动执行的JavaScript函数/表达式。
out中的default属性可以赋一个默认值,当赋予其value属性的EL表达式返回null时,就会显示默认值default属性可以赋动态值,如果这个动态值返回nullout就会显示一个空的字符串。

例如,在下面的out标签中,如果在HttpSession中没有找到myVar变量,就会显示应用程序范围的变量myVar值。如果应用程序范围的myVar变量也没有找到,则输出一个空的字符串:

1
2
<c:out value="${sessionScope.myVar}"
default="${applicationScope.myVar"/>

第5章 JSTL

JSP标准标签库(JavaServer Pages Standard TagLibraryJSTL)是一个定制标签库的集合,用来解决像遍历Map或集合、条件测试、XML处理,甚至数据库访问和数据操作等常见的问题。
本章要介绍的是JSTL中最重要的标签,尤其是访问有界对象遍历集合,以及格式化数字和日期的那些标签。如果有兴趣进一步了解,可以在JSTL规范文档中找到所有JSTL标签的完整版说明。

4.6.1 实现无脚本的JSP页面

为了关闭JSP页面中的脚本元素,要使用jsp-property-group元素以及url-patternscripting-invalid两个子元素。url-pattern元素定义了要禁用脚本的JSP页面的URL模式。

关闭所有JSP页面中的脚本

将一个应用程序中所有JSP页面的脚本都关闭:

1
2
3
4
5
6
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<scripting-invalid>true</scripting-invalid>
</jsp-property-group>
</jsp-config>

注意

在部署描述符中只能有一个jsp-config元素,如果需要同时禁用脚本和EL,则需要:
jsp-config元素下编写两个jsp-property-group子元素,一个用来禁用脚本,一个用来禁用EL

4.6.2 禁用EL计算

在某些情况下,比如,当需要在JSP 2.0及其更高版本的容器中部署JSP 1.2应用程序时,可能就需要禁用JSP页面中的EL计算了。目前有两种方式可以禁用JSP中的EL计算。

通过page指令来关闭EL表达式计算

page指令的isELIgnored属性设为true,即可关闭该JSP页面中EL表达式的计算.如下代码所示:

1
<%@ page isELIgnored="true" %>

isELIgnored属性的默认值为false。如果想在一个或者几个JSP页面中关闭EL表达式计算,建议使用isELIgnored属性。

通过部署描述符来关闭EL表达式计算

可以在部署描述符中使用jsp-property-group元素。jsp-property-group元素是jsp- config元素的子元素。利用jsp-property-group可以将某些设置应用到程序中的一组JSP页面中。

为了利用 jsp-property-group 元素来禁用EL计算,还必须有url-pattern el-ignored两个子元素。url-pattern元素用于定义要禁用ELJSP页面URL样式。el-ignored元素必须设为True

在指定JSP页面中关闭EL表达式计算

下面举一个例子,示范如何在名为noEI.jspJSP页面中禁用EL计算:

1
2
3
4
5
6
<jsp-config>
<jsp-property-group>
<url-pattern>/noEl.jsp</url-pattern>
<el-ignored>true</el-ignored>
</jsp-property-group>
</jsp-config>

关闭所有JSP页面中的EL表达式计算

也可以像下面这样,通过给 url-pattern 元素赋值*.jsp,来禁用一个应用程序中所有JSP页面的EL计算:

1
2
3
4
5
6
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<el-ignored>true</el-ignored>
</jsp-property-group>
</jsp-config>

满足以上两个条件其中一种即可关闭EL表达式计算

例如,虽然一个JSP页面中page指令的isELIgnored属性设为false,但该JSP页面的URL与部署描述符中禁用了EL计算的模式相匹配,那么该页面的EL计算也将被禁用。

4.6 如何在JSP2.0及其更高版本中配置EL

有了ELJavaBeans定制标签,就可以编写无脚本的JSP页面了。JSP 2.0及其更高的版本中还提供了一个开关,用来禁用所有JSP页面中的脚本。从而,强制要求工程师编写无脚本的JSP页面

4.5 应用EL的Demo

Demo1

示例app04a包含了一个JSP页面,该页面通过EL访问一个Address这个JavaBean并输出该bean的属性。该bean对象是另一个JavaBean(Employee)的一个属性,并用EL访问一个Map对象的内容,以及HTTP头部信息和会话标识。EmployeeServlet 类创建了所需的对象,并将这些对象放入到ServletRequest中,然后通过RequestDispatcher跳转到employee.jsp页面。

Employee类

这个类用来保存员工信息,员工有编号,姓名,地址等信息.其中地址信息用用单独的地址类来表示,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package app04a.model;
public class Employee
{
// 员工编号
private int id;
// 员工姓名
private String name;
// 地址
private Address address;
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public Address getAddress()
{
return address;
}
public void setAddress(Address address)
{
this.address = address;
}
}

Address类

这个类用来保存地址信息,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package app04a.model;
public class Address
{
// 国家
private String country;
// 省份
private String province;
// 城市
private String city;
// 地区
private String district;
// 街道名称
private String streetName;
// 街道编号
private String streetNumber;
// 邮政编码
private String zipCode;
/**
* 获取国家.
*
* @return
*/
public String getCountry()
{
return country;
}
/**
* 设置国家.
*
* @param country
*/
public void setCountry(String country)
{
this.country = country;
}
/**
* 获取省份.
*
* @return
*/
public String getProvince()
{
return province;
}
/**
* 设置省份.
*
* @param province
*/
public void setProvince(String province)
{
this.province = province;
}
/**
* 获取城市.
*
* @return
*/
public String getCity()
{
return city;
}
/**
* 设置城市.
*
* @param city
*/
public void setCity(String city)
{
this.city = city;
}
/**
* 获取地区(县,等).
*
* @return
*/
public String getDistrict()
{
return district;
}
/**
* 设置地区.
*
* @param district
*/
public void setDistrict(String district)
{
this.district = district;
}
/**
* 获取街道名称.
*
* @return
*/
public String getStreetName()
{
return streetName;
}
/**
* 设置街道名称.
*
* @return
*/
public void setStreetName(String streetName)
{
this.streetName = streetName;
}
/**
* 获取街道编号.
*
* @return
*/
public String getStreetNumber()
{
return streetNumber;
}
/**
* 设置街道编号
*
* @param streetNumber
*/
public void setStreetNumber(String streetNumber)
{
this.streetNumber = streetNumber;
}
/**
* 获取邮政编码.
*
* @return
*/
public String getZipCode()
{
return zipCode;
}
/**
* 设置邮政编码.
*
* @param zipCode
*/
public void setZipCode(String zipCode)
{
this.zipCode = zipCode;
}
}

EmployeeServlet类

在这个类的doGet方法中,创建员工,创建员工的地址对象,然后把员工对象设为当前请求对象的属性。这样就完成了数据初始化话的操作,然后把请求转发到/employee.jsp这JSP页面,由该JSP页面进行显示.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package app04a.servlet;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import app04a.model.Address;
import app04a.model.Employee;
@WebServlet(
urlPatterns =
{"/employee"}
)
public class EmployeeServlet extends HttpServlet
{
private static final long serialVersionUID = 3398443364110578529L;
@Override
public void doGet(HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException
{
// 创建地址对象
Address address = new Address();
address.setCountry("中国");
address.setProvince("江苏");
address.setCity("南京");
address.setDistrict("玄武区");
address.setStreetName("通往幸福的康庄大道");
address.setStreetNumber("LoveYou233");
address.setZipCode("16888");
// 创建员工
Employee employee = new Employee();
employee.setId(1099);
employee.setName("小明");
employee.setAddress(address);
// 把employee对象设置为请求对象的属性
request.setAttribute("employee", employee);
// 把请求转发给/employee.jsp,
RequestDispatcher rd = request.getRequestDispatcher("/employee.jsp");
rd.forward(request, response);
}
}

employee.jsp页面

这个jsp页面通过EL表达式从请求对象中取出员工的信息,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>Employee</title>
<!-- 引入table CSS样式用到JS代码 -->
<script type="text/javascript" src="table.js"></script>
<!-- 引入table的css样式 -->
<link type="text/css" rel="styleSheet" href="table.css" />
</head>
<body>
<table class="altrowstable" id="alternatecolor">
<tr>
<td align='right'>姓名:</td>
<td>${requestScope.employee.name}</td>
</tr>
<tr>
<td align='right'>地址:</td>
<td>${employee.address.country},${employee.address.province},${employee.address.city},${employee.address.district},${employee.address.streetName},${employee.address.streetNumber},${employee.address.zipCode}</td>
</tr>
</table>
</body>
</html>

在这个JSP页面中,通过表格来显示数据,为了美观这里加上了CSS和JS来实现明亮相间的表格效果,不过本篇的重点在于EL表达式,并不在JS,和CSS.

CSS文件

table.css:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
table.altrowstable {
font-family: verdana, arial, sans-serif;
font-size: 16px;
color: #333333;
border-width: 1px;
border-color: #a9c6c9;
border-collapse: collapse;
}
table.altrowstable th {
border-width: 1px;
padding: 8px;
border-style: solid;
border-color: #a9c6c9;
}
table.altrowstable td {
border-width: 1px;
padding: 8px;
border-style: solid;
border-color: #a9c6c9;
}
.oddrowcolor {
background-color: #1f3de199;
}
.evenrowcolor {
background-color: #2db6b6;
}

JS文件

table.js:这个JS代码回把表格设置成明暗相间的形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function altRows(id) {
if (document.getElementsByTagName) {
var table = document.getElementById(id);
var rows = table.getElementsByTagName("tr");
for (i = 0; i < rows.length; i++) {
if (i % 2 == 0) {
rows[i].className = "evenrowcolor";
} else {
rows[i].className = "oddrowcolor";
}
}
}
}
window.onload = function() {
altRows('alternatecolor');
}

可以通过URL:http://localhost:8080/app04a/employee访问到EmployeeServlet这个Servlet,浏览器显示效果如下:
这里有一张图片

Demo2

在这个demo中,先是创建了一个Map,用来存放各个省份的省会。然后把这个Map设置为请求对象的属性,接着把请求转发到``这个JSP页面进行显示,如下所示。

ProvincialCapitalServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package app04a.servlet;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
// 映射URL
@WebServlet(
urlPatterns =
{"/provincialcapital"}
)
public class ProvincialCapitalServlet extends HttpServlet
{
private static final long serialVersionUID = -7130200218441279178L;
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException,IOException
{
// 设置省会
Map<String,String> provincialCapital = new HashMap<String,String>();
provincialCapital.put("黑龙江", "哈尔滨");
provincialCapital.put("吉林", "长春");
provincialCapital.put("辽宁", "沈阳");
provincialCapital.put("河北", "石家庄");
provincialCapital.put("河南", "郑州");
provincialCapital.put("山西", "太原");
provincialCapital.put("山东", "济南");
provincialCapital.put("江苏", "南京");
provincialCapital.put("江西", "南昌");
provincialCapital.put("湖南", "长沙");
provincialCapital.put("湖北", "武汉");
provincialCapital.put("云南", "昆明");
provincialCapital.put("贵州", "贵阳");
provincialCapital.put("四川", "成都");
provincialCapital.put("福建", "福州");
provincialCapital.put("广东", "广州");
provincialCapital.put("陕西", "西安");
provincialCapital.put("青海", "西宁");
provincialCapital.put("浙江", "杭州");
provincialCapital.put("甘肃", "兰州");
provincialCapital.put("安徽", "合肥");
provincialCapital.put("台湾", "台北");
provincialCapital.put("海南", "海口");
// 把省会信息设置为请求对象的属性.
request.setAttribute("PC", provincialCapital);
// 请求转发给jsp页面进行显示
request.getRequestDispatcher("/ProvincialCapital.jsp").forward(request,
response);
}
}

ProvincialCapital.jsp

/app04a/WebContent/ProvincialCapital.jsp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>显示部分省会</title>
<script type="text/javascript" src="table.js"></script>
<!-- 引入table的css样式 -->
<link type="text/css" rel="styleSheet" href="table.css" />
</head>
<body>
<table class="altrowstable" id="alternatecolor">
<tr>
<td align='right'>湖南的省会是:</td>
<td>${requestScope.PC["湖南"]}</td>
</tr>
<tr>
<td align='right'>江苏的省会是:</td>
<td>${requestScope.PC["江苏"]}</td>
</tr>
</table>
</body>
</html>

EL表达式分析

1
${requestScope.PC["江苏"]}

这个EL表达的意思是:从请求对象中的PC属性(Map)中取出key为江苏对应的值,
通过http://localhost:8080/app04a/provincialcapital这个URL就可访问这个Servlet,浏览器显示效果如下图所示:
这里有一张图片

4.4 使用其他EL运算符

除了.[]运算符外,EL还提供了其他运算符:

  • 算术运算符、
  • 关系运算符、
  • 逻辑运算符、
  • 条件运算符
  • 以及empty运算符。

使用这些运算符时,可以进行不同的运算。但是,由于EL的目的是为了写出无脚本的JSP页面,因此,除了关系运算符外,这些EL运算符的用处都很有限。

4.4.1 算术运算符

算术运算符有5种:

  • 加法(+
  • 减法(
  • 乘法(*
  • 除法(/div
  • 求余/取模(%mod

除法和取余运算符有两种形式,与XPathECMAScript是一致的。

优先级

注意,EL表达式的计算按优先级从高到低、从左到右进行。下列运算符是按优先级递减顺序排列的:

1
2
*、/、div、%、mod
+−

这表示*/div%以及mod运算符的优先级别相同,+的优先级别相同,但第二组运算符的优先级小于第一组运算符。因此,表达式:

1
${1+2*3}

的运算结果是7,而不是9

4.4.2 逻辑运算符

下面是逻辑运算符列表:

  • 与运算(&&或者and
  • 或运算(||或者or
  • 非运算(!或者not

4.4.3 关系运算符

下面是关系运算符列表:

  • 等于(==或者eq
  • 不等于(!=或者ne
  • 大于(>或者gt
  • 大于或等于(>=或者ge
  • 小于(<或者lt
  • 小于或等于(<=或者le

例如,表达式${3==4}返回false${"b"<"d"}则返回true

条件运算符

1
${statement? A:B}

如果statement的计算结果为true,那么该表达式的输出结果就是A,否则为B

例如,利用下列EL表达式可以测试HttpSession中是否包含名为loggedIn的属性。如果找到这个属性,就显示You have logged in,否则显示You have not logged in

1
${(sessionScope.loggedIn==null)? "You have not logged in":"You have logged in"}

4.4.4 empty运算符

empty运算符用来检查某一个值是否为null或者empty。下面是一个empty运算符的使用范例:

1
${empty X}

**如果Xnull,或者说X是一个长度为0的字符串,那么该表达式将返回true如果X是一个空的Map、空的数组或者空的集合,它也将返回true**,否则,将返回false

4.3.5 header隐式对象

隐式对象header表示一个包含所有请求标题的Map集合。为了获取header值,要利用header名称作为key。例如,为了获取accept-language这个header值,可以使用以下表达式:

1
${header["accept-language"]}

如果header名称是一个有效的Java变量名,如connection,那么也可以使用. 运算符:

1
${header.connection}

headerValues隐式对象

隐式对象headerValues表示一个包含所有请求标题Map集合,使用header的名称作为key的。但是,与header隐式对象不同的是,根据请求标题从headerValues隐式对象(Map集合)中取得的值是一个字符串数组,为了取得该数组中的元素,还需要指定数组的下标。
例如,为了获取请求标题为accept-language的第一个值,要使用以下表达式:

1
${headerValues["accept-language"][0]}

4.3.6 cookie隐式对象

隐式对象cookie可以用来获取一个cookie。这个对象表示当前HttpServletRequest之中的所有cookie。例如,为了获取名为jsessionidcookie值,要使用以下表达式:

1
${cookie.jsessionid.value}

为了获取名为jsessionidcookie的路径值,要使用以下表达式:

1
${cookie.jsessionid.path}

4.3.2 initParam隐式对象

隐式对象initParam用于获取上下文参数的值。例如,为了获取名为password的上下文参数值,可以使用以下表达式:

1
${initParam.password}

或者

1
${initParam["password"]}

4.3.3 param隐式对象

隐式对象param用于获取请求参数的值。这个对象表示一个包含所有请求参数的Map集合。例如,要获取userName参数,可以使用如下表达式:

1
${param["userName"]}

4.3.4 paramValues隐式对象

利用隐式对象paramValues可以获取一个请求参数多个值。这个对象表示一个包含所有请求参数的Map集合,参数名称作为key。每个key的对应值是一个字符串数组,数组中包含了指定参数名称的所有值。即使该参数只有一个值,它也仍然返回一个只带有一个元素的数组。

例如,为了获得selectedOptions参数的第一个值和第二个值,可以使用以下表达式:

1
2
${paramValues.selectedOptions[0]}
${paramValues.selectedOptions[1]}

4.3.7 applicationScope、sessionScope、requestScope和pageScope隐式对象

隐式对象applicationScope用于获取应用程序范围级变量的值。假如有一个应用程序范围级变量myVar,就可以利用以下表达式来获取这个属性:

1
${applicationScope.myVar}

隐式对象sessionScoperequestScopepageScopeapplicationScope相似。只不过其取值范围分别为sessionrequestpage而已。

有界对象

注意,在servlet/JSP编程中,有界对象是指在以下对象中作为属性的对象PageContextServletRequestHttpSession或者ServletContext
有界对象也可以通过没有范围的EL表达式获取。在这种情况下,JSP 容器将返回PageContextServletRequestHttpSession或者ServletContext中第一个同名的对象。执行顺序是从最小范围(PageContext)到最大范围(ServletContext)。例如,以下表达式将返回today引用的任意范围的对象:

1
${today}

4.3.1 pageContext

pageContext对象包含了所有其他的JSP隐式对象,如下表所示:

对象 类型
request javax.servlet.http.HttpServletRequest
response javax.servlet.http.HttpServletResponse
Out javax.servlet.jsp.JspWriter
session javax.servlet.http.HttpSession
application javax.servlet.ServletContext
config javax.servlet.ServletConfig
PageContext javax.servlet.jsp.PageContext
page javax.servlet.jsp.HttpJspPage
exception java.lang.Throwable

获取EL的pageContext中的其他对象

例如,可以利用以下任意一个表达式来获取当前的ServletRequest

1
2
${pageContext.request}
${pageContext["request"]}

获取EL的pageContext对象中的request对象中的请求方法

因为EL中可以使用[],也可以使用.运算符,所以可以利用以下任意一个表达式来获取请求方法:

1
2
3
4
${pageContext["request"]["method"]}
${pageContext["request"].method}
${pageContext.request["method"]}
${pageContext.request.method}

对请求参数的访问比对其他隐式对象更加频繁;因此,EL中提供了paramparamValues两个隐式对象。