• Spring MVC -- 表达式于语言(EL)


    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三种脚本

  • 相关阅读:
    【Android】Android消息处理机制
    【Android】Sensor框架HAL层解读
    【Android】Sensor框架Framework层解读
    【流媒体】初识流媒体与流媒体技术
    【Git】Git与GitHub 入门
    【Delphi】基于状态机的串口通信
    【Delphi】SPComm注意事项
    【Android】事件输入系统-代码层次解读
    【Android】事件处理系统
    【Android】窗口机制分析与UI管理系统
  • 原文地址:https://www.cnblogs.com/zyly/p/10852247.html
Copyright © 2020-2023  润新知