JSP 2.0最重要的特性之一就是表达式语言(EL),JSP用户可以用它来访问应用程序数据。由于受到ECMAScript和XPath表达式语言的启发,EL也设计成可以轻松地编写免脚本(就是不用在jsp文件中嵌入脚本)的JSP页面。也就是说页面中不使用任何JSP声明、表达式或者scriptlet。

本篇博客将会介绍如何使用EL表达式在JSP页面中显示数据和对象属性,它涵盖了最新的EL3.0版本技术。

一 表达式语言简史

JSP 2.0最初是将EL应用在JSP标准标签库(JSTL)1.0规范中。

JSP 1.2程序员将JSTL库导入到他们的应用程序中,就可以使用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.0EL也无法满足JSF的所有需求,因此为JSF 1.0开发了一款EL的变体,后者这两种语言变体合二为一。

2013年5月发布了EL 3.0版本,EL不再是JSP或任何其它技术的一部分,而是一个独立的规范。EL 3.0添加了对lambda表达式的支持,并允许集合操作,其lambda支持不需要Java SE8,Java SE7即可。

二 表达式语言的语法

EL表达式以${开头,并以}结束。EL表达式的结构如下:

${expression}
#{expression}

例如,表达式x+y,可以写成:

${x+y}

或者:

#{x+y}

\${exp}和#{exp}结构都由EL引擎以相同的方式进行计算。然而,当EL未被用作独立引擎而是使用诸如JSF或JSP的底层技术时,该技术可以不同地解释构造。例如,在JSF中,\${exp}结构用于立即计算,#{expr}结构用于延迟计算(即表达式直到系统需要它的值时,才进行计算)。另一方面,立即计算的表达式,会在JSP页面编译时同时编译,并在执行JSP页面时被执行。在JSP 2.1和更高版本中,#{exp}表达式只能在接受延迟表达式的标签属性中使用。

两个表达式可以连接在一起。对于一系列的表达式,它们的取值将是从左到右进行,计算结构的类型为String,并且连接在一起。假设$a+b=8$,$c+d=10$,那么这两个表达式的计算结果将是810:

${a+b}${c+d}

表达式\${a+b}and\${c+d}的取值结果则是8and10。

注意:在EL表达式中的"+"只有数学运算的功能,没有连接符的功能,它会试着将运算符两边的操作数转换为数值类型,进而进行数学加法运算,然后将结果输出。若出现\${"a"+"b"}则会出现异常。如果想将两个字符串连接可以使用\${"a"+="b"}。

如果在定制标签的属性值中使用EL表达式,那么该表达式的取值结果字符串将会强制变成该属性需要的类型:

<my:tag someAttribute="${expression}"/>

像\${这样的字符顺序就表示是一个EL表达式的开头,如果需要的只是文本\${,则需要在它前面加一个转义符\\\${。

1、关键字

以下是关键字,它们不能用作标识符:

and eq gt true instanceof
or ne le false empty
not lt ge null div mod

2、[]和.运算符

EL表达式可以返回任意类型的值。如果object是一个带有属性的对象,则可以利用[]或者.运算符来object的属性。[]和.运算符类似;[]是比较规范的形式,.运算符则比较快捷。

为了访问对象的属性,可以使用以下任意一种形式:

${object["propertyName"]}
${object.propertyName}

但是,如果propertyName不是有效的Java变量名, 即属性名称中包含一些特殊字符,如. 或 – 等并非字母或数字的符号,则只能使用[]运算符。

例如,下面这两个EL表达式可以用来访问隐式对象header中的host属性(EL表达式的隐式对象是指不需要new,就可以使用的对象,即JSP容器为每个页面中的开发人员提供的Java对象):

${header["host"]}
${header.host}

但是,要访问accept-language属性,只能使用[]运算符。

如果对象的属性碰巧返回带有属性的另一个对象,即可以使用[],也可以用.运算符来访问第二个对象的属性。例如隐式对象pageContext是表示当前JSP的PageContext对象,它有request属性,表示HttpServlertRequest。HttpServlertRequest自带servlertPath属性,那么下列几个表达式结果相同,均能得出pageContext中HttpServlertRequest的servlertPath属性:

${pageContext["request"]["servletPath"]}
${pageContext.request["servletPath"]}
${pageContext.request.servletPath}
${pageContext.["request"].servletPath}

要访问HttpSession,可以使用以下语法:

${pageContext.session}

例如,以下表达式会得出session标识符:

${pageContext.session.id}

3、自动转变类型

EL 提供方一个方便的功能就是:自动转变类型,我们来看下面这个范例:

${param.count + 20}

假若窗体传来count的值为10时,那么上面的结果为30。之前没接触过JSP 的读者可能会认为上面的例子是理所当然的,但是在JSP 1.2 之中不能这样做,原因是从窗体所传来的值,它们的类型一律是String,所以当你接收之后,必须再将它转为其他类型。如:int、float 等等,然后才能执行一些数学运算,下面是之前的做法:

<%
  String str_count = request.getParameter("count");
  int count = Integer.parseInt(str_count);
  count = count + 20;
%>

所以,注意不要和java的语法(当字符串和数字用“+”链接时会把数字转换为字符串)搞混淆。

4、取值规则

EL表达式的取值是从左到右进行的,对于exp-a[exp-b]形式的表达式,其EL表达式的取值方法如下:

  1. 先计算exp-a得到value-a;
  2. 如果value-a为null,则返回null;
  3. 然后计算exp-b得到value-b;
  4. 如果value-b为null,则返回null;
  5. 如果value-a为java.util.Map,则会查看value-b是否为Map的一个key。若是,则返回value-a.get(value-b),若不是,则返回null;
  6. 如果value-a为java.util.List或者假设它是一个Array,则要进行一下处理:

a.强制value-b为int,如果强制失败,则抛出异常;

b.如果value-a.get(value-b)抛出IndexOutOfBoundsException,或者假设Array.get(value-a,value-b)抛出ArrayIndexOfBoundsException,则返回null;

c.否则,若value-a是一个List,则返回value-a.get(value-b);若value-a是一个Array,则返回Array.get(value-a,value.b);

7.如果value-a不是一个Map,List或者Array,那么value-a必须是一个JavaBean。在这种情况下,必须强制value-b为String。如果value-b是value-a的一个可选属性,则要调用该属性的getter方法,从中返回值。如果getter方法抛出异常,该表达式就是无效的,否则,该表达式有效。

三 使用EL访问数据

1、访问变量

EL 存取变量数据的方法很简单:例如:

${username}

它的意思是取出某一范围中名称为username的变量。因为我们并没有指定哪一个范围的username,所以它的默认值会先从Page 范围找,假如找不到,再依序到Request、Session、Application范围。假如途中找到username,就直接回传,不再继续找下去,

但是假如全部的范围都没有找到时,就回传null,当然EL表达式还会做出优化,页面上显示空白,而不是打印输出null。

属性范围

EL中的名称

Page

PageScope

Request

RequestScope

Session

SessionScope

Application

ApplicationScope

我们也可以指定要取出哪一个范围的变量:

范例

说明

${pageScope.username}

取出Page范围的username变量

${requestScope.username}

取出Request范围的username变量

${sessionScope.username}

取出Session范围的username变量

${applicationScope.username}

取出Application范围的username变量

其中,pageScope、requestScope、sessionScope和applicationScope都是EL 的隐含对象,由它们的名称可以很容易猜出它们所代表的意思,

例如:${sessionScope.username}是取出Session范围的username 变量。这种写法是比之前JSP的写法容易、简洁许多.:

<%
  String username = session.getAttribute("username");
%>

2、访问JaveBean

利用.或[]运算符,都可以访问bean的属性,其结构如下:

${beanName["propertyName"]}
${beanName.propertyName}

例如,访问myBean的secret属性,可以使用以下表达式:

${myBean.secret}

3、访问List、Array和Map

可以通过索引来访问List和Array,如下表达返回hobbies(Array或List)中的3个元素:

//Lits或Array
${requestScope.hobbies[0]}
${requestScope.hobbies[1]}
${requestScope.hobbies[2]}

可以通过如下方式访问Map:

${map[key]}

例如:

//Map
${requestScope.map["china"]}
${requestScope.map.china}
${{"Canada":"Ottawa","China":"Beijing"}["Canada"]}

四  EL隐式对象

在JSP页面中,可以利用JSP脚本来访问JSP隐式对象。如下:

    <%
//设置register.jsp注册信息提交内容编码方式 只对表单post提交方式有效 tomcat8以后默认提交内容编码为utf-8
request.setCharacterEncoding("UTF-8");
String name = request.getParameter("uname");
String pwd = request.getParameter("upwd");
//要求年龄输入必须是数字
int age = Integer.valueOf(request.getParameter("uage"));
String[] hobbies = request.getParameterValues("uhobbies"); %>

但是,在免脚本的JSP页面中(不使用JSP脚本),则不可能访问这些隐式对象。EL允许通过提供一组它自己的隐式对象来访问不同的对象。EL隐式对象如下表:

隐式对象

类型

说明

pageContext

javax.servlet.jsp.PageContext

表示此JSP的PageContext

pageScope

java.util.Map

取得Page范围的属性名称所对应的值

pageRequest

java.util.Map

取得Request范围的属性名称所对应的值

sessionScope

java.util.Map

取得Session范围的属性名称所对应的值

applicationScope

java.util.Map

取得Application范围的属性名称所对应的值

param

java.util.Map

如同ServletRequest.getParameter(String name)。返回String类型的值

paramValues

java.util.Map

如同ServletRequest.getParameterValues(String name)。返回String[]类型的值

header

java.util.Map

如同ServletRequest.getHeader(String name)。返回String类型的值

headerValues

java.util.Map

如同ServletRequest.getHeaders(String name)。返回String[]类型的值

cookie

java.util.Map

如同HttpServletRequest.getCookies()。返回String[]类型的值

initParam

java.util.Map

如同ServletContext.getInitParameter(String name)。返回String类型的值

1、pageContext

pageContext隐式对象表示当前JSP页面的javax.servlet.jsp.PageContext。它包含了所有其它的JSP隐式对象,如下表:

对象 EL中的类型
request javax.servlet.http.HttpServlertRequest
response javax.servlet.http.HttpServlertResponse
out javax.servlet.jsp.JspWriter
session javax.servlet.http.HttpServlertRequest
application javax.servlet.ServletContext
config javax.servlet.ServletConfig
PageContext javax.servlet.jsp.PageContext
page javax.servlet.jsp.HttpJspPage
exception javax.lang.Throwable

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

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

并且,还可以利用以下任意一个表单时来获取请求方法:

${pageContext["request"]["method"]}
${pageContext["request"].method}
${pageContext.request["method"]}
${pageContext.request.method}

下表列出${pageContext.request}中一些有用的属性:

属性 说明
characterEncoding 请求的字符编码
contentType 请求的MIME类型
locale 浏览器首先loale
locales 所有locale
protocol HTTP协议,例如:HTTP/1.1
remoteAddr 客户端IP地址
remoteHost 客户端IP地址或主机名
scheme 请求发送方案,HTTP或HTTPS
serverName 服务器主机名
serverPort 服务器端口
secure 请求是否通过安全链接传输

对请求参数的访问比对其它隐式对象更加频繁;因此,这里提供了param和paramValues两个隐式对象。

2、作用于访问对象(EL域对象)

与范围有关的EL隐含对象包含以下四个:pageScope、requestScope、sessionScope 、applicationScope,它们基本上就和JSP的pageContext、request、session和application一样。

不过必须注意的是,这四个隐含对象只能用来取得范围属性值,即JSP中的getAttribute(String name),却不能取得其他相关信息。例如:JSP中的request对象除可以存取属性之外,还可以进行设置属性、请求转发等:

request.setAttribute("name", "郑洋");
//请求转发
request.getRequestDispatcher("rq.jsp").forward(request,response);

但是在EL中,它就只能单纯用来取得对应范围的属性值。例如:我们要在session 中储存一个属性,它的名称为username,在JSP 中使用session.getAttribute("username")来取得username 的值,但是在EL中,则是使用${sessionScope.username}来取得其值的。

注意:如果不指定域对象,则默认根据从小到大的顺序依次取值,即返回pageScope、requestScope、sessionScope、applicationScope中第一个同名的对象。

3、param

隐式对象param用于获取请求参数值(主要是表单数据)。这个对象表示一个包含所有请求参数的Map。例如,要获取userName参数,可以使用以下任意一种表达式:

${param.userName}
${param["userName"]}

等价于JSP脚本:

<%
  String username = request.getAttribute("username");
%>

4、paramValues

利用隐式对象可以获取一个请求参数的多个值。这个对象表示一个包含所有请求参数,并以参数名称作为key的Map,每个key的值时一个字符串数组,其中包含了指定参数名称的所有值。即使该参数只有一个值,它也仍然返回一个带有一个元素的数组。例如,为了获取selectedOptions参数的第一个值和第二个值,可以使用以下表达式:

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

5、header

隐式对象header表示一个包含所有请求标题的Map,主要包含以下属性:

为了获取header值,要利用header属性名称作为key。例如,为了获取accept-language这个header,可以使用以下表达式:

${header["accept-language"]}

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

${header.connnection}

6、headerValues

隐式对象headerValues表示一个包含所有请求标题并以header属性名称作为key的Map。但是与header不同的是,隐式对象headerValues返回的Map是一个字符串数组。例如,为了获取标题accept-language的第一个值,要使用以下表达式:

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

7、cookie

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

${cookie.jsessionid.value}

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

${cookie.jsessionid.path}

8、initParam

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

$[initParam.password}
$[initParam["password"]}

看到这里,大家应该很明确EL表达式只能通过内置对象取值,也就是只读操作,如果想进行写操作的话就让后台代码去完成,毕竟EL表达式仅仅是视图上的输出标签罢了。

五 使用其他EL运算符

除了.和[]运算符,EL还提供了其它运算符:算术运算符、关系预算法、逻辑运算符、条件运算符、以及empty运算符。使用这些运算符,可以进行不同的运算,但是由于EL的目的是方便免脚本JSP页面的编程,因此,除了关系运算符外,这些EL运算符的用处都很有限。

1、算术运算符

算术运算符有5种:

  • 加法(+);
  • 减法(-);
  • 乘法(*);
  • 除法(/或div)
  • 取余/取模(%和mod)

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

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

  • */div%mod;
  • +-;

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

${1+2*3}

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

注意:在EL表达式中的"+"只有数学运算的功能,没有连接符的功能,它会试着将运算符两边的操作数转换为数值类型,进而进行数学加法运算,然后将结果输出。若出现\${"a"+"b"}则会出现异常。如果想将两个字符串连接可以使用\${"a"+="b"}。

2、关系运算符

下面是关系运算符列表:

关系运算符

说明

范例

结果

== 或 eq

等于

\${5==5}或\${5eq5}

true

!= 或 ne

不等于

\${5!=5}或\${5ne5}

false

< 或 lt

小于

\${3<5}或\${3lt5}

true

> 或 gt

大于

\${3>5}或\${3gt5}

false

<= 或 le

小于等于

\${3<=5}或\${3le5}

true

>= 或 ge

大于等于

\${3>=5}或\${3ge5}

false

表达式语言不仅可在数字与数字之间比较,还可在字符与字符之间比较,字符串的比较是根据其对应UNICODE值来比较大小的。

注意:在使用EL 关系运算符时,不能够写成:

${param.password1} = =${param.password2}

或者

${ ${param.password1 } = = ${param.password2 } }

而应写成

${ param.password1 = =param.password2 }

3、逻辑运算符

下面是逻辑运算符列表:

逻辑运算符

范例

结果

&&或and

交集\${A && B}或\${A and B}

true/false

||或or

并集\${A || B}或\${A or B}

true/false

!或not

非\${! A }或\${not A}

true/false

4、条件运算符

E条件运算符的语法如下:

${statement?A:B}

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

例如,利用下列EL表达式可以测试HttpSession中是否包含名为loggedIn的属性。如果找到这个属性,就显示“You have logged in(您已经登录)”,否则显示“You have not logged in(您尚未登录)”。

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

5、empty运算符

empty 运算符主要用来判断值是否为空(NULL,空字符串,空集合)。下面是一个empty运算符的使用范例:

${empty X}

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

6、字符串连接运算符

+=运算符用于练级字符串,例如,以下表达式打印a+b的值:

${a+=b}

7、分号操作符

;操作符用于分割两个表达式。

六 引入静态属性和静态方法

我们可以使用EL表达式引用在任何Java类中定义的静态字段和方法。但是,在JSP页面中引用静态字段或方法之前,必须使用page伪指令导入包或类包。java.lang包是一个例外,因为它是自动导入的。

我们可以使用page指令导入java.time包:

<%@ page import="java.time.*"%> 

或者,导入单个类:

<%@ page import="java.time.LocalDate"%> 

然后,就可以使用EL表达式引用LocalDate类的静态now()方法:

Today is ${LocalDate.now()}

七 创建Set、List和Map

使用EL表达式可以动态的创建Set、List和Map。创建一个Set的语法如下:

{comma-delimited-elements}

例如,如下表达式创建一个5个数字的Set:

${{1,2,3,4,5}}

创建一个List语法如下:

[comma-delimited-elements]

例如,如下表达式创建一组花名的List:

${["Aster","Carnation","Rose"]}

最后,创建一个Map的语法为:

[comma-delimited-key-value-entries]

如下为一组国家及其首都的Map:

${{"Cannada":"Ottawa","China":"Beijing","France":"Paeis"}}

八 操作集合

EL 3.0带来了很多新特性。其中一个主要的贡献是操纵集合的能力。你可以通过调用流方法将集合转换为流来使用此功能。

下面展示如何将列表转换为流,假设myList是一个java.util.List:

${myList.stream()}

大部分流的操作会返回另一个流,因为可以形成链式操作:

${myList.stream().operation-1().operation-2().toList()}

在链式操作的末尾,通常调用toList()方法,以便打印或格式化结构,以下小结介绍了你可以对流执行的一些操作。

1、toList

toList()方法返回一个List,它包含与当前流相同的成员。调用此方法的主要目的是轻松地打印或操作流元素。下面是一个将列表转换为流,并返回列表的示例:

${[100,200,300].stream().toList()}

当然这个例子也没什么用,稍后在接下来的小节中,你将看到更多的例子。

2、toArray

与toList()类似,但返回的是一个Java数组,同样,在数组中呈现元素通常是有用的,因为许多Java方法将数组作为参数。这里是一个toArray()的例子:

${["one","two","three"].stream().toArray()}

与toList()不同,toArray()不打印元素,因此次,toList()更经常使用。

3、limit

limit()方法限制流中元素的数量。

名为cities的List包含7个城市:

[Paris, Strasbourg, London, New York, Beijing, Amsterdam, San Francisco]

下面的代码将元素的数量限制为3:

${cities.stream().limit(3).toList()}

执行时,表达式将返回此列表:

[Paris, Strasbourg, London]

如果传递给limit()方法的参数大于元素的数量,则返回所有的元素。

4、sorted

此方法对流中的元素进行排序,例如,这个表达式:

${cities.stream().sorted().toList()}

返回如下排序后的列表:

[Amsterdam, Beijing, London, New York, Paris, San Francisco, Strasbourg]

5、average

此方法返回流中所有元素的平均值。其返回值是一个Optional对象,它可能为null。需要调用get()获取实际值。

此表达式返回4.0:

${[1,3,5,7].stream().average().get()}

6、sum

才方法计算流中所有元素的总和。例如,此表达式返回16:

${[1,3,5,7].stream().sum()}

7、count

此方法返回流中元素的数量。例如,次表达式返回7:

${[1,3,5,7].stream().count()}

8、min

此方法返回流中元素中的最小值。同average()方法一样,其返回值是一个Optional对象,因此你需要调用get()方法来获取实际值。

例如,此表达式的返回值为1;

${[1,3,100,1000].stream().min().get()}

9、max

此方法返回流中元素中的最大值。同average()方法一样,其返回值是一个Optional对象,因此你需要调用get()方法来获取实际值。

例如,此表达式的返回值为1000;

${[1,3,100,1000].stream().max().get()}

10、map

此方法将流中的每一个元素映射到另一个流中的另一个元素,并返回该流。此方法接受一个lambda表达式。

例如,此映射方法使用lambda表达式x->2*x,这实际上将每个元素乘2,并将它们返回到新的流中:

${[1,3,5].stream().map(x->2*x).toList()}

返回列表如下:

[2,6,10]

下面是另一个示例,它将字符映射为大写。

${cities.stream().map(x->x.toUpperCase()).toList()}

它返回以下列表:

[PARIS, STRASBOURG, LONDON, NEW YORK, BEIJING, AMSTERDAM, SAN FRANCISCO]

11、filter

此方法根据lambda表达式过滤流中的所有元素,并返回包含结果的新流。

例如,以下表达式测试城市是否以“S”开头,并返回所有的结果:

${cities.stream().filter(x->x.startsWith("S").toList())}

它产生的列表如下所示:

[Strasbourg, San Francisco]

12、forEach

此方法对流中的所有元素执行操作,它返回void。

例如,此表达式将城市中的所有元素打印到控制台:

${cities.stream().forEach(x->System.out.println(x))}

部分代码:

    <%
List<String> cities= Arrays.asList("Paris","Strasbourg","London","New York","Beijing","Amsterdam","San Francisco");
out.print(cities);
//只在当前页面有效
pageContext.setAttribute("cities", cities);
%>
<br/>
1:${cities}<br/>
2:${cities.stream().map(x->x.toUpperCase()).toList()}<br/>
3:${cities.stream().sorted().toList()}<br/>
4:${cities.stream().filter(x->x.startsWith("S")).toList()}<br/>
5:${cities.stream().forEach(x->System.out.println(x))}<br/>

注意:需要使用tag伪指令导包:

<%@ page import="java.util.*" %>

输出:

九 格式化集合

由于EL定义了如何写表达式而不是函数,因此无法直接打印或格式化集合,毕竟,打印和格式化不是EL负责的领域。然而,打印和格式化是两个不能忽视的重要任务。

最简单的方法就是使用forEach()方法,以下代码可以再tomcat 8以上运行:

<ul>
${cities.stream().forEach(x->pageContext.out.println("<li>"+=x+="</li>"))}
</ul>

遗憾的是,这在GlassFish4中不起作用,所以forEach()不能通用。

但是我们可以使用以下两种解决方案,虽然不像forEach()那么优雅,但是在所有主要的servlet容器上都可以使用。下面我们将介绍两种格式化集合的方法。

1、使用HTML注释

该解决方案适用于Java SE7版本以上。

List字符串表示形式如下:

[element-1,element-2,...]

现在,如果我想在HTML中呈现列表元素,需要这么写:

<ul>
<li>element-1</li>
<li>element-2</li>
...
</ul>

现在,你可能已经注意到了每个元素必须转向 <li>element-n</li>。那么我们应该如何做?

我们还记得map()方法么,它可以将流中的每一个元素映射到另一个流中的另一个元素,并返回该流。因此我们可以利用map()方法转换每个元素。所以,代码可以这么写:

${myList.stream().map(x->"<li>"+=x+="</li>").toList()}

这样,我们将会得到如下的List:

[<li>element-1</li>,<li>element-2</li>,...]

足够接近,但任然需要删除括号和逗号。遗憾的是,我们无法控制列表的字符串表示。但是好在我们可以使用HTML注释。

所以,下面这个例子:

<ul>
<!-- ${cities.stream().map(x->" -->
<li>"+=x+="</li>
<!--").toList()}-->
</ul>

上面代码${cities.stream().map(x->"<li>"+=x+="</li>").toList()}中,把<li>"+=x+="</li>以外的代码全部使用<!-- -->注释掉,即只保留列表元素,其他EL语法注释掉。

结果如下所示:

<ul>
<!-- [ -->
<li>Paris</li>
<!--, -->
<li>Strasbourg</li>
<!--, -->
<li>London</li>
<!--, -->
<li>New York</li>
<!--, -->
<li>Beijing</li>
<!--, -->
<li>Amsterdam</li>
<!--, -->
<li>San Francisco</li>
<!--]-->
</ul>

可以看到这段代码有效的注释掉了括号和逗号。虽然结果看起来有点混论,但是它是有效的HTML,更重要的是,它能工作。下面是页面的显示效果:

2、使用String.join()

这第二个解决方案适用于Java SE8以上版本,该方法之所以有效,因为EL 3.0允许引入静态方法。在Java SE8中,String类新增了一些静态方法,其中一个方法就是join()。String类中有两个join()重载方法。但是这里需要使用到的一个方法如下所示:

public static String join(CharSequence delimiter,Iterable<? extends CharSequence> elements)

此方法返回用指定分隔符连接在一起的CharSequence元素组成的字符串。而java.util.Collection接口正好实现了Iterable接口,因此,可以讲Collection传递给join()方法。

例如,下面是如何将列表格式化成HTML有序列表:

<ol>
${"<li>"+=String.join("</li><li>",cities)+="</li>"}
</ol>

此表达式适用于至少有一个元素的集合,如果你可能要处理一个空集合,这里有一个更好的表达式:

<ol>
${empty cities?"":"<li>"
+=String.join("</li><li>",cities.stream().sorted().toList())
+="</li>"}
</ol>

十 格式化数字

要格式化数字,你可以利用EL 3.0中允许引用静态方法的能力。String类的format()静态方法可以用来格式化数字。

例如,以下表达式返回带有两个小数点的数字:

${String.format("%-10.2f%n",125.178) }

更多格式化规则可以查阅java.text.DecimalFormat的javadoc文档。

十一 格式化日期

可以通过String.format()来格式化一个date或time。例如:

${d=LocalDate.now().plusDays(2);String.format("%tB %te,%tY%n",d,d,d)}

首先计算LocalDate.now().plusDays(2),并将结果复制给变量d,然后再利用String.format()方法来格式化LocalDate,引用了3次变量d。输出如下:

十二 在JSP 2.0及更高版本中配置EL

有了EL,JavaBean和定制标签,就可以编写免脚本的JSP页面了。JSP2.0及更高的版本中还提供了一个开关,可以使所有的JSP页面都禁用脚本。现在,软件架构师可以强制编写免脚本的JSP页面了。

另一方面,在有些情况下,可能还会需要在应用程序中取消EL。例如,正在使用与JSP 2.0兼容的容器(tomcat),却尚未准备将JSP应用程序升级到JSP 2.0,那么就需要这么做。在这种情况下,可以关闭EL表达式的计算。

注意:最初,JSP 2.0版本才开始支持EL,也就是说JSP 2.0版本之前的应用程序默认是不支持EL的。

1、实现免脚本的JSP页面

为了关闭JSP页面中的脚本元素,要在部署描述符中(web.xml文件)使用jsp-property-group元素以及url-pattern和scripting-invalid两个子元素。url-pattern元素定义禁用脚本要应用的URL样式。下面展示如何将一个应用程序中所有JSP页面的脚本都关闭:

  <jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<scripting-invalid>true</scripting-invalid>
</jsp-property-group>
</jsp-config>

所以当执行带有JSP脚本的代码时:

    <%
out.println("在免脚本jsp页面中,可以使用这个jsp脚本么?");
%>

注意:在部署描述符中只能有一个jsp-config元素。如果已经为禁用EL而定义了一个jsp-property-group,就必须在同一个jsp-config下,为禁用脚本编写jsp-property-group。

2、禁用EL计算

在有些情况下,比如,当需要在JSP 2.0及更高版本的容器中部署JSP 1.2应用程序(该版本默认不支持EL)时,可能就需要禁用JSP页面中的EL计算了。此时,出现的EL结构,就不会作为EL表达式进行计算。目前有两种方式可以禁用JSP中的EL计算。

(1) 可以将page指令的isELIgnored属性设置为true,像这样:

<%@ page isELIgnored="true"%>

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

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

为了利用jsp-property-group元素禁用EL运算,还必须有url-patter和el-ignored两个子元素。url-pattern元素用于定义EL禁用要应用的URL样式。el-ignored元素必须设置为true。

下面举一个例子,展示如何在名为noEl.jsp的jsp页面中禁用EL计算。

  <jsp-config>
<jsp-property-group>
<url-pattern>/noEl.jsp</url-pattern>
<el-ignored>true</el-ignored>
</jsp-property-group>
</jsp-config>

此时,noEl.jsp页面<body>内容如下:

<body>
${1+3}
</body>

输出如下:

无论是将page指令的isELIgnored属性设置为true,还是其URL与el-ignored为true的jsp-property-group的URL模式向匹配,都将禁用JSP页面中的EL计算。假设将一个JSP页面中的page指令的isELIgnored属性设置为false,但其URL与在部署描述符中禁用了EL计算的JSP页面的模式匹配,那么该页面的EL计算也将被禁用。

此外,如果使用的是与Servlet2.3及更低版本兼容的部署描述符,那么EL计算已经默认关闭,即使使用的是JSP 2.0及更高版本的容器,也一样。

参考博客

[1]Spring MVC学习指南

[2]JSP 中EL表达式用法详解

[3]JSP中的九大隐式对象及四个作用域

[4]JSP入门及JSP三种脚本

Spring MVC -- 表达式于语言(EL)的更多相关文章

  1. Spring MVC教程——检视阅读

    Spring MVC教程--检视阅读 参考 Spring MVC教程--一点--蓝本 Spring MVC教程--c语言中午网--3.0版本太老了 Spring MVC教程--易百--4.0版本不是通 ...

  2. 表达式语言EL

    表达式语言EL 表达式语言 EL(Expression Language,表达式语言)主要是用在JSP页面中,用来辅助我们产生无脚本的JSP页面,此处的脚本指的是JSP中的Java代码. EL的语法是 ...

  3. Java EE之表达式语言EL(上)

    1.了解表达式语言 表达式语言(EL)用于在不使用脚本.声明或者表达式的情况下,在JSP页面中渲染数据. EL曾是JSTL 1.0规范(与JSP 1.2)中的一部分,并且只可以用作JSTL标签的特性. ...

  4. spring mvc EL ModelAndView的 Model 值 在jsp中不显示

    问题:spring mvc开发过程中, 经常会给model addAttribute, 然后通过EL在jsp中显示,比如 ${msg}, 但是有时候会出现jsp最后显示的还是${msg},而不是msg ...

  5. Spring MVC 多语言化的实践和学习

    一.主要参考: SpringMVC简单实现国际化/多语言 - CSDN博客 https://blog.csdn.net/u013360850/article/details/70860144/ 二.总 ...

  6. 表达式语言EL简单学习

    Jsp2.0最重要的特性就是表达式语言EL.jsp用户可以用它来访问应用程序数据. EL表达式以${开头并以}结束. ${expresion}     ${x+y} 它也常用来连接两个表达式,取值将从 ...

  7. MAC OS X El CAPITAN 搭建SPRING MVC (1)- 目录、包名、创建web.xml

    一. 下载STS(Spring Tool Suite) 官方地址:http://spring.io/tools/sts 下载spring tool suite for mac 最新版本.这个IDE是很 ...

  8. [Java语言] 《struts2和spring MVC》的区别_动力节点

    1.Struts2是类级别的拦截, 一个类对应一个request上下文,SpringMVC是方法级别的拦截,一个方法对应一个request上下文,而方法同时又跟一个url对应,所以说从架构本身上Spr ...

  9. spring mvc 快速入门

    ---------- 转自尚学堂 高淇 --------- Spring  MVC 背景介绍 Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块.使用 Spring 可插入的 MVC ...

随机推荐

  1. python学习类与方法的调用规则

    1类方法的特点是类方法不属于任何该类的对象,只属于类本身 2类的静态方法类似于全局函数,因为静态方法既没有实例方法的self参数也没有类方法的cls参数,谁都可以调用 3.实例方法只属于实例,是实例化 ...

  2. jmeter设置代理服务器录制脚本

    新建测试计划之后: 1.添加非测试元件:HTTP代理服务器 a.其中目标控制器可以控制选哪个线程放录制的脚本: b.将端口设置为8888或者其他不常用的端口,保持跟其他应用的端口不一致,否则被占用导致 ...

  3. python 打印html源码中xpath

    实例: #coding:utf-8 from lxml import etree import urllib url=urllib.urlopen('http://www.baidu.com').re ...

  4. 关于vue的v-for遍历不显示问题

    实属不才,因为好久没看vue导致忘光了,然后发生了这么小的一个问题,惭愧. 注:vue的注册的el一定要放嘴最外层,不要和v-for放在一起,否则不会显示,因为可以这样讲,el包含的是一个容器,而v- ...

  5. 小米BL不解锁刷机

    关于小米NOTE顶配近期解锁的问题中发现还有很多人不会用9008模式刷机,现出个简单教程方便米粉们救砖.硬件:小米NOTE顶配手机 win10系统的电脑 手机与电脑相连的数据线软件:老版本的mifla ...

  6. NET PDB文件到底包含多少秘密?

    虽然我希望.NET PDB文件与本地PDB文件处理方式相同,但我们在这件事上没有任何选择,因为事情就是这样.我相信微软的调试器团队多年来听到过很多类似帕特里克的评论.也许我们会在未来的Visual S ...

  7. mariadb启动不了

    提示地址已经被使用,是否有其他的进程正在使用 /var/run/sdata/mysql.sock 查询该文件,发现没有,sdata目录都不存在,应该是上次mysql意外关闭导致这个目录丢失了, 使用r ...

  8. vue 自动px单位自动转换rem

    vue 适配移动端 假设设计图是375 第一步 安装 lib-flexible npm i lib-flexible --save 第二步 安装 px2rem-loader npm install p ...

  9. P4899 【[IOI2018] werewolf 狼人】

    感觉已经几次碰到这种类型的题目了,写篇\(Blog\)总结一下 题意: 是否存在一条\((s_i, t_i)\)的路径,满足先只走编号不超过\(L_i\)的点,再走编号不超过\(R_i\)的点 \(S ...

  10. tomcat9源码导入idea

    maven部署 下载源码 tomcat最新版的github地址 tomcat9官网下载 步骤 源码根目录新建 home 文件夹 把 conf 文件夹和 webapps 文件夹移动到 home 文件夹 ...