高頻訪問、低頻更新的頁面(如論壇總版、首頁聚合),每次請求執行JSP渲染浪費CPU。利用Filter包裝HttpServletResponse捕獲out輸出,首次生成靜態HTML文件/內存字符串,後續請求直接返回靜態內容,實現「偽靜態化」加速。本文演示基於CharArrayWriter的輸出捕獲全鏈路。
doFilter()前,創建HttpServletResponseWrapper子類,重寫getWriter()返回PrintWriter(CharArrayWriter)。chain.doFilter(req, wrapper),JSP正常執行,所有out.print()寫入內存CharArrayWriter。wrapper.toString()獲取完整HTML,寫入文件或緩存對象。FileCaptureFilter)package com.junjing.filter;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class FileCaptureFilter implements Filter {
private String realPathPrefix;
public void init(FilterConfig config) throws ServletException {
realPathPrefix = config.getServletContext().getRealPath("/");
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
// 目標靜態文件路徑
String staticFile = realPathPrefix + "forum/latest.html";
// 包裝Response捕獲輸出
CaptureResponseWrapper wrapper = new CaptureResponseWrapper(response);
chain.doFilter(request, wrapper); // JSP執行,輸出被捕獲
// 獲取生成的HTML
String html = wrapper.getCapturedHtml();
// 寫入靜態文件(生產建議:異步寫入、文件鎖、臨時文件重命名)
writeStaticFile(staticFile, html);
// 直接輸出給瀏覽器(首次請求)
response.getWriter().print(html);
}
private void writeStaticFile(String path, String content) {
try (FileWriter fw = new FileWriter(path)) {
fw.write(content);
} catch (IOException e) {
e.printStackTrace(); // 生產接入日志框架
}
}
public void destroy() {}
}
CaptureResponseWrapper)package com.junjing.filter;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class CaptureResponseWrapper extends HttpServletResponseWrapper {
private final CharArrayWriter buffer = new CharArrayWriter();
private PrintWriter writer;
public CaptureResponseWrapper(HttpServletResponse response) {
super(response);
}
@Override
public PrintWriter getWriter() {
if (writer == null) {
writer = new PrintWriter(buffer);
}
return writer;
}
@Override
public ServletOutputStream getOutputStream() {
throw new UnsupportedOperationException("此Filter僅支持字符輸出捕獲");
}
public String getCapturedHtml() {
if (writer != null) writer.flush();
return buffer.toString();
}
}
<filter>
<filter-name>FileCaptureFilter</filter-name>
<filter-class>com.junjing.filter.FileCaptureFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>FileCaptureFilter</filter-name>
<url-pattern>/latest.jsp</url-pattern>
</filter-mapping>
Cache.get(key, Callable)單飛機制,避免緩存擊穿導致大量線程同時執行JSP。ApplicationEvent、Guava EventBus),緩存監聽器精準失效相關Key,避免全站緩存清空。fastcgi_cache/proxy_cache、Varnish、CDN邊緣緩存,將緩存下沉到基礎設施層,應用層無感。| 特徵 | 適用 | 不適用 |
|---|---|---|
| 訪問頻率 | 高 (QPS > 100) | 低 |
| 更新頻率 | 低 (分鐘/小時級) | 高 (秒級/實時) |
| 個性化程度 | 低/可片段化 | 高度個性化(每用戶不同) |
| 數據一致性要求 | 允許秒級延遲 | 強一致性(金融交易) |
「捕獲輸出→靜態化→直接服務」是經典JSP時代的高性能模式,現代架構雖下沉到Nginx/CDN,但理解其原理對排查緩存不生效、內容不一致等問題極有幫助。