JavaWeb高级编程(上)
好久没更新了,发一篇以前记录学习的笔记。
面向读者:已经具有丰富的Java语言和Java SE平台知识的软件开发者和软件工程师。
预掌握知识:
- Internet、TCP、HTTP协议
- HTML(5)
- XML
- Javascript或ECMAScript
- CSS
- SQL,MySQL
- 事务及其概念
- IDE使用
- 简单命令行的执行
需要工具:
- Apache Maven版本3.1.1或更新
- 可以执行命令行且能够读取命令行的操作系统
- 文本编辑器或者IDE
推荐sublime2编辑器,或者IDEA集成开发环境
- 支持Java SE 8的Java开发工具包
下面开始正式内容(撒花)
Java EE平台介绍
相信大家已经看过不少关于Java的介绍,所以首先略过Java发展...
平台新特性
1. Java SE7* 添加菱形操作符(<>)。
> `Map<String,Map<String,Map<Integer,List<MyBean>>>> map = new Hashtable<String,Map<String,Map<Integer,List<MyBean>>>>();`
> 可以简化为 `Map<String,Map<String,Map<Integer,List<MyBean>>>> map = new Hashtable<>();`
* 使用try-with-resource管理资源
> 将以前在try块或者finally块中关闭的资源,现在可以如try(resource)-catch-finally一样,放在try后的括号中,这样资源就会在隐式的finally块中自动关闭。
> 同时,对于try-catch-finally的另一处改进就是可以同时捕捉多个异常,条件是异常之间不能有继承关系。
-
Java SE8
- 添加lambda表达式
lambda表达式的本质是匿名函数,在定义和调用时不需要被赋予类型名或绑定到标志符。
详细介绍
基本Web应用程序结构
大量的组件组成了Java EE Web应用程序。首先,需要自己的代码和它依赖的第三方库。然后需要部署描述符,其中包含了部署和启动应用程序的指令。还可以添加ClassLoader用于将自己的应用程序与同一台服务器上的其它Web应用隔离开。最后通过某种方式将应用程序打包,生成WAR和EAR文件。
+
所有的Java EE Web应用程序服务器都支持WAR文件应用程序归档,大多数服务器还支持未归档的应用程序目录。不过它们的目录结构预定都是相同的。
在该结构中,类文件都存储在/WEB-INF/classes中,WEB-INF目录存储了一些包含了信息和指令的文件,Java EE Web应用服务器使用它们决定如何部署和运行应用程序。classes目录被用作包的根目录。所有编译后的应用程序类文件和其它资源都被存储在该目录中。
WAR文件包含的应用程序依赖的JAR文件都被存储在/WEB-INF/lib中。目录/WEB-INF/tags和/WEB-INF/tld分别用于存储JSP标签文件和标签库描述符。
根级别的/META-INF目录中包含了应用程序清单文件。它可以存储特定Web容器或应用程序服务器需要使用的资源。根级别的/META-INF目录并不在应用程序类路径上。不能使用ClassLoader获得该目录中的资源。不过/WEB-INF/classes/META-INF在路径上。可以将任何希望使用的资源文件存储在该目录中,这样就可以通过ClassLoader访问这些资源。一些Java EE组件指定了某些文件存储在该目录中。
部署描述符是用于描述Web应用程序的元数据,并为Java EE Web应用程序服务器部署和运行Web应用程序提供指令。从传统上讲,所有元数据都来自于部署描述符文件/WEB-INF/web.xml。该文件通常包含Servlet、监听器和过滤器的定义,以及HTTP会话、JSP和应用程序的配置选项。Java EE 6 中的Servlet3.0添加了使用注解和Java Configuration API配置Web应用程序的能力。它还增加了Web片段的概念--应用程序中的JAR文件可以包含Servlet、过滤器和监听器的配置,这些配置将被添加到必要的JAR文件的部署描述符文件/META-INF/web-fragment.xml中。Web片段也可以使用注解和Java Configuration API。
在Java SE平台上,当低级别类加载器申请加载一个类时,它总是首先将该任务委托给它的父类加载器。继续向上委托直至根类加载器确认成功。
在Java EE Web应用服务器中,每个Web应用程序都被分配了一个自由的相互隔离的类加载器,它们都继承自公共的服务器加载器。通过隔离不同的应用程序,它们不能访问互相的类。Web应用程序加载器通常会在自己无法加载某个类的时候,请求它的父类加载器帮助加载。通过这种方式,类加载任务会在最后而不是首先委托给它的父类,Web应用程序中的类和库会被优先使用,而不是服务器提供的版本优先使用。
每个应用服务器都包含了一个Web容器,用于管理Servlet的生命周期、将请求URL映射到对应的Servlet代码、接受和响应HTTP请求以及管理过滤器。
Apache Tomcat是目前最常见和最流行的Web容器。
Tomcat的主要优点是占用内存小、配置简单以及长期的社区参与。同时,Tomcat可以完美地完成许多任务,但是不能轻松地部署复杂的企业级应用程序。
Tomcat提供了Servlet、Java Server Pages(JSP)、Java Unified Expression Language(EL)和WebSocket规范。
Tomcat详细信息
GlassFish服务器是一个开源的、也是商业的完整Java EE应用服务器实现。它提供了Java EE规范的所有特性,包括Web容器,而且它还是Java EE规范的参考实现。GlassFish的开源版本由社区提供支持,而Oracle的商业GlassFish服务器版本由Oracle公司提供收费的商业支持。Oracle只为Java EE7之前的版本提供商业支持,从Java EE8开始,GlassFish不再包含商业支持选项。
GlassFish的一个优势是它的管理界面,可以通过图形Web用户界面、命令行界面和配置文件等方式对服务器进行设置。服务器管理员甚至可以使用管理界面在GlassFish集群中部署新的GlassFish实例。
GlassFish详细信息
之后我们的Web应用服务器将使用Tomcat。
Tomcat的安装,和对应IDE的配置参考教程:
Windows版
Ubuntu版
在Java EE平台中,Servlet用于接收和响应终端用户的请求。Servlet在Java EE API规范中的定义如下:
Servlet是一个运行在Web服务器中的Java小程序。Servlet将会接收和响应来自Web客户端的请求,使用HTTP进行通信。
创建Servlet
Servlet是所有Web应用程序的核心类,它是唯一的既可以直接处理和响应用户请求,也可以将处理工作委托给应用中的其它部分的类。
除非某些过滤器提前终止了客户端的请求,否则所有的请求都将被发送到某些Servlet中。
所有的Servlet都实现了javax.servlet.Servlet接口,但通常不是直接实现的。Servlet只是一个简单接口,它包含了初始化并销毁Servlet和处理请求的方法。
多数情况下,Servlet都继承自javax.servlet.GenericServlet。GenericServlet仍然是一个不依赖于具体协议的Servlet,它只包含一个抽象的service方法,但它同时也包含了几个辅助方法用于日志操作和从应用和Servlet配置中获取信息。
作为响应HTTP请求的java.servlet.http.HttpServlet,它继承了GenericServlet,并实现了只接受HTTP请求的service方法。然后它提供了响应每种HTTP方法类型的方法的空实现。
我们之后的Servlet将总是继承HTTPServlet。它的方法接收的是javax.servlet.http.HttpServletRequest和javax.servlet.http.HttpServletResponse参数。
eg:
import javax.servlet.http.HttpServlet;
public class HelloServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest r,HttpServletResponse ,re)
throws ServletException,IOException{
response.getWriter().println("Hello, World")
}
}
以上,该Servlet即能够对GET请求做出响应,并在响应主体中显示出“Hello, World”。
初始化和销毁方法
@Override
public void init() throws ServletException{
...
}
@Override
public void destroy(){
...
}
在Web容器启动Servlet时,会调用Servlet的init方法,有时也会在部署应用程序时调用该方法。
在容器关闭Servlet时,会调用destroy方法。
配置可部署的Servlet
在web-INF目录中创建web.xml文件,并对Servlet进行配置,使它可以正确地部署到服务器中。
向描述符中添加Servlet
如在web.xml中添加部署描述:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>Hello World Application</display-name>
示例中,<display-name></display-name>
描述的为应用程序在应用服务器中显示的名字。<web-app></web-app>
中version特性描述应用程序使用的Servlet API版本。
在该例中,init方法将在web应用程序启动后,第一个请求到达Servlet时调用,但是如果init需要花费过长时间来完成工作,则会导致第一个请求的时间被延长很多,因此我们可以在Servlet配置中进行调整,以让它在Web应用启动之后立即启动。
<servlet>
<servlet-name>helloServlet</servlet-name>
<servlet-class>com.wrox.HelloServlet</servlet-class>
<load-on-start-up>1</load-on-start-up>
</servlet>
如上,在<load-on-start-up>
标签中设置1,使得Web容器在应用程序启动之后,立即启动Servlet。如果多个Servlet配置都包含了该标签,它们的启动顺序将按照标签内的值的大小顺序启动,1表示第一个启动,数字越大表示启动越晚。如果出现值相同的情况,则按照它们出现在配置文件中的顺序来启动。
使用配置文件来告诉Servlet来应该对哪些请求URL做出响应:
<servlet-mapping>
<servlet-name>helloServlet</servlet-name>
<url-pattern>/greeting</url-pattern>
</servlet-mapping>
使用如上配置之后,所有访问应用程序相对URL/greeting的请求都将由helloServlet处理。
Web容器通过这种方式关联两个配置。
Servlet类的service方法会处理所有到达的请求。最终,它必须根据所使用的协议解析并处理到达请求中的数据,然后返回客户端可接受的响应。
了解doGet、doPost和其他方法
HttpServletRequest接口是对ServletRequest的扩展,它将提供关于收到请求的额外的与HTTP协议相关的信息。它指定了多个可以获得HTTP请求的详细信息的方法。它也允许设置请求特性。
HttpServletREquest最重要的功能:从客户端发送的请求中获取参数。
请求参数有两种不同的形式:查询参数(也成为URI参数)、以application/x-www-form-urlencoded或multipart/form-data编码的请求正文。所有的请求方法都支持查询参数,它们被添加在HTTP请求的第一行数据中,如:
GET /index.jsp?productId=123456789&category=Book HTTP/1.1
例子中包含两个查询参数:productId和category。
方法getParameter将返回参数的单个数值。如果参数有多个数值,getParameter将返回第一个值,而getParameterValues将返回参数的值的数组。如果参数只有一个值,则返回只有一个值的数组。方法getParameterMap将返回一个包含了所有参数名值对的java.util.Map<String, String[]>,而getParameterNames方法将返回所有可用参数的名字的枚举。
方法getInputStream将返回一个javax.servlet.ServletInputStream,而方法getReader将返回一个java.io.BufferedReader,它们都可用于读取请求内容。如果请求内容是基于字符编码的,使用BufferedReader是最简单的方式,请求数据是二进制的,必须使用ServletInputStream,这样才可以访问字节格式的请求内容。永远不应该在同一请求上同时使用这两种方法。
HttpServletResponse继承了ServletResponse接口,提供了对响应中与HTTP协议相关的属性的访问。可以使用响应对象完成设置响应头、编写响应正文、重定向请求、设置HTTP状态码以及将Cookies返回到客户端等任务。
方法getOutputStream返回一个javax.servletServletOutputStream,而方法getWriter将返回一个java.io.PrintWriter,通过对它们都可以向响应中输出数据。
我们可以在Servlet中重写doGet和doPost方法来分别处理get和post请求。
需要注意的是,我们应该在Servlet的声明上进行注解,如:
@WebServlet{
name = "helloServlet",
urlPatterns = {"/greeting","/wazzup"},
loadOnStartup = 1
}
public class HelloServlet extends HttpServlet
{
...
}
如上所示,可以代替编写对用的XML。
使用初始化参数配置应用程序
虽然在Servlet类上的注解代替了在部署描述符文件中的Servlet声明和映射,但是存在其它的一些配置必须通过部署描述符才能完成。上下文初始化参数就是其中之一。在web.xml文件中使用<context-param》标签声明上下文初始化参数。
声明初始化参数举例:
<context-param>
<param-name>setting</param-name>
<param-value>value</param-value>
</context-param>
在Servlet代码的任何地方都能够轻松获得和使用初始化参数,使用代码举例:
ServletContext sc = this.getServletContext();
sc.getInitParameter("setting");
或者:
定义:
<servlet>
<servlet-name>...</servlet-name>
<servlet-class>...</servlet-class>
<init-param>
<param-name>setting</param-name>
<param-value>hello</param-value>
</init-param>
</servlet>
使用:
ServletConfig sc = this.getServletConfig();
sc.getInitParameter("setting");
Web应用程序是天然的多线程应用程序。Web容器中通常会包含某种类型的线程池,它们被称为连接池或执行池。当容器收到请求时,它将在池中寻找可用的线程。如果找不到可用的线程,并且线程池已经达到了最大线程数,那么该请求将被放入一个队列等待获得可用的线程。
使用JSP显示页面内容
JSP是一个混合解决方案,它结合了Java代码和HTML标签。JSP可以包含除了Java代码之外的任何HTML标签、内建JSP标签、自定义JSP标签以及表达式语言。
JSP中的指令标签:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
该指令标签提供了对JSP如何转换、渲染和传输的控制。
指令标签中:特性language将告诉容器JSP中使用的是哪种脚本语言。contentType告诉容器在发送响应时如何设置其中Content-Type头的值。Content-Type头同时包含内容类型和字符编码,以分号隔开。
JSP中除了各种不同的HTML和JSP标签,还有几种独特的结构用于JSP中:
- 使用指令
<%@ 指令 %>
指令用于指示JSP解释器执行某个操作或者对文件做出假设、导入类、在转换时包含其他JSP或者包含JSP标签库。
2. 使用声明
<%! 声明 %>
用于在JSP Servlet类的范围内声明一些东西,例如定义实例变量、方法或声明标签中的类。这些声明都将自动出现在自动生成的JSP Servlet类中,所以声明中定义的类实际上是JSP Servlet类的内部类。
3. 使用脚本
<% 脚本 %>
- 使用表达式
<%= 表达式 %>
无论何时在JSP中包含直接使用类的Java代码,该JSP要么使用完全限定类名,要么在JSP文件中添加一条导入指令。
eg: <%@ page import="java.util.*,java.io.*" %>
包含其它JSP
eg: <%@ include file="/path/to/some/file.jsp" %>
<jsp:include page="/path/to/some/file.jsp" />
前一个是静态方式包含,后一个是动态方式。
在静态方式中,在JSP被转换成Java之前,编译器将使用被包含JSP文件的内容替换include指令。在此之后,合并后的JSP文件将被转换成Java代码并编译。
在动态方式中,被包含的文件将会被单独编译。在运行时,请求将会被临时地重定向到被包含的JSP,再将该JSP的结果输出到响应中,然后再将控制权返还给主JSP页面。
Java方法编译后的字节数目最大不能超过65534字节。
如果希望使用标签库中的标签,需要使用taglib指令引用该标签库:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
特性uri指定了目标标签库所属的URI命名空间,特性prefix定义了用于引用标签库中标签时使用的别名。
jsp:forward标签用于将当前JSP正在处理的一些请求转发至其他JSP。
eg:<jsp:forward page="/path/to/some/file.jsp"
使用该标签时,在该标签之前生成的任何响应内容仍然会被发送到客户端浏览器中。任何在此标签之后的代码都将被忽略。
标签<jsp:useBean>
在页面中声明一个JavaBean,标签<jsp:getProperty>
将从使用jsp:useBean声明的bean中获取属性值,标签<jsp:setProperty>
将用于设置该实例的属性,标签<jsp:plugin>
用于在HTML页面中内嵌Java Applet。
WEB-INF目录中的文件都是禁止通过web访问的。可以将JSP文件添加到该目录中以防止用户通过浏览器访问这些JSP。
使用请求派发器将Servlet中的请求转发给JSP
private void funName(HttpServletRequest re,HttpServletREsponse res)
throws ServletException, IOException
{
re.getRequestDispatcher("/WEB-INF/jsp/view/ticketForm.jsp")
.forward(re,res);
}
使用会话维持状态
HTTP请求自身是完全无状态的。从服务器的角度来说,当用户的Web浏览器打开第一个连接到服务器的套接字时请求就开始了,直到服务器返回最后一个数据包关闭连接时,该请求结束。此时,在用户的浏览器和服务器之间不再有任何联系,当下一个连接开始时,无法将新的请求与之前的请求关联起来。
使用会话可以:
- 维持请求和请求之间的状态
- 记住用户
- 启动应用程序的工作流
在Web会话理论中,会话是由服务器或Web应用程序管理的某些文件、内存片段、对象或者容器,它包含了分配给它的各种不同的数据。用户浏览器中不需要保持或维持任何此类数据。它们只有服务器或Web应用程序代码管理。容器和用户浏览器之间将通过某种方式连接起来。处于此原因,通常会话将被赋予一个随机生成的字符串,成为会话ID。第一次创建会话时,创建的会话ID将会作为响应的一部分返回到用户浏览器中。接下来从该用户浏览器中发出的请求都将通过某种方式包含该会话ID。当应用程序收到含有会话ID的请求时,它可以通过该ID将现有会话与当前请求关联起来。
其中需要注意的是如何将会话ID从服务器返回到浏览器中,并在之后的请求中包含该ID。目前有两种技术用于完成该任务:会话cookie和URL重写。
HTTP 1.1中给出解决方案:HTTP cookie。
cookie是一种必要的通信机制,可以通过Set-Cookie响应头在服务器和浏览器之间传递任意数据,并存储在用户计算机中,然后再通过请求头Cookie从浏览器返回到服务器。cookie可以有各种不同的特性:
Domain将告诉浏览器应该将cookie发送到哪个域名中
Path进一步将cookie限制在相对于域的某个特定URL中。每次浏览器发出请求时,它都将找到匹配该域和路径的所有cookie,然后将cookie随着请求一起发送到服务器。
Expires定义了cookie的绝对过期日期
Max-Age定义了cookie在过期之前所需的秒数
Secure表示浏览器将只会通过HTTPS发送cookie
HTTPOnly将把cookie限制在浏览器请求中,这样其他技术如:JavaScript和Flash将无法访问cookie。
在Java EE应用服务器中,会话cookie的名字默认为JSESSIONID。
另一种传输会话ID的流行方式是通过URL。Web或应用服务器知道如何查找URL中包含了会话ID的特定模式,如果找到了,就从URL中获得会话。
不同的技术对如何在URL中内嵌和定位会话ID使用不同的策略:
PHP,使用名为PHPSESSID的查询参数:
http://www.example.com/support?PHPSESSID=xxxxxxx&foo=bar
Java,使用矩阵参数:
http://www.exaple.com/support;JSESSIONID=xxxxxxx?foo=bar
当使用这种方法的时候,必须将会话ID内嵌在应用程序返回的所有URL中,包括页面的链接、表单操作以及302重定向。
会话是存在漏洞的,在执行重要任务、含有敏感数据的应用程序中,使用某些商业的扫描器检测应用程序中的漏洞是更加明智的选择。
会话漏洞及解决方案:
- 复制并粘贴错误
漏洞:用户复制并粘贴地址栏中的URL
方案:完全禁止在URL中内嵌会话ID
- 会话固定:
攻击者可能会首先找到一些允许在URL中内嵌会话ID的网站。通过此种方式获得一个会话,然后将含有会话ID的URL发送给目标用户,此时,如果用户点击链接进入网站,它的会话ID就变成了URL中含有的固定ID--攻击者已经持有该ID。如果用户接着在该会话期间登录网站,那么攻击者也可以登录成功,因为这个会话ID是他共享的。
解决方案:
- 禁止在URL中内嵌会话ID
- 在登录后采用会话迁移
- 跨站脚本和会话劫持
漏洞:使用JavaScript读取会话cookie的内容
解决方案:
- 不要在网站中使用跨站脚本
- 在所有的cookie中使用HttpOnly特性
- 不安全的cookie
中间人攻击(MitM攻击),典型的如:数据截获攻击
解决方案:使用HTTPS保护网络通信将有效地防止MitM攻击,并保护会话ID cookie不被盗用。不过用户可能首先使用HTTP访问网站,即使立刻将请求进行重定向到HTTPS,攻击可能已经发生了。使用cookie的Secure标志可以解决此问题。
在许多情况下,都可以在Java EE中直接使用HTTP会话,不需要显式的配置,不过可以在部署描述符中配置它们,并且处于安全的目的也应该配置。在部署描述符中使用
所有在
<session-config>
<session-timeout>30</session-timeout>
<cookie-config>
<name>JSESSIONID</name>
<domain>example.org</domain>
<path>/shop</path>
<comment><![CDATA[example]]</comment>
<http-only>true</http-only>
<secure>false</secure>
<max-age>1800</max-age>
</cookie-config>
<tracking-mode>COOKIE</tracking-mode>
<tracking-mode>URL</tracking-mode>
<tracking-mode>SSL</tracking-mode>
<session-config>
在JSP中使用表达式语言
表达式语言(EL)源于JSP标准库(JSTL)的一部分,用于在不使用脚本、声明或者表达式的情况下,在JSP页面中渲染数据。
EL的基本语法描述了一个必须与其他JSP页面语法分开执行的表达式。基本的EL语法有两种类型:立即执行和延迟执行。
1. 立即执行
立即执行EL表达式将在页面渲染的时候,被JSP引擎解析和执行。因为JSP从上向下执行,这意味着EL表达式将在JSP引擎发现它,并在继续执行其他页面部分之前执行它。如下为一个有效的EL表达式:
$
美元符号和开始/结束花括号定义了EL表达式的边界。
2. 延迟执行
延迟执行EL表达式是统一表达式的一部分,主要用于满足JavaServer Faces的需要。尽管延迟执行语法在JSP中是合法的,但通常不会出现在JSP中。如下,其中expr是一个合法的表达式:
#
在JSF中,延迟执行表达式将在页面渲染或者回传到页面时执行,或者同时在两个阶段内执行。在JSP中,#{}延迟执行语法只是一个有效的JSP标签特性,用于将EL表达式的执行推迟到标签的渲染过程中。不同于在特性值绑定到标签之前执行EL表达式的方式,该标签的特性将获得一个对未执行EL表达式的引用。该标签可以在之后一个合适的时间,调用一个方法来执行EL表达式。
EL可以直接用在JSP的任何位置,除了少数例外情况。首先,EL表达式不能用在任何指令中。在编译JSP时,指令(<%@ page %>、<%@ include %>和<%@ taglib %>)将会被执行,但EL表达式是在稍后渲染JSP时执行,所以在其中添加EL表达式是无法正常工作的。另外,JSP声明(<%! %>)、脚本(<% %>)或者表达式(<%= %>)中的EL表达式也是无效的。除此之外,EL表达式可以添加到其他任何位置。一种常见的情况是将EL表达式添加到输出到屏幕的简单文本中。:
The user will see ${expr} text and will know that ${expr} is good.
当表达式执行时,结果会内嵌在文本中显示到屏幕。
另外,表达式还可以用在标准的HTML标签特性中:
<input type="text" name="something" value="${expr}" />
表达式也可以使用在JSP标签特性中:
<c:url value="/something/${expr}/${expr}" />
JSP引擎不会解析这些HTML 特性中的内容,它会将其中的内容当作普通文本输出到响应中,所以可以在引用或者文本形式中包含EL表达式。
EL语法是弱类型,并且它包含许多内建的隐式类型转换。表达式主要的规则是执行后要产生某个值。不能在表达式中声明变量、执行赋值语句或者不产生结果的操作。
EL中的保留关键字:
true、false、null、instanceof、empty、div、mod、and、or、not、eq、ne、lt、gt、le、ge
举例:
关键字empty用于验证某些集合、Map或者数组是否含有值,或者某些字符串是否含有一个或多个字符。
$
eq、ne、lt、gt、le和ge运算符分别是Java关系运算符==,!=,<,>,<=和>=,当然你仍然可以使用传统的关系运算符。
EL表达式中第一个执行的操作符是括号[]和点(.)解析操作符。如:
${mycollection["key"].memberName["anotherKey"]}
引擎首先将解析对象myCollection中映射到key的值。然后在该值中解析memberName方法、字段或者属性。最后在该方法、字段或者属性中再解析anotherKey所对应的值。在这些操作符都执行之后,下面开始解析分组圆括号操作符()。
EL引擎执行的最后一个操作符是分号(😉。该操作看上去与C中的逗号(,)相像,它允许在表达式中同时使用多个表达式,但只有最后一个表达式的值会被保留下来。如:
${x = y + 3; obj.callMethod(x); 'hello, world'}
该表达式最后执行字符串字面量"hello, world"。该表达式的最终结果是最后一个分号之后的表达式"hello, world"。
EL中字符串字面量既可以使用双引号也可以使用单引号。
在EL表达式中,只允许使用十进制字面量,对于其他类型的字面量,EL表达式中没有对应的用法。
无论何时需要,都可以直接在EL表达式中创建集合。
字面量集合中的元素以逗号分隔开。
{1,2,'three',4.00,x}
其中x可以是任何数据类型。
构造列表的方法与构造集合的方法基本一致,区别在于列表使用的是方括号,而集合使用的是花括号。
[1,2,'three',[x,y],{'foo','bar'}]
列表中的元素将以逗号分隔。
HashMap<Object,Object>集合字面量:
{'one':1,2:'two','key':x,'list':[1,2,3]}
EL除了使用公共访问方法访问属性的标准语法,还提供了访问JavaBean中属性的简化语法。假设现在有一个名为Shirt的类,它包含一个公开字段size。现有一个名为shirt的变量,使用EL访问size字段:
${shirt.size}
当使用这种方法时,EL引擎看到该语法,它将寻找shirt中的属性而不是字段。所以需要对Shirt类进行修改,使用标准JavaBean访问和设置方法getSize和setSize将size封装为私有字段。表达式shirt.size就变成了shirt.getSize()的快捷方式。
还可以使用[]操作符访问属性:
${shirt["size"]}
在EL早期版本,只可以访问JavaBean属性,不可以调用对象方法,不过在EL2.1添加了在JSP中调用对象方法的能力。因此,可以通过${shirt.getSize()}访问Shirt的size属性。
在EL中,函数是映射到类中静态方法的一个特殊工具。函数调用的语法如下所示:[ns]是命名空间,[fn]是函数名,从[a1]到[an]都是参数
${[ns]:[fn]([a1[,a2[,a3[,...]]]])}
JSTL函数库的命名空间为fn;不过也可以在taglib指令中使用任何其他命名空间。
与Java访问静态字段和方法的方式相同:在EL中使用完全限定的类名,接着是点操作符,再接着是字段或者方法名。如:
${java.lang.Integer.MAX_VALUE}
除非使用的类已经使用JSP page指令导入,否则必须使用完全限定的类型。在JSP中,如同Java一样,所有在java.lang包中的类都已经被隐式地导入。
在大多数情况下,lambda表达式是一个参数名字的列表,紧接着是某种类型的操作符,最后是函数体。EL中的lambda表达式语法几乎与Java8中的一样。
EL的lambda表达式体中包含的则是另一个EL表达式。
访问Map值的方式:
${map["username"]}
${map.username}
列表访问方式:
${list[0]}
${list{'0'}}
empty操作符使用:
${empty set}