JSP 表達式語言 (EL) 完整指南
EL (Expression Language) 簡化 JSP 中資料存取與邏輯運算,取代繁瑣的 Scriptlet,支援算術、邏輯、關係運算與隱含物件存取,是現代 JSP 開發核心技能。
EL 基本語法
標準語法
${運算式}
屬性值應用對比
<!-- 傳統方式:字串常值 -->
<jsp:setProperty name="box" property="perimeter" value="100"/>
<!-- EL 方式:動態運算式 -->
<jsp:setProperty name="box" property="perimeter" value="${2*box.width + 2*box.height}"/>
模板文字中使用
<jsp:text>
盒子周長:${2*box.width + 2*box.height}
</jsp:text>
括號控制優先序
${(1 + 2) * 3} <!-- 等於 9 -->
${1 + (2 * 3)} <!-- 等於 7 -->
停用 EL 評估
<%@ page isELIgnored="true" %> <!-- 頁面層級停用 -->
<%@ page isELIgnored="false" %> <!-- 頁面層級啟用 (預設) -->
EL 運算子完整對照表
| 分類 | 運算子 | 別名 | 說明 |
| 存取 | . | - | Bean 屬性或 Map 鍵值存取 |
| [] | - | 陣列/List 索引、Map 鍵值 (含特殊字元) |
| 分組 | () | - | 子運算式分組,改變優先序 |
| 算術 | + | - | 加法 |
| - | - | 減法/負號 |
| * | - | 乘法 |
| / | div | 除法 |
| % | mod | 取餘數 |
| 關係 | == | eq | 等於 |
| != | ne | 不等於 |
| < | lt | 小於 |
| > | gt | 大於 |
| <= | le | 小於等於 |
| >= | ge | 大於等於 |
| 邏輯 | && | and | 邏輯與 |
| || | or | 邏輯或 |
| ! | not | 邏輯非 |
| 空值檢查 | empty | - | null、空字串、空陣列、空集合、空 Map 皆為 true |
empty 運算子判斷規則
| 值 | empty 結果 |
| null | true |
| "" | true |
| new int[0] | true |
| new ArrayList() | true |
| new HashMap() | true |
| "text" | false |
| 0 | false |
| false | false |
EL 函數機制
語法
${前綴:函數名(參數1, 參數2, ...)}
JSTL fn 函數庫常用函數
<%@ taglib prefix="fn" uri="https://java.sun.com/jsp/jstl/functions" %>
| 函數 | 說明 | 範例 |
| fn:length() | 字串長度/集合大小 | ${fn:length("Hello")} → 5 |
| fn:toUpperCase() | 轉大寫 | ${fn:toUpperCase(name)} |
| fn:toLowerCase() | 轉小寫 | ${fn:toLowerCase(name)} |
| fn:substring() | 子字串 | ${fn:substring(str, 0, 5)} |
| fn:substringAfter() | 指定字串後內容 | ${fn:substringAfter(url, "://")} |
| fn:substringBefore() | 指定字串前內容 | ${fn:substringBefore(email, "@")} |
| fn:trim() | 去除首尾空白 | ${fn:trim(input)} |
| fn:replace() | 字串取代 | ${fn:replace(text, "old", "new")} |
| fn:split() | 字串分割為陣列 | ${fn:split(csv, ",")} |
| fn:join() | 陣列合併為字串 | ${fn:join(arr, ", ")} |
| fn:contains() | 包含子字串 | ${fn:contains(text, "key")} |
| fn:containsIgnoreCase() | 不區分大小寫包含 | ${fn:containsIgnoreCase(text, "KEY")} |
| fn:startsWith() | 開頭比對 | ${fn:startsWith(url, "https")} |
| fn:endsWith() | 結尾比對 | ${fn:endsWith(file, ".jpg")} |
| fn:indexOf() | 子字串索引 | ${fn:indexOf(str, "target")} |
| fn:escapeXml() | XML/HTML 轉義 | ${fn:escapeXml(userInput)} |
自訂 EL 函數
- 建立公靜態方法類別
- 編寫 TLD 檔案定義函數
- JSP 引入 taglib 使用
EL 隱含物件完整對照
| 隱含物件 | 型別 | 對應 API | 用途 |
| pageScope | Map<String,Object> | pageContext.getAttribute(name, PageContext.PAGE_SCOPE) | 頁面作用域屬性 |
| requestScope | Map<String,Object> | request.getAttribute(name) | 請求作用域屬性 |
| sessionScope | Map<String,Object> | session.getAttribute(name) | Session 作用域屬性 |
| applicationScope | Map<String,Object> | application.getAttribute(name) | 應用程式作用域屬性 |
| param | Map<String,String> | request.getParameter(name) | 單一請求參數 |
| paramValues | Map<String,String[]> | request.getParameterValues(name) | 多值請求參數 |
| header | Map<String,String> | request.getHeader(name) | 單一 HTTP 標頭 |
| headerValues | Map<String,String[]> | request.getHeaders(name) | 多值 HTTP 標頭 |
| initParam | Map<String,String> | application.getInitParameter(name) | Context 參數 (web.xml) |
| cookie | Map<String,Cookie> | request.getCookies() 查找 | Cookie 物件 |
| pageContext | PageContext | pageContext | 頁面上下文,可存取 request、session 等 |
實戰範例
存取請求參數
<%@ page import="java.io.*,java.util.*" %>
<html><body>
<p>username 參數:${param.username}</p>
<p>username (括號寫法):${param["username"]}</p>
<p>所有 username 值:${paramValues.username}</p>
</body></html>
存取 HTTP 標頭
<html><body>
<p>User-Agent:${header["user-agent"]}</p>
<p>Accept-Language:${header["accept-language"]}</p>
<p>所有 Accept 標頭:${headerValues.accept}</p>
</body></html>

作用域屬性存取
<!-- 明確指定作用域 -->
${applicationScope.configValue}
${sessionScope.userName}
${requestScope.errorMessage}
${pageScope.tempValue}
<!-- 不指定作用域:依序搜尋 page→request→session→application -->
${configValue}
pageContext 進階存取
<p>查詢字串:${pageContext.request.queryString}</p>
<p>請求方法:${pageContext.request.method}</p>
<p>Servlet 路徑:${pageContext.request.servletPath}</p>
<p>Session ID:${pageContext.session.id}</p>
<p>應用程式路徑:${pageContext.servletContext.contextPath}</p>
條件式顯示
<c:if test="${not empty param.search}">
搜尋關鍵字:${param.search}
</c:if>
<c:choose>
<c:when test="${user.role == 'admin'}">
<a href="/admin">管理後台</a>
</c:when>
<c:when test="${user.role == 'member'}">
<a href="/member">會員中心</a>
</c:when>
<c:otherwise>
<a href="/login">請登入</a>
</c:otherwise>
</c:choose>
EL vs Scriptlet 比較
| 特性 | EL (${...}) | Scriptlet (<% %>) |
| 可讀性 | 高,類似模板語法 | 低,混雜 Java 程式碼 |
| 型別安全 | 執行期動態解析 | 編譯期檢查 |
| Null 安全 | 自動處理 (empty 運算子) | 需手動檢查 |
| HTML 輸出 | 自動編碼 (搭配 JSTL) | 需手動 escape |
| 維護性 | 優,前端友善 | 差,邏輯與顯示耦合 |
| 效能 | 略低 (反射/解析) | 較高 (編譯為 Servlet) |
最佳實務建議
- 盡量使用 EL + JSTL:替代 Scriptlet,符合 MVC 分離原則
- 複雜邏輯移至 Java 類別:EL 僅做資料呈現與簡單判斷
- 明確指定作用域:避免同名屬性衝突,提升可讀性
- 善用 fn:escapeXml():防止 XSS,輸出使用者輸入內容時必加
- 自訂函數封裝業務邏輯:如格式化、驗證、權限判斷