EL&JSTL&文件上传下载

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 方法:

JsB7od.png

在 jsp 页面的代码脚本中创建对象及其赋一些值:

JsBvy8.png

使用 EL 表达式获取此对象的属性:

${域对象的key.属性[下标]|.map的key

JsDElV.png

目前以上输出的都是类对象中存在的属性并且有 get 方法的,若访问不存在的属性或者存在属性但是没有对应的 get 方法的属性:

JsrZ9I.png

修改 JavaBean 类,不添加新的属性但是添加一个 getAge 方法:

JsrBE4.png

在 EL 表达式中输出 age 属性:

JsrRKK.png

就会发现 EL 表达式可以获取 JavaBean 中没有对应的属性但是存在对应的 get 方法的属性(一般没人会闲的蛋疼平白无故的增加这种 get 方法)

EL 表达式运算

关系运算

JsyN0U.png

逻辑运算

Js6dDf.png

算术运算

Js6gvq.png

empty 运算

empty 运算可以判断一个数据是否为空,如果为空,则输出 true,不为空输出 false

  1. 值为 null
  2. 字符串为空串 ""
  3. 数组长度为 0
  4. List 集合长度为 0
  5. Map 集合长度为 0

JsgDpj.png

三元运算

表达式1?表达式2:表达式3

. 点运算和 [] 中括号运算

. 点运算,可以输出 Bean 对象中某个属性的值
[] 中括号运算,可以输出有序集合中某个元素的值
并且 [] 中括号运算,还可以输出 map 集合中 key 里含有特殊字符的 key 的值

在 jsp 页面中:

JsRPz9.png

浏览器输出效果:

JsRhl9.png

EL 表达式的 11 个隐含对象

EL 表达式中 11 个隐含对象,是 EL 表达式中自己定义的,可以直接使用

  1. PageContextImpl pageContext
    它可以获取 jsp 中的九大内置对象
  2. Map<String, Object> pageScope
    它可以获取 pageContext 域中的数据
  3. Map<String, Objcet> requestScope
    它可以获取 Request 域中的数据
  4. Map<String, Object> sessionScope
    它可以获取 Session 域中的数据
  5. Map<String, Object> applicationScope
    它可以获取 ServletContext 域中的数据
  6. Map<String, String> param
    它可以获取请求参数的值
  7. Map<String, String[]> paramValues
    它也是用来获取请求参数,常用来获取多个值
  8. Map<String, String> header
    它可以获取请求头的信息
  9. Map<String, String[]> headerValues
    它也是用来获取请求头参数信息的,也是常用来获取多个值
  10. Map<String, Cookie> cookie
    它可以获取当前请求的 Cookie 信息
  11. Map<String, String> initParam
    它可以获取在 web.xml 中配置的 标签中的上下文参数

四个域对象

JsbpvT.png

浏览器显示效果:

JsbPrF.png

pageContext 对象

Jsjtu6.png

浏览器显示:

JsjDCd.png

param对象

param 对象主要用来获取地址栏中的请求参数的值

JyC2Se.png

在浏览器地址栏后添加任意请求参数,浏览器显示:

JyCNz4.png

header对象

header 对象主要用来获取请求头中的参数信息

Jyigrd.png

浏览器显示:

JyFCM4.png

cookie 对象主要用来获取当前请求的 Cookie 信息

JyFIT1.png

JyF7Y6.png

initParam 对象

initParam 对象主要用来获取 web.xml 文件中的 标签中的信息

在 web.xml 文件中添加几个键值对:

JykQpT.png

JyApDJ.png

浏览器显示:

JyAiU1.png

JSTL

STL 标签库 全称是指 JSP Standard Tag Library JSP 标准标签库。是一个不断完善的开放源代码的 JSP 标签库。
EL 表达式主要是为了替换 jsp 中的表达式脚本,而标签库则是为了替换代码脚本。这样使得整个 jsp 页面变得更佳简洁。
JSTL 由五个不同功能的标签库组成

JyRBuR.png

在 jsp 标签库中使用 taglib 指令引入标签库

  1. CORE 标签库
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
  2. FMT 标签库
    <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
  3. FUNCTION 标签库
    <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
  4. SQL 标签库
    <%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %>
  5. 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

Jyhrbn.png

浏览器显示:

JyhW2F.png

if标签

格式:<c:if test="${EL表达式}">表达式为真执行中间的语句</c:if>

Jy5EY6.png

浏览器显示:

Jy5mlD.png

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 标签的注意事项:

  1. 不能在标签里使用 html 注释,使用注释需要使用 jsp 注释
  2. otherwise 标签里不能使用 when 标签,when 标签的父标签只能是 choose
    若需要在 otherwise 标签下再次进行多路判断,就得在写一个 choose 标签

JyHNee.png

若有些小朋友就是很顽皮,非要在 otherwise 里直接使用 when 标签将会出现:

JyHTyT.png

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 标签遍历数字

JyxeUO.png

使用 forEach 标签遍历数组

Jyx1KI.png

使用 forEach 标签遍历 Map 集合

JyxUPg.png

使用 forEach 标签遍历 List 集合

Jyxrq0.png

页面最终输出:

Jyx4MR.png

forEach 标签的各个属性

<c:forEach items="" var="" step="" end="" begin="" varStatus="">
</c:forEach>
  • items:表示遍历的集合
  • var:表示遍历到的数据
  • step:表示遍历时的步长值
  • end:表示遍历结束的索引值
  • begin:表示遍历开始的索引值
  • varStatus:表示当前遍历到的数据的状态

说起 varStatus 现在页面上输出下看一下是个什么东西:

J6pqiT.png

javax.servlet.jsp.jstl.core.LoopTagSupport 是一个类 而且 1Status 还是其的内部类,如果想知道其是什么东西,就需要去看一下 jstl jar包的源码:

J69lY8.png

J69vh8.png

从图中可以看出 LoopTagSupport 抽象类中还有一个内部类 Status 并且其还实现了 LoopTagStatus 接口,继续看以下 LoopTagStatus 接口,看一下各个方法实现了什么样的功能:

J6CH8U.png

文件的上传和下载

文件的上传和下载,是非常常见的功能。很多的系统中,或者软件中都经常使用文件的上传和下载

文件的上传

  1. 要有一个form标签,method=post 请求
  2. form 标签的 enctype 属性值必须为 multipart/form-data 值
  3. 在 form 标签中使用 input type=file 添加上传的文件
  4. 编写服务器代码(Servlet程序)接收,处理上传的数据

enctype=multipart/form-data 表示提交的数据,以多段(每一个表单项一个数据段)的形式进行拼接,然后以二进制流的形式发送给服务器

J6kHoR.png

我们在 Servlet 程序中不能像之前使用 getParameter 方法直接获取表单项中的数据,而是需要通过 getInputStream 方法获取到请求体的流

J6ZbnO.png

然后发现终端打印输出:

J6Zv4A.png

一般在浏览器中不会显示文件上传流的数据,在服务器端打印输出就会发现这些数据非常大而乱,不易于人们观察调试

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();
        }
    }
}

在网页中输入如下数据:

Jcc2gH.png

提交后,服务器终端输出:

JcggoV.png

查看当前 web 工程的部署目录 / :

JcgvSe.png

文件的下载

大体流程:

JcWCPf.png

代码示例:

@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 将会在浏览器直接呈现图片而不是下载:

JcTktA.png

再次将代码取消注释,再次访问 Servlet 程序:

JcTJ10.png

就会发现浏览器已经以下载的形式接收到服务器发送过来的文件。

如果将文件名修改为带有中文字符,默认在浏览器中下载的时候是不会得到正确的文件名的:

JgfrRK.png

下载的文件名:

Jgf6MD.png

查看响应头的数据:

JgfCVA.png

若想接受带有中文字符的文件名,需要在服务器端对文件名进行 URL 编码:

JghSzT.png

再次打开浏览器:

JghAoR.png

查看响应头的数据:

Jghskn.png

使用 URL 编码可以解决谷歌浏览器和 IE 浏览器的编码问题,但是遇到火狐浏览器后:

Jg49ht.png

原因是火狐浏览器使用的 Base64 的编码集,关于 Base64 的编码和解码:

JgInS0.png

但是想在服务器端不能单纯的将文件名的字符串修改为对应的 Base64 编码的字符串,而是有特定的格式:

请求头:Content-Disposition: attachment; filename==?charset?B?xxxxxx?=
=?charset?B?xxxxxx?=:
=?          表示编码内容的开始
charset     表示字符集编码
B           表示 Base64 编码
xxxxxx      表示文件名的 Base64 编码的字符串
?=          表示编码内容的结束

JgonNd.png

这样就解决了火狐浏览器中文字符的问题。

但是反过头来再使用 IE 浏览器使用下载功能时,就会发现 IE 浏览器出现了乱码

Jgou4A.png

若使用谷歌浏览器发现不会出现乱码问题,这就说明谷歌浏览器不仅支持 URL 编码而且还支持 Base64 编码

可以通过判断请求头中的 User-Agent 的值来确定请求使用的浏览器是不是火狐:

Jg7ZYd.png

不一定每天 code well 但要每天 live well
原文地址:https://www.cnblogs.com/geekfx/p/12790774.html