將動態JSP渲染結果捕獲並寫入靜態.html文件,後續請求直接訪問靜態文件,繞過JSP引擎,大幅提升高頻頁面響應速度。本文演示基於ServletOutputStream與HttpServletResponseWrapper的輸出攔截全鏈路。
ByteArrayOutputStream作為內存緩衝區。ServletOutputStream子類,write()委託給ByteArrayOutputStream。HttpServletResponseWrapper,重寫getOutputStream()、getWriter()返回上述包裝流。RequestDispatcher.include()包含目標JSP,輸出被攔截存入內存。.html文件,重定向到靜態文件。ToHtmlServlet)package examples;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class ToHtml extends HttpServlet {
private static final String CONTENT_TYPE = "text/html; charset=GBK";
public void init() throws ServletException {}
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType(CONTENT_TYPE);
service(req, resp);
}
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext sc = getServletContext();
String jspUrl = "/index.jsp"; // 目標動態頁面
String htmlFile = "index.htm"; // 生成的靜態文件名
String htmlPath = "e:\\Tomcat 5.5\\webapps\\jspTohtml\\index.htm"; // 物理路徑
RequestDispatcher rd = sc.getRequestDispatcher(jspUrl);
// 1. 內存字節緩衝區
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 2. 自定義ServletOutputStream委託給BAOS
final ServletOutputStream sos = new ServletOutputStream() {
public void write(byte[] data, int offset, int length) { baos.write(data, offset, length); }
public void write(int b) throws IOException { baos.write(b); }
};
// 3. 字符流包裝(JSP可能用out.print字符輸出)
final PrintWriter pw = new PrintWriter(new OutputStreamWriter(baos));
// 4. 包裝Response,攔截輸出流
HttpServletResponse wrappedResp = new HttpServletResponseWrapper(resp) {
public ServletOutputStream getOutputStream() { return sos; }
public PrintWriter getWriter() { return pw; }
};
// 5. 包含JSP執行,輸出被捕獲到baos
rd.include(req, wrappedResp);
pw.flush();
// 6. 寫入靜態文件
try (FileOutputStream fos = new FileOutputStream(htmlPath)) {
baos.writeTo(fos);
}
// 7. 重定向到靜態頁面
resp.sendRedirect(htmlFile);
}
public void destroy() {}
}
<servlet>
<servlet-name>ToHtml</servlet-name>
<servlet-class>examples.ToHtml</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ToHtml</servlet-name>
<url-pattern>/ToHtml</url-pattern>
</servlet-mapping>
index.jsp)<%@ page contentType="text/html; charset=gb2312" %>
<html><head><title>Cache Filter Test</title></head><body>
簡單測試: s=
<%
int s = 0;
// 模擬耗時計算
for (int i = 0; i < 1000; i++) {
for (int j = 0; j < 1000; j++) { s += j; }
}
out.print(s);
%>
</body></html>
https://localhost:8080/jspTohtml/ToHtml。index.jsp(含耗時循環),捕獲輸出寫入index.htm。index.htm,瀏覽器顯示計算結果。index.htm直接由默認Servlet靜態服務,無JSP執行開銷。
getOutputStream()(二進制)與getWriter()(字符),兼容JSPout.print()與response.getOutputStream()混用場景。include()返回後繼續執行,可獲取捕獲內容;forward()會提交響應不可攔截。OutputStreamWriter(baos)默認用平台編碼,生產環境顯式指定new OutputStreamWriter(baos, "GBK")與JSPpageEncoding一致。ByteArrayOutputStream,無共享狀態。| 方案 | 優勢 | 適用場景 |
|---|---|---|
Nginx fastcgi_cache / proxy_cache | 基礎設施層透明緩存,應用零代碼 | 全站通用、高併發靜態化 |
Spring @Cacheable + 模板引擎 | 方法級緩存,Key可SpEL表達式 | 業務邏輯緩存、片段緩存 |
Thymeleaf/FreeMarker TemplateEngine.process() 直寫文件 | 無需Servlet容器,構建期/定時任務生成 | 構建時靜態化、部署包含靜態文件 |
| SSG (Next.js/Astro/Nuxt) | 構建期預渲染所有頁面 | 內容型站點、極致性能 |
Servlet捕獲JSP輸出生成靜態文件是經典「偽靜態化」模式。理解ResponseWrapper攔截機制,對排查現代框架中響應體獲取異常、緩存不生效等問題極有幫助。