在大型 Web 應用中,頁首 (Header)、頁尾 (Footer)、導航欄、側邊欄等片段在多個頁面重複出現。若在每個 JSP/HTML 重複編寫,維護成本極高。**伺服器端包含 (Server-Side Includes, SSI)** 允許在頁面中嵌入 Servlet 輸出,實現頁面片段的模組化復用。本文詳細講解 Java SSI 的語法、配置、實戰範例與現代替代方案。
.shtml** (伺服器識別需解析 SSI 指令)。conf/web.xml 取消註解 SSIServlet 映射 *.shtml)。
<!--#servlet name="Servlet名稱" code="Servlet類別" codebase="類別路徑"
參數名1="參數值1" 參數名2="參數值2" ... -->
<!--#param name="參數名1" value="參數值1" -->
<!--#param name="參數名2" value="參數值2" -->
<!--#/servlet-->
name:在 web.xml 註冊的 Servlet 名稱。code:Servlet 完整類別名 (可選,若已在 web.xml 映射)。codebase:類別檔基礎路徑 (罕用)。param 標籤 (支援複雜值)。
import javax.servlet.*;
import java.io.*;
public class Header extends GenericServlet {
public void service(ServletRequest req, ServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8"); // 建議 UTF-8
PrintWriter out = resp.getWriter();
// 取得傳入參數 title
String title = "歡迎光臨"; // 預設值
String[] titles = req.getParameterValues("title");
if (titles != null && titles.length > 0) title = titles[0];
// 輸出頁首 HTML 片段
out.println("" + title + " ");
out.println("*** " + title + " ***
");
out.flush(); // SSI 片段輸出建議 flush,不 close (由容器管理)
}
public String getServletInfo() { return "Header Fragment Servlet"; }
public void init(ServletConfig cfg) throws ServletException { super.init(cfg); }
public void destroy() { super.destroy(); }
}
public class Footer extends GenericServlet {
public void service(ServletRequest req, ServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.println("
");
out.println("版權所有 © 2024 某某科技有限公司
");
out.flush();
}
// ... init/destroy/getServletInfo 同前
}
<servlet>
<servlet-name>Header</servlet-name>
<servlet-class>Header</servlet-class>
</servlet>
<servlet>
<servlet-name>Footer</servlet-name>
<servlet-class>Footer</servlet-class>
</servlet>
<!-- Together.shtml -->
<html>
<body>
<!--#servlet name="Header" title="首頁歡迎您" -->
<!--#/servlet-->
<div style="padding:20px;">
<h2>主內容區域</h2>
<p>這裡是頁面主體內容,可以是靜態 HTML 或動態生成。</p>
</div>
<!--#servlet name="Footer" -->
<!--#/servlet-->
</body>
</html>
SSI 檔案中可再包含其他 SSI,但需容器支援 (Tomcat 預設支援)。避免循環包含導致無限遞歸。
<!--#servlet name="UserPanel" userId="${param.userId}" -->
EL 表達式在 SSI 中**不一定支援**,建議在包含的 Servlet 中直接從 req.getParameter() 獲取原始請求參數 (SSI 包含時會傳遞原請求)。
servlet 標籤觸發一次 service() 呼叫,過多片段會增加請求處理開銷。PrintWriter 直接寫入回應流,**避免大量字串拼接**。<!--#include file="footer.html" --> (檔案包含),無 Servlet 開銷。所有參與 SSI 的 Servlet、.shtml 頁面、容器預設編碼**必須統一 (UTF-8)**,否則片段拼接會產生亂碼。
| 方案 | 優勢 | 適用場景 |
|---|---|---|
JSP 標籤檔 / 片段 (<jsp:include>, Tag File) | 原生 JSP 支援、EL/標籤庫整合、編譯期優化 | JSP 架構專案 |
| 模板引擎 (Thymeleaf, FreeMarker, Velocity) | 強大模板繼承、片段復用、邏輯分離、前端友好 | Spring MVC, 現代 Web 架構 |
| 前端組件化 (React, Vue, Web Components) | 瀏覽器端組合、獨立部署、狀態管理 | SPA, 微前端 |
| Edge Side Includes (ESI) | CDN/反向代理層組合、快取片段獨立失效 | 高流量靜態化架構 |