EL 表达式
EL 表达式的全称是:Expression Language 是表达式语言
EL 表达式主要是代替 jsp 页面中的表达式脚本在 jsp 页面中进行数据的输出。因为 EL 表达式在输出数据的时候,要比 jsp 的表达式脚本要简洁很多。
格式:${表达式}
EL 表达式在输出 null 值的时候,输出的是空串。jsp 表达式脚本输出 null 值的时候,输出的是 null 字符串。
<body>
<%
request.setAttribute("key", "value");
%>
使用表达式脚本获取 key 的值:<%=request.getAttribute("key")%> <br>
使用 EL 表达式获取 key 的值:${key} <br>
使用表达式脚本获取 key1 的值:<%=request.getAttribute("key1")%> <br>
使用 EL 表达式获取 key1 的值:${key1} <br>
</body>
网页输出效果:
使用表达式脚本获取 key 的值:value
使用 EL 表达式获取 key 的值:value
使用表达式脚本获取 key1 的值:null
使用 EL 表达式获取 key1 的值:
EL 表达式搜索域数据的顺序
EL 表达式主要是在 jsp 页面中输出数据
主要是输出域对象中的数据
当四个域中都有相同的 key 的数据的时候,EL 表达式会按照四个域的从小到大的顺序去进行搜索,找到就输出
EL 表达式输出复杂对象
首先创建一个JavaBean类,增加一些属性、构造器、get 和 set 方法、toString 方法:
在 jsp 页面的代码脚本中创建对象及其赋一些值:
使用 EL 表达式获取此对象的属性:
${域对象的key.属性[下标]|.map的key
目前以上输出的都是类对象中存在的属性并且有 get 方法的,若访问不存在的属性或者存在属性但是没有对应的 get 方法的属性:
修改 JavaBean 类,不添加新的属性但是添加一个 getAge 方法:
在 EL 表达式中输出 age 属性:
就会发现 EL 表达式可以获取 JavaBean 中没有对应的属性但是存在对应的 get 方法的属性(一般没人会闲的蛋疼平白无故的增加这种 get 方法)
EL 表达式运算
关系运算
逻辑运算
算术运算
empty 运算
empty 运算可以判断一个数据是否为空,如果为空,则输出 true,不为空输出 false
- 值为 null
- 字符串为空串 ""
- 数组长度为 0
- List 集合长度为 0
- Map 集合长度为 0
三元运算
表达式1?表达式2:表达式3
. 点运算和 [] 中括号运算
. 点运算,可以输出 Bean 对象中某个属性的值
[] 中括号运算,可以输出有序集合中某个元素的值
并且 [] 中括号运算,还可以输出 map 集合中 key 里含有特殊字符的 key 的值
在 jsp 页面中:
浏览器输出效果:
EL 表达式的 11 个隐含对象
EL 表达式中 11 个隐含对象,是 EL 表达式中自己定义的,可以直接使用
- PageContextImpl pageContext
它可以获取 jsp 中的九大内置对象 - Map<String, Object> pageScope
它可以获取 pageContext 域中的数据 - Map<String, Objcet> requestScope
它可以获取 Request 域中的数据 - Map<String, Object> sessionScope
它可以获取 Session 域中的数据 - Map<String, Object> applicationScope
它可以获取 ServletContext 域中的数据 - Map<String, String> param
它可以获取请求参数的值 - Map<String, String[]> paramValues
它也是用来获取请求参数,常用来获取多个值 - Map<String, String> header
它可以获取请求头的信息 - Map<String, String[]> headerValues
它也是用来获取请求头参数信息的,也是常用来获取多个值 - Map<String, Cookie> cookie
它可以获取当前请求的 Cookie 信息 - Map<String, String> initParam
它可以获取在 web.xml 中配置的标签中的上下文参数
四个域对象
浏览器显示效果:
pageContext 对象
浏览器显示:
param对象
param 对象主要用来获取地址栏中的请求参数的值
在浏览器地址栏后添加任意请求参数,浏览器显示:
header对象
header 对象主要用来获取请求头中的参数信息
浏览器显示:
cookie 对象
cookie 对象主要用来获取当前请求的 Cookie 信息
initParam 对象
initParam 对象主要用来获取 web.xml 文件中的
在 web.xml 文件中添加几个键值对:
浏览器显示:
JSTL
STL 标签库 全称是指 JSP Standard Tag Library JSP 标准标签库。是一个不断完善的开放源代码的 JSP 标签库。
EL 表达式主要是为了替换 jsp 中的表达式脚本,而标签库则是为了替换代码脚本。这样使得整个 jsp 页面变得更佳简洁。
JSTL 由五个不同功能的标签库组成
在 jsp 标签库中使用 taglib 指令引入标签库
- CORE 标签库
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
- FMT 标签库
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
- FUNCTION 标签库
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
- SQL 标签库
<%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %>
- XML 标签库
<%@ taglib prefix="xml" uri="http://java.sun.com/jsp/jstl/xml" %>
set 标签
回忆一下 jsp 中怎么设置域数据:域对象.setAttribute(key, value)
使用 set 标签实现往域中存放数据
格式:<c:set scope="xxx" var="key" value="value" />
scope:page(默认)、request、session、applicat
浏览器显示:
if标签
格式:<c:if test="${EL表达式}">表达式为真执行中间的语句</c:if>
浏览器显示:
choose-when-otherwise 标签
if 标签不能判断 else 的情况,若想实现多路判断,就是用 choose-when-otherwise 标签,此标签功能和 swith-case-default 功能一样
<c:choose>
<c:when test="${表达式1}">
执行语句1
</c:when>
<c:when test="${表达式2}">
执行语句2
</c:when>
...
<c:when test="${表达式n}">
执行语句n
</c:when>
<c:ohterwise>
default 语句
</c:otherwise>
</c:choose>
使用 choose-when-otherwise 标签的注意事项:
- 不能在标签里使用 html 注释,使用注释需要使用 jsp 注释
- otherwise 标签里不能使用 when 标签,when 标签的父标签只能是 choose
若需要在 otherwise 标签下再次进行多路判断,就得在写一个 choose 标签
若有些小朋友就是很顽皮,非要在 otherwise 里直接使用 when 标签将会出现:
forEach 标签
从字面意思也知道是用来实现循环遍历的
格式:
<%-- 相当于 for 循环:
for(int i = 1; i <= 10; i+=2)
--%>
<c:forEach var="i" begin="1" end="10" step="2">
${ i } <br/>
</c:forEach>
使用 forEach 标签遍历数字
使用 forEach 标签遍历数组
使用 forEach 标签遍历 Map 集合
使用 forEach 标签遍历 List 集合
页面最终输出:
forEach 标签的各个属性
<c:forEach items="" var="" step="" end="" begin="" varStatus="">
</c:forEach>
- items:表示遍历的集合
- var:表示遍历到的数据
- step:表示遍历时的步长值
- end:表示遍历结束的索引值
- begin:表示遍历开始的索引值
- varStatus:表示当前遍历到的数据的状态
说起 varStatus 现在页面上输出下看一下是个什么东西:
javax.servlet.jsp.jstl.core.LoopTagSupport 是一个类 而且 1Status 还是其的内部类,如果想知道其是什么东西,就需要去看一下 jstl jar包的源码:
从图中可以看出 LoopTagSupport 抽象类中还有一个内部类 Status 并且其还实现了 LoopTagStatus 接口,继续看以下 LoopTagStatus 接口,看一下各个方法实现了什么样的功能:
文件的上传和下载
文件的上传和下载,是非常常见的功能。很多的系统中,或者软件中都经常使用文件的上传和下载
文件的上传
- 要有一个form标签,method=post 请求
- form 标签的 enctype 属性值必须为 multipart/form-data 值
- 在 form 标签中使用 input type=file 添加上传的文件
- 编写服务器代码(Servlet程序)接收,处理上传的数据
enctype=multipart/form-data 表示提交的数据,以多段(每一个表单项一个数据段)的形式进行拼接,然后以二进制流的形式发送给服务器
我们在 Servlet 程序中不能像之前使用 getParameter 方法直接获取表单项中的数据,而是需要通过 getInputStream 方法获取到请求体的流
然后发现终端打印输出:
一般在浏览器中不会显示文件上传流的数据,在服务器端打印输出就会发现这些数据非常大而乱,不易于人们观察调试
commons-fileupload.jar 常用API 介绍说明
commons-fileupload.jar 需要依赖 commons-io.jar 这个包,所以两个包都要引入
ServletFileUpload 类
用于解析上传的数据
boolean ServletFileUpload.isMultipartContent(HttpServletRequest request)
判断当前上传的数据格式是否是多段的格式public List /* FileItem */ parseRequest(HttpServletRequest request)
解析上传的数据
FileItem 接口
表示每一个表单项
String getFieldName()
获取表单项的name属性值String getName()
获取上传的文件名String getString() | String getString(String encoding)
获取当前表单项的值boolean isFormField()
判断当前这个表单项,是否是普通的表单项。还是上传的文件类型
true 表示是普通类型的表单项
false 表示上传的文件类型void write(File file)
将上传的文件写到参数 file 所指向的硬盘位置
代码示例:
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 首先判断请求类型是否是多段上传流类型
if ( ServletFileUpload.isMultipartContent(req) ) {
// 是多段数据
// 创建 FileItemFactory 的工厂实现类 DiskFileItemFactory 类
DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
// 创建用于解析上传数据的工具类 ServletFileUpload 类
ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
try {
// 解析上传数据 得到每一个表单项
List<FileItem> list = servletFileUpload.parseRequest(req);
// 遍历每一个表单项
for (FileItem fileItem : list) {
// 判断当前表单项的类型是普通类型还是上传的文件
if(fileItem.isFormField()) {
// 普通类型 将内容输出到终端
// 获取当前表单项的 name 属性值
String fieldName = fileItem.getFieldName();
// 获取当前表单项的 value 属性值
String string = fileItem.getString("UTF-8");
System.out.println("name = " + fieldName + ", value = " + string);
} else {
// 上传文件类型 将文件保存到工程目录下
String fieldName = fileItem.getFieldName();
// 获取上传文件名
String fileName = fileItem.getName();
System.out.println( "上传文件名:" + fileName );
// 将上传的文件写出到当前工程路径
fileItem.write(new File(req.getServletContext().getRealPath("/") + fileName));
}
}
} catch (FileUploadException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
在网页中输入如下数据:
提交后,服务器终端输出:
查看当前 web 工程的部署目录 / :
文件的下载
大体流程:
代码示例:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 获取要下载的文件名
String fileName = "zy.bmp";
// 2. 读取要下载的文件内容
ServletContext servletContext = getServletContext();
/*
InputStream getResourceAsStream(String path)
此方法 / 开始的路径为:http://ip:port/资源路径 映射到 web 工程的 web 文件夹下
*/
InputStream resourceAsStream = servletContext.getResourceAsStream("/file/" + fileName);
// 3. 通过响应头告诉客户端回传的数据类型
/*
String getMimeType(String file)
此方法获得对应的文件的 MimeType 类型
*/
String mimeType = servletContext.getMimeType("/file/" + fileName);
resp.setContentType(mimeType);
// 4. 通过响应头告诉客户端接收到的数据用于下载(如果没有这一步回传的图片将在浏览器中显示而不是下载)
resp.setHeader("Content-Disposition", "attachment;fileName=" + fileName);
// 5. 获取响应输出流
ServletOutputStream outputStream = resp.getOutputStream();
// 6. 将输入流的数据全部复制到输出流
/*
使用 commons-io jar 包下有一个一个 IOUtils 的工具类,有一系列的 copy 方法实现数据的一次性复制
*/
IOUtils.copy(resourceAsStream, outputStream);
}
若将第 4 步的代码注释掉,打开浏览器访问 Servlet 将会在浏览器直接呈现图片而不是下载:
再次将代码取消注释,再次访问 Servlet 程序:
就会发现浏览器已经以下载的形式接收到服务器发送过来的文件。
如果将文件名修改为带有中文字符,默认在浏览器中下载的时候是不会得到正确的文件名的:
下载的文件名:
查看响应头的数据:
若想接受带有中文字符的文件名,需要在服务器端对文件名进行 URL 编码:
再次打开浏览器:
查看响应头的数据:
使用 URL 编码可以解决谷歌浏览器和 IE 浏览器的编码问题,但是遇到火狐浏览器后:
原因是火狐浏览器使用的 Base64 的编码集,关于 Base64 的编码和解码:
但是想在服务器端不能单纯的将文件名的字符串修改为对应的 Base64 编码的字符串,而是有特定的格式:
请求头:Content-Disposition: attachment; filename==?charset?B?xxxxxx?=
=?charset?B?xxxxxx?=:
=? 表示编码内容的开始
charset 表示字符集编码
B 表示 Base64 编码
xxxxxx 表示文件名的 Base64 编码的字符串
?= 表示编码内容的结束
这样就解决了火狐浏览器中文字符的问题。
但是反过头来再使用 IE 浏览器使用下载功能时,就会发现 IE 浏览器出现了乱码
若使用谷歌浏览器发现不会出现乱码问题,这就说明谷歌浏览器不仅支持 URL 编码而且还支持 Base64 编码
可以通过判断请求头中的 User-Agent 的值来确定请求使用的浏览器是不是火狐: