JSP 提供兩種包含機制:靜態包含 (include 指令) 與 動態包含 (jsp:include 動作)。兩者在編譯時機、變量作用域、性能、適用場景上存在本質差異。本文通過代碼對比、字節碼視角、實戰案例徹底講透。
| 維度 | 靜態包含 <%@ include file="..." %> | 動態包含 <jsp:include page="..." /> |
|---|---|---|
| 處理階段 | 編譯期 (JSP → Servlet 合並為一個 .java) | 運行期 (請求轉發到目標頁面,再合併響應) |
| 生成 Servlet | 單一 Servlet 類 | 主頁面 Servlet + 被包含頁面獨立 Servlet |
| 變量共享 | 完全共享 (同一 _jspService 方法內) | 隔離 (各自獨立 _jspService,通過 request 域傳遞) |
| 參數傳遞 | 不支持 | 支持 <jsp:param> |
| 文件變更生效 | 需重新編譯主頁面 | 即時生效 (目標頁面重新編譯即可) |
| 性能 | 極高 (無運行期開銷) | 略低 (RequestDispatcher.include 開銷) |
| 適用場景 | 頁面片段、公共頭尾、靜態模板 | 動態組件、獨立邏輯模塊、需參數化的片段 |
title
<%-- main.jsp --%>
<%@ page language="java" contentType="text/html; charset=UTF-8" %>
<%
String title = "靜態包含測試"; // 定義變量
%>
<%@ include file="header.jsp" %> <%-- 編譯期合併 --%>
<div>主頁內容</div>
<%-- header.jsp --%>
<h1><%= title %></h1> <%-- 直接訪問 main.jsp 定義的變量 --%>
結果:正常輸出「靜態包含測試」。
字節碼視角:header.jsp 內容被內聯到 main_jsp._jspService() 方法中,變量 title 為方法局部變量,自然可訪問。
<%-- main.jsp --%>
<%@ page language="java" contentType="text/html; charset=UTF-8" %>
<%
String title = "動態包含測試";
request.setAttribute("title", title); // 必須放入 request 域
%>
<jsp:include page="header.jsp" /> <%-- 運行期 include --%>
<div>主頁內容</div>
<%-- header.jsp --%>
<h1>${title}</h1> <%-- EL 從 requestScope 獲取 --%>
結果:正常輸出「動態包含測試」。
機制:main.jsp 編譯為 Servlet A,header.jsp 編譯為 Servlet B。運行時 A 調用 request.getRequestDispatcher("header.jsp").include(req, resp),B 從 request 域讀取屬性。
<jsp:include page="header.jsp">
<jsp:param name="title" value="參數化標題" />
</jsp:include>
被包含頁面可通過 ${param.title} 或 request.getParameter("title") 獲取。
| 坑位 | 現象 | 原因 | 解決 |
|---|---|---|---|
| 靜態包含重複聲明變量 | 編譯錯誤:duplicate local variable | 合併為同一方法,局部變量名衝突 | 約定變量命名規範;改用動態包含隔離作用域 |
| 動態包含無法讀取主頁局部變量 | EL 顯示空、腳本報 NullPointerException | 不同 Servlet,作用域隔離 | 主頁 request.setAttribute() 或 <jsp:param> 顯式傳遞 |
| 靜態包含修改文件不生效 | 修改 header.jsp,瀏覽器刷新無變化 | 主頁面 Servlet 未重新編譯 | 觸發主頁面重新編譯 (保存主頁、清理 work 目錄、重啟容器) |
| 包含循環 | StackOverflowError / 容器啟動失敗 | A include B, B include A | 架構設計禁止循環依賴 |
| 大量靜態包含導致類過大 | JSP 編譯超過 64KB 方法體限制 | 合併後 _jspService 方法超限 | 拆分為動態包含、或使用 Tag File、Fragment |
JSP 2.0+ 支持 .tag 文件,放置於 /WEB-INF/tags/,提供:
<%@ attribute %>);<%@ tag body-content="scriptless" %> 片段注入;<%@ taglib prefix="my" tagdir="/WEB-INF/tags" %> → <my:header title="xxx" />。推薦:複雜邏輯片段優先用 Tag File,兼具封裝性、類型安全、IDE 提示。
需要包含頁面片段
│
├─► 片段極簡、無邏輯、極少變更、追求極致性能
│ └─► 靜態包含 <%@ include %>
│
├─► 片段有獨立邏輯、需參數化、頻繁變更、需隔離作用域
│ └─► 動態包含 <jsp:include> + <jsp:param>
│
└─► 片段復用度高、需屬性驗證、支持片段注入、團隊協作
└─► Tag File (.tag) [最佳實踐]
理解編譯期合併 vs 運行期轉發是掌握 JSP 包含的鑰匙。靜態包含「快但耦合」,動態包含「柔但慢」,Tag File「強且雅」。根據片段複雜度、變更頻率、作用域隔離需求,選擇最合適的方案,避開變量衝突、修改不生效、類體積超限等經典坑位。