表达式语言 Expression Language
JSP 2.0最重要的特性之一就是表达式语言 (EL),JSP用户可以用它来访问应用程序数据。由于 受到ECMAScript和XPath表达式语言的启发,EL也设计 成可以轻松地编写免脚本的JSP页面。也就是说,页面 不使用任何JSP声明、表达式或者scriptlets。 JSP 2.0最初是将EL应用在JSP标准标签库(JSTL) 1.0规范中。JSP 1.2程序员将标准库导入到他们的应用 程序中,就可以使用EL。JSP 2.0及其更高版本的用户 即使没有JSTL,也能使用EL,但在许多应用程序中, 还是需要JSTL的,因为它里面还包含了与EL无关的其 他标签。 JSP 2.1和JSP 2.2中的EL要将JSP 2.0中的EL与 JSF(JavaServer Faces)中定义的EL统一起来。JSF是 在Java中快速构建Web应用程序的框架,并且是构建在 JSP 1.2之上。由于JSP 1.2中缺乏整合式的表达式语 言,并且JSP 2.0 EL也无法满足JSF的所有需求,因此为 JSF 1.0开发出了一款EL的变体。后来这两种语言变体 合二为一。
一.表达式语言的语法
EL表达式以 ${ 开头,并以 } 结束。EL表达式的结 构如下:
$ { expression}
它们的取值将是从左到右进行,计算结果的类型为 String,并且连接在一起。假如a+b等于8,c+d等于 10,那么这两个表达式的计算结果将是810:
${a+b}${c+d}
表达式${a+b}and${c+d}的取值结果则是8and10
如果在定制标签的属性值中使用EL表达式,那么 该表达式的取值结果字符串将会强制变成该属性需要的 类型:
<my:tag someAttribute="${expression}" />
像${这样的字符顺序就表示是一个EL表达式的开 头。如果需要的只是文本${,则需要在它前面加一个转 义符,如\${。
二.关键字
以下是关键字,它们不能用作标识符:
and eq gt true instanceof or ne le false empty not lt ge null div mod
三. [] 和 . 运算符
EL表达式可以返回任意类型的值。如果EL表达式 的结果是一个带有属性的对象,则可以利用[ ]或者.运 算符来访问该属性。“[ ]”和“.”运算符类似; “[ ]”是比较 规范的形式, “.”运算符则比较快捷。 为了访问对象的属性,可以使用以下任意一种形 式:
${object["propertyName"]} ${object.propertyName}
但是,如果propertyName不是有效的Java变量名, 只能使用[ ]运算符。例如,下面这两个EL表达式就可以用来访问隐式对象标题中的HTTP标题host:
<body> hello; ${header.host}; <br /> ${header["host"]}; </body>
但是,要想访问accept-language标题,则只能使 用“[ ]”运算符,因为accept-language不是一个合法的 Java变量名。如果用“.”运算符访问它,将会导致异常。
<body> hello; <br /> <%-- header.accpt-language 不可用 %--> ${header["accept-language"]}; </body>
如果对象的属性碰巧返回带有属性的另一个对象, 则既可以用“[ ]” ,也可以用“.”运算符来访问第二个对象 的属性。例如,隐式对象pageContext是表示当前JSP的 PageContext对象。它有request属性,表示 HttpServletRequest。HttpServletRequest带有servletPath 属性。下列几个表达式的结果相同,均能得出 pageContext中HttpServletRequest的servletPath属性值
<body> hello; ${pageContext["request"]["servletPath"]} ${pageContext.request["servletPath"]} ${pageContext.request.servletPath} ${pageContext["request"].servletPath} </body>
要访问HttpSession,可以使用以下语法:
<body> hello; ${pageContext.session } <br /> ${pageContext.session.id } </body>
四,取值规则
EL表达式的取值是从左到右进行的。对于expra[expr-b]形式的表达式,其EL表达式的取值方法如下:
如果value-a不是一个Map、List或者 array, 那么,value-a必须是一个JavaBean。在这种情况下,必 须强制value-b为String。如果value-b是value-a的一个可 读属性,则要调用该属性的getter方法,从中返回值。 如果getter方法抛出异常,该表达式就是无效的,否 则,该表达式有效。
五. 访问JavaBean
利用“.”或“[]”运算符,都可以访问 bean 的属性, 其结构如下:
${beanName["propertyName"]} ${beanName.propertyName}
如果该属性是一个带属性的对象,那么同样也可以 利用“.”或“[]”运算符来访问第二个对象的该属性。假如 该属性是一个Map、List或者array,则可以利用和访问 Map值或List成员或array元素的同样规则。
六. EL隐式对象
对象 | 描述 |
pageContext | 这是当前JSP的javax.servlet.jsp.PageContext |
initParam | 这是一个包含所有环境初始化参数,并用参数名作为key的Map |
param | 这是一个包含所有请求参数,并用参数名作为key的Map。每个key的值就是指定名称的第一个参数值。因此,如果两个请求参数同名,则只有第一个能够利用param获取值。要想访问同名参数的所有参数值,就得用params代替 |
paramValues | 这是一个包含所有请求参数,并用参数名作为key的Map。每个key的值就是一个字符串数组,其中包含了指定参数名称的所有参数值。就算该参数只有一个值,它也仍然会返回一个带有一个元素的数组 |
header | 这是一个包含请求标题,并用标题名作为key的Map。每个key的值就是指定标题名称的第一个标题。换句话说,如果一个标题的值不止一个,则只返回第一个值。要想获得多个值的标题,得用headerValues对象代替 |
headerValues | 这是一个包含请求标题,并用标题名作为key的Map。每个key的值就是一个字符串数组,其中包含了指定标题名称的所有参数值。就算该标题只有一个值,它也仍然会返回一个带有一个元素的数组 |
cookie | 这是一个包含了当前请求对象中所有Cookie对象的Map。Cookie名称就是key名称,并且每个key都映射到一个Cookie对象 |
applicationScope | 这是一个包含了ServletContext对象中所有属性的Map,并用属性名称作为key |
sessionScope | 这是一个包含了HttpSession对象中所有属性的Map,并用属性名称作为key |
requestScope | 这是一个Map,其中包含了当前HttpServletRequest对象中的所有属性,并用属性名称作为key |
pageScope | 这是一个Map,其中包含了全页面范围内的所有属性。属性名称就是Map的key |
1.pageContext
pageContext 对象表示当前页面的javax.servlet.jsp.PageContext. 它包含了所有的其他的JSP隐式对象
对象 | EL中的类型 |
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 |
例如,可以利用以下任意一个表达式来获取当前的 ServletRequest:
${pageContext.request} ${pageContext["request"]
并且,还可以利用以下任意一个表达式来获取请求 方法:
${pageContext["request"]["method"]} ${pageContext["request"].method} ${pageContext.request["method"]} ${pageContext.request.method}
2.initParam
隐式对象initParam用于获取上下文参数的值,例如,为了获取名为password的上下文的值,可以使用以下表达式
${initParam.password}
或
${initParam["password"]
3.Param
隐式对象param用于获取请求参数值。这个对象表 示一个包含所有请求参数的Map。例如,要获取 userName参数,可以使用以下任意一种表达式:
${param.userName} //获取url ? 号后的param的值 ${param["userName"]}
4.ParamValues
利用隐式对象paramValues可以获取一个请求参数 的多个值。这个对象表示一个包含所有请求参数,并以 参数名称作为key的Map。每个key的值是一个字符串数 组,其中包含了指定参数名称的所有值。即使该参数只 有一个值,它也仍然返回一个带有一个元素的数组。例 如,为了获得selectedOptions参数的第一个值和第二个 值,可以使用以下表达式:
${paramValues.selectedOptions[0]} ${paramValues.selectedOptions[1]}
例: url为 http://localhost:8080/jspTest1/NewFile.jsp?text=sss&text=sss2
jsp为 ${paramValues.text[1]}
显示
5. header
隐式对象header表示一个包含所有请求标题的 Map。为了获取header值,要利用header名称作为key。 例如,为了获取accept-language这个header值,可以使 用以下表达式:
${header["accept-language"]
如果header名称是一个有效的Java变量名,如 connection,那么也可以使用“. ”运算符:
${header.connection}
隐式对象headerValues表示一个包含所有请求 head,并以header名称作为key的Map。但是,与head不 同的是,隐式对象headerValues返回的Map返回的是一 个字符串数组。例如,为了获取标题accept-language的 第一个值,要使用以下表达式:
${headerValues["accept-language"][0]}
6. cookie
隐式对象cookie可以用来获取一个cookie。这个对 象表示当前HttpServletRequest中所有cookie的值。例 如,为了获取名为jsessionid的cookie值,要使用以下表 达式:
${cookie.jsessionid.value}
为了获取jsessionid cookie的路径值,要使用以下表 达式:
${cookie.jsessionid.path}
7.applicationScope, sessionScope, requestScope, pageScope
隐式对象applicationScope用于获取应用程序范围级 变量的值。假如有一个应用程序范围级变量myVar,就 可以利用以下表达式来获取这个属性:
${applicationScope.myVar}
例:
jsp页面
<body> <jsp:useBean id="today" class="java.util.Date" /> ${pageScope.today} </body>
显示
注意,在servlet/JSP编程中,有界对象是指在以下 对象中作为属性的对象:PageContext、 ServletRequest、HttpSession或者ServletContext。隐式对 象sessionScope、requestScope和pageScope与 applicationScope相似。但是,其范围分别为session、 request和page。 有界对象也可以通过没有范围的EL表达式获取。 在这种情况下,JSP 容器将返回PageContext、 ServletRequest、HttpSession或者ServletContext中第一个 同名的对象。执行顺序是从最小范围(PageContext) 到最大范围(ServletContext)。例如,以下表达式将返 回today引用的任意范围的对象:
${today}
七. 使用其他EL运算符
除了“.”和“[]”运算符外,EL还提供了其他运算符: 算术运算符、关系运算符、逻辑运算符、条件运算符以 及empty运算符。使用这些运算符时,可以进行不同的 运算。但是,由于EL的目的是方便免脚本JSP页面的编 程,因此,除了关系运算符外,这些EL运算符的用处 都很有限。
1. 算术运算符
i)算术运算符有5种:
加法(+) 减法(−) 乘法(*) 除法(/和div) 取余/取模(%和mod)
除法和取余运算符有两种形式,与XPath和 ECMAScript是一致的。
ii)注意,EL表达式的计算按优先级从高到低、从左 到右进行。下列运算符是按优先级递减顺序排列的:
* 、/、div、%、mod、 //这一级运算符的优先级相同
+ 、- //这一级的优先级小于第一级
2.逻辑运算符
逻辑运算符: 和(&&和and) 或(|| 和or) 非(!和not)
3.关系运算符
关系运算符列表
- 等于(=和eq)
- 不等于(!=和ne)
- 大于(>和gt)
- 大于等于(>=和ge)
- 小于(<和lt)
- 小于等于(<=和le)
例如,表达式${3==4}返回False,${“b”<“d”}则返 回True。 EL关系运算符的语法如下:
${statement? A:B} //三目运算符
4.emty运算符
empty运算符用来检查某一个值是否为null或者 empty。下面是一个empty运算符的使用范例
例
jsp页面
<body> <% java.util.Date today1 = null; %> ${empty today1} <jsp:useBean id="today" class="java.util.Date" /> </body>
浏览器显示
八. 应用EL
示例app04a包含了一个JSP页面,该页面通过EL访 问一个JavaBean(Address,详见清单4.1)并输出该 bean的属性。该bean对象是另一个 JavaBean(Employee,详见清单4.2)的一个属性,并 用EL访问一个Map对象的内容,以及HTTP头部信息和 会话标识。EmployeeServlet 类(详见清单4.3)创建了 所需的对象,并将这些对象放入到ServletRequest中, 然后通过RequestDispatcher跳转到employee.jsp页面。
Address类
package model; public class Address { private String streetName; private String streetNumber; private String city; private String state; private String zipCode; private String country; public String getStreetName() { return this.streetName ;} public void setStreetName(String streetName) { this.streetName= streetName;} public String getStreetNumber() { return this.streetNumber; } public void setStreetNumber(String streetNumber) { this.streetNumber = streetNumber;} public String getCity() { return this.city; } public void setCity(String city) { this.city = city ; } public String getState() { return this.state; } public void setState(String state) { this.state = state; } public String getZipCode() { return this.zipCode;} public void setZipCode(String zipCode) { this.zipCode = zipCode; } public String getCountry() { return this.country; } public void setCountry(String country) { this.country = country; } }
Employee类
package model; public class Employee { private int id; private String name; private Address address; public int getId() { return this.id; } public void setId(int id) { this.id = id ;} public String getName() { return this.name; } public void setName(String name) { this.name = name; } public Address getAddress() { return this.address; } public void setAddress(Address address) { this.address = address; } }
EmployeeServlet类
package 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 model.*; @WebServlet(urlPatterns = { "/employee"}) public class EmployeeServlet extends HttpServlet{ private static final long serialVersionUID = 10L; @Override public void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException { Address address = new Address(); address.setStreetName("Rue "); address.setStreetNumber("509ab"); address.setCity("Brossard"); address.setState("Quebec"); address.setZipCode("A1A B2b"); address.setCountry("canada"); Employee employee = new Employee(); employee.setId(1099); employee.setName("charles"); employee.setAddress(address); request.setAttribute("employee",employee); Map<String, String> capitals = new HashMap<String,String>(); capitals.put("China" ,"Beijing"); capitals.put("Austria","Vienna"); capitals.put("Australia","Canberra"); capitals.put("Canada","Ottua"); request.setAttribute("capitals", capitals); /*定义一个对象,该对象接收来自客户端的请求,并将它们发送到服务器上的任何资源(例如servlet,HTML文件或JSP文件)。servlet容器创建RequestDispatcher对象,该对象用作位于特定路径或由特定名称给定的服务器资源的包装器。 此接口旨在包装servlet,但servlet容器可以创建RequestDispatcher 对象以包装任何类型的资源。*/ RequestDispatcher rd = request.getRequestDispatcher("/employee.jsp"); /* 将来自servlet的请求转发到服务器上的另一个资源(servlet,JSP文件或HTML文件)。*/ rd.forward(request,response); } }
employee.jsp
// employee.jsp <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Employee</title> </head> <body> accept-language: ${header['accept-language']} <br /> session id: ${pageContext.session.id } <br /> employee: ${requestScope.employee.name}, ${employee.address.city} <br /> capital: ${capitals["Canada"]} </body> </html>
浏览器显示结果
请注意,在app04a中使用一个servlet和JSP页面来 显示JavaBean属性和其他值符合现代Web应用程序的推 荐的设计,在第16章中会进一步讨论。 要特别注意在JSP页面的EL表达式中,对于request 域的employee对象的访问,可以是显式的,也可以是隐 式的.
九.如何在就jsp2.0 及其更高版本中配置EL
有了EL、JavaBeans和定制标签,就可以编写免脚 本的JSP页面了。JSP 2.0及其更高的版本中还提供了一 个开关,可以使所有的JSP页面都禁用脚本。现在,软件架构师们可以强制编写免脚本的JSP页面了。 另一方面,在有些情况下,可能还会需要在应用程 序中取消EL。例如,正在使用与JSP 2.0兼容的容器, 却尚未准备升级到JSP 2.0,那么就需要这么做。在这 种情况下,可以关闭EL表达式的计算。
1. 实现免脚本的JSP页面
为了关闭JSP页面中的脚本元素,要使用jspproperty-group元素以及url-pattern和scripting- invalid两 个子元素。url-pattern元素定义禁用脚本要应用的URL 样式。下面示范如何将一个应用程序中所有JSP页面的 脚本都关闭:
web.xml ---- 主要web.xml修改后要重启服务器
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<el-ignored>true</el-ignored>
<el-ignored>true</el-ignored> //此项不知到要不要加
<scripting-invalid>true</scripting-invalid>
</jsp-property-group>
</jsp-config>
注意: 在部署描述符中只能有一个jsp-config元素。如果已经为禁用EL而 定义了一个jsp-property-group,就必须在同一个jsp-config元素下,为禁用脚本而编写jsp-property- group。
十. 禁用EL计算
在某些情况下,比如,当需要在JSP 2.0及其更高版 本的容器中部署JSP 1.2应用程序时,可能就需要禁用 JSP页面中的EL计算了。此时,一旦出现EL架构,就不 会作为一个EL表达式进行计算。目前有两种方式可以 禁用JSP中的EL计算。 第一种,可以将page指令的isELIgnored属性设为 True,如下:
<%@ page isELIgnored="true" %>
第二种,可以在部署描述符中使用jsp-propertygroup元素。jsp-property-group元素是jsp- config元素的 子元素。利用jsp-property-group可以将某些设置应用到 应用程序中的一组JSP页面中。 为了利用 jsp-property-group 元素禁用EL计算,还 必须有url-pattern 和 el-ignored两个子元素。url-pattern 元素用于定义EL禁用要应用的URL样式。el-ignored元 素必须设为True。
下面举一个例子,示范如何在名为noEI.jsp的JSP页 面中禁用EL计算:
<jsp-config>
<jsp-property-group>
<url-pattern>/noEl.jsp</url-pattern>
<el-ignored>true</el-ignored>
</jsp-property-group>
</jsp-config
也可以像下面这样,通过给 url-pattern 元素赋值 *.jsp,来禁用一个应用程序中所有 JSP页面的EL计算:
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<el-ignored>true</el-ignored>
</jsp-property-group>
</jsp-config>
无论是将其page指令的isELIgnored属性设为True, 还是将其URL与子元素el-ignored设为True的jspproperty-group元素中的模式相匹配,都将禁用JSP页面 中的EL计算。假如将一个JSP页面中page指令的 isELIgnored属性设为False,但其URL与在部署描述符 中禁用了EL计算的JSP页面的模式匹配,那么该页面的 EL计算也将被禁用。 此外,如果使用的是与Servlet 2.3及其更低版本兼 容的部署描述符,那么EL计算已经默认关闭,即便使 用的是JSP 2.0及其更高版本的容器,也一样