前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JavaWeb高级编程(上)

JavaWeb高级编程(上)

作者头像
范中豪
发布2020-02-18 14:02:23
1.4K0
发布2020-02-18 14:02:23
举报
文章被收录于专栏:CV学习史CV学习史

好久没更新了,发一篇以前记录学习的笔记。 面向读者:已经具有丰富的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的另一处改进就是可以同时捕捉多个异常,条件是异常之间不能有继承关系。
  2. 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进行通信。

使用IDEA创建具有Maven依赖的Webapp方法

创建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:

代码语言:javascript
复制
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”。

初始化和销毁方法

代码语言:javascript
复制
@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中添加部署描述:

代码语言:javascript
复制
<?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应用启动之后立即启动。

代码语言:javascript
复制
<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做出响应:

代码语言:javascript
复制
<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的声明上进行注解,如:

代码语言:javascript
复制
@WebServlet{
    name = "helloServlet",
    urlPatterns = {"/greeting","/wazzup"},
    loadOnStartup = 1
}
public class HelloServlet extends HttpServlet
{
...
}

如上所示,可以代替编写对用的XML。

使用初始化参数配置应用程序 虽然在Servlet类上的注解代替了在部署描述符文件中的Servlet声明和映射,但是存在其它的一些配置必须通过部署描述符才能完成。上下文初始化参数就是其中之一。在web.xml文件中使用<context-param》标签声明上下文初始化参数。

声明初始化参数举例:

代码语言:javascript
复制
<context-param>
    <param-name>setting</param-name>
    <param-value>value</param-value>
</context-param>

在Servlet代码的任何地方都能够轻松获得和使用初始化参数,使用代码举例:

代码语言:javascript
复制
ServletContext sc = this.getServletContext();
sc.getInitParameter("setting");

或者: 定义:

代码语言:javascript
复制
<servlet>
    <servlet-name>...</servlet-name>
    <servlet-class>...</servlet-class>
    <init-param>
        <param-name>setting</param-name>
        <param-value>hello</param-value>
    </init-param>
</servlet>

使用:

代码语言:javascript
复制
ServletConfig sc = this.getServletConfig();
sc.getInitParameter("setting");

Web应用程序是天然的多线程应用程序。Web容器中通常会包含某种类型的线程池,它们被称为连接池或执行池。当容器收到请求时,它将在池中寻找可用的线程。如果找不到可用的线程,并且线程池已经达到了最大线程数,那么该请求将被放入一个队列等待获得可用的线程。

使用JSP显示页面内容

JSP是一个混合解决方案,它结合了Java代码和HTML标签。JSP可以包含除了Java代码之外的任何HTML标签、内建JSP标签、自定义JSP标签以及表达式语言。 JSP中的指令标签:

代码语言:javascript
复制
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

该指令标签提供了对JSP如何转换、渲染和传输的控制。 指令标签中:特性language将告诉容器JSP中使用的是哪种脚本语言。contentType告诉容器在发送响应时如何设置其中Content-Type头的值。Content-Type头同时包含内容类型和字符编码,以分号隔开。 JSP中除了各种不同的HTML和JSP标签,还有几种独特的结构用于JSP中:

  1. 使用指令
代码语言:javascript
复制
<%@ 指令 %>

指令用于指示JSP解释器执行某个操作或者对文件做出假设、导入类、在转换时包含其他JSP或者包含JSP标签库。

  1. 使用声明
代码语言:javascript
复制
<%! 声明 %>

用于在JSP Servlet类的范围内声明一些东西,例如定义实例变量、方法或声明标签中的类。这些声明都将自动出现在自动生成的JSP Servlet类中,所以声明中定义的类实际上是JSP Servlet类的内部类。

  1. 使用脚本
代码语言:javascript
复制
<% 脚本 %>
  1. 使用表达式
代码语言:javascript
复制
<%= 表达式 %>

无论何时在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正在处理的一些请求转发至其他JSP。 eg:<jsp:forward page="/path/to/some/file.jsp"

使用该标签时,在该标签之前生成的任何响应内容仍然会被发送到客户端浏览器中。任何在此标签之后的代码都将被忽略。

标签<jsp:useBean>在页面中声明一个JavaBean,标签<jsp:getProperty>将从使用声明的bean中获取属性值,标签<jsp:setProperty>将用于设置该实例的属性,标签<jsp:plugin>用于在HTML页面中内嵌Java Applet。

WEB-INF目录中的文件都是禁止通过web访问的。可以将JSP文件添加到该目录中以防止用户通过浏览器访问这些JSP。

使用请求派发器将Servlet中的请求转发给JSP

代码语言:javascript
复制
private void funName(HttpServletRequest re,HttpServletREsponse res)
    throws ServletException, IOException
{
    re.getRequestDispatcher("/WEB-INF/jsp/view/ticketForm.jsp")
        .forward(re,res);
}

使用会话维持状态

HTTP请求自身是完全无状态的。从服务器的角度来说,当用户的Web浏览器打开第一个连接到服务器的套接字时请求就开始了,直到服务器返回最后一个数据包关闭连接时,该请求结束。此时,在用户的浏览器和服务器之间不再有任何联系,当下一个连接开始时,无法将新的请求与之前的请求关联起来。 使用会话可以:

  1. 维持请求和请求之间的状态
  2. 记住用户
  3. 启动应用程序的工作流

在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重定向。

会话是存在漏洞的,在执行重要任务、含有敏感数据的应用程序中,使用某些商业的扫描器检测应用程序中的漏洞是更加明智的选择。 会话漏洞及解决方案:

  1. 复制并粘贴错误 漏洞:用户复制并粘贴地址栏中的URL 方案:完全禁止在URL中内嵌会话ID
  2. 会话固定: 攻击者可能会首先找到一些允许在URL中内嵌会话ID的网站。通过此种方式获得一个会话,然后将含有会话ID的URL发送给目标用户,此时,如果用户点击链接进入网站,它的会话ID就变成了URL中含有的固定ID--攻击者已经持有该ID。如果用户接着在该会话期间登录网站,那么攻击者也可以登录成功,因为这个会话ID是他共享的。 解决方案:
    1. 禁止在URL中内嵌会话ID
    2. 在登录后采用会话迁移
  3. 跨站脚本和会话劫持 漏洞:使用JavaScript读取会话cookie的内容 解决方案:
    1. 不要在网站中使用跨站脚本
    2. 在所有的cookie中使用HttpOnly特性
  4. 不安全的cookie 中间人攻击(MitM攻击),典型的如:数据截获攻击 解决方案:使用HTTPS保护网络通信将有效地防止MitM攻击,并保护会话ID cookie不被盗用。不过用户可能首先使用HTTP访问网站,即使立刻将请求进行重定向到HTTPS,攻击可能已经发生了。使用cookie的Secure标志可以解决此问题。

在许多情况下,都可以在Java EE中直接使用HTTP会话,不需要显式的配置,不过可以在部署描述符中配置它们,并且处于安全的目的也应该配置。在部署描述符中使用标签配置会话。 所有在和中的标签都是可选的,但是如果选择了这些标签就要按照如下顺序添加到部署描述符中:

代码语言:javascript
复制
<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表达式:

${expr}

美元符号和开始/结束花括号定义了EL表达式的边界。 2. 延迟执行 延迟执行EL表达式是统一表达式的一部分,主要用于满足JavaServer Faces的需要。尽管延迟执行语法在JSP中是合法的,但通常不会出现在JSP中。如下,其中expr是一个合法的表达式:

#{expr}

在JSF中,延迟执行表达式将在页面渲染或者回传到页面时执行,或者同时在两个阶段内执行。在JSP中,#{}延迟执行语法只是一个有效的JSP标签特性,用于将EL表达式的执行推迟到标签的渲染过程中。不同于在特性值绑定到标签之前执行EL表达式的方式,该标签的特性将获得一个对未执行EL表达式的引用。该标签可以在之后一个合适的时间,调用一个方法来执行EL表达式。

EL可以直接用在JSP的任何位置,除了少数例外情况。首先,EL表达式不能用在任何指令中。在编译JSP时,指令(<%@ page %>、<%@ include %>和<%@ taglib %>)将会被执行,但EL表达式是在稍后渲染JSP时执行,所以在其中添加EL表达式是无法正常工作的。另外,JSP声明(<%! %>)、脚本(<% %>)或者表达式(<%= %>)中的EL表达式也是无效的。除此之外,EL表达式可以添加到其他任何位置。一种常见的情况是将EL表达式添加到输出到屏幕的简单文本中。:

代码语言:javascript
复制
The user will see ${expr} text and will know that ${expr} is good.  

当表达式执行时,结果会内嵌在文本中显示到屏幕。 另外,表达式还可以用在标准的HTML标签特性中:

代码语言:javascript
复制
<input type="text" name="something" value="${expr}" />

表达式也可以使用在JSP标签特性中:

代码语言:javascript
复制
<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或者数组是否含有值,或者某些字符串是否含有一个或多个字符。

${empty x}

eq、ne、lt、gt、le和ge运算符分别是Java关系运算符==,!=,<,>,<=和>=,当然你仍然可以使用传统的关系运算符。

EL表达式中第一个执行的操作符是括号[]和点(.)解析操作符。如:

代码语言:javascript
复制
${mycollection["key"].memberName["anotherKey"]}

引擎首先将解析对象myCollection中映射到key的值。然后在该值中解析memberName方法、字段或者属性。最后在该方法、字段或者属性中再解析anotherKey所对应的值。在这些操作符都执行之后,下面开始解析分组圆括号操作符()。 EL引擎执行的最后一个操作符是分号(;)。该操作看上去与C中的逗号(,)相像,它允许在表达式中同时使用多个表达式,但只有最后一个表达式的值会被保留下来。如:

代码语言:javascript
复制
${x = y + 3; obj.callMethod(x); 'hello, world'}

该表达式最后执行字符串字面量"hello, world"。该表达式的最终结果是最后一个分号之后的表达式"hello, world"。 EL中字符串字面量既可以使用双引号也可以使用单引号。 在EL表达式中,只允许使用十进制字面量,对于其他类型的字面量,EL表达式中没有对应的用法。 无论何时需要,都可以直接在EL表达式中创建集合。 字面量集合中的元素以逗号分隔开。

代码语言:javascript
复制
{1,2,'three',4.00,x}

其中x可以是任何数据类型。 构造列表的方法与构造集合的方法基本一致,区别在于列表使用的是方括号,而集合使用的是花括号。

代码语言:javascript
复制
[1,2,'three',[x,y],{'foo','bar'}]

列表中的元素将以逗号分隔。 HashMap<Object,Object>集合字面量:

代码语言:javascript
复制
{'one':1,2:'two','key':x,'list':[1,2,3]}

EL除了使用公共访问方法访问属性的标准语法,还提供了访问JavaBean中属性的简化语法。假设现在有一个名为Shirt的类,它包含一个公开字段size。现有一个名为shirt的变量,使用EL访问size字段:

代码语言:javascript
复制
${shirt.size}

当使用这种方法时,EL引擎看到该语法,它将寻找shirt中的属性而不是字段。所以需要对Shirt类进行修改,使用标准JavaBean访问和设置方法getSize和setSize将size封装为私有字段。表达式shirt.size就变成了shirt.getSize()的快捷方式。 还可以使用[]操作符访问属性:

代码语言:javascript
复制
${shirt["size"]}

在EL早期版本,只可以访问JavaBean属性,不可以调用对象方法,不过在EL2.1添加了在JSP中调用对象方法的能力。因此,可以通过${shirt.getSize()}访问Shirt的size属性。 在EL中,函数是映射到类中静态方法的一个特殊工具。函数调用的语法如下所示:[ns]是命名空间,[fn]是函数名,从[a1]到[an]都是参数

代码语言:javascript
复制
${[ns]:[fn]([a1[,a2[,a3[,...]]]])}

JSTL函数库的命名空间为fn;不过也可以在taglib指令中使用任何其他命名空间。

与Java访问静态字段和方法的方式相同:在EL中使用完全限定的类名,接着是点操作符,再接着是字段或者方法名。如:

代码语言:javascript
复制
${java.lang.Integer.MAX_VALUE}

除非使用的类已经使用JSP page指令导入,否则必须使用完全限定的类型。在JSP中,如同Java一样,所有在java.lang包中的类都已经被隐式地导入。 在大多数情况下,lambda表达式是一个参数名字的列表,紧接着是某种类型的操作符,最后是函数体。EL中的lambda表达式语法几乎与Java8中的一样。 EL的lambda表达式体中包含的则是另一个EL表达式。 访问Map值的方式:

代码语言:javascript
复制
${map["username"]}
${map.username}

列表访问方式:

代码语言:javascript
复制
${list[0]}
${list{'0'}}

empty操作符使用:

代码语言:javascript
复制
${empty set}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-02-03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Java EE平台介绍
    • 平台新特性
    • 创建Servlet
    • 初始化和销毁方法
    • 配置可部署的Servlet
      • 向描述符中添加Servlet
      • 了解doGet、doPost和其他方法
      • 使用JSP显示页面内容
      • 使用会话维持状态
      • 在JSP中使用表达式语言
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档