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 對象主要用來獲取當前請求的 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 瀏覽器出現了亂碼

在這裏插入圖片描述

若使用谷歌瀏覽器發現不會出現亂碼問題,這就說明谷歌瀏覽器不僅支持 URL 編碼而且還支持 Base64 編碼

可以通過判斷請求頭中的 User-Agent 的值來確定請求使用的瀏覽器是不是火狐:

Jg7ZYd.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章