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 的值來確定請求使用的瀏覽器是不是火狐: