網頁計數器是經典的 Web 應用功能,用於記錄網站訪問人次。本文介紹如何利用 Servlet Filter(過濾器)實現一個高效、自動持久化的訪客計數器,涵蓋初始化讀取、請求攔截計數、銷毀寫入檔案的完整生命週期管理。
選用 javax.servlet.Filter 而非 Servlet 或 Listener 的優勢在於:
init(),關閉時自動調用 destroy(),無需手動觸發。doFilter() 中精確判斷請求路徑(如 /login.action),只統計有效業務訪問。ServletContext 記憶體中,僅在啟動讀取與關閉寫入時存取磁碟,效能極佳。public class EncodingFilter implements Filter {
private String charset = "UTF-8";
private ServletContext context;
private String counterFilePath;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 1. 讀取初始化參數
this.charset = filterConfig.getInitParameter("encoding");
this.context = filterConfig.getServletContext();
// 2. 確定計數器檔案路徑(建議放在 WEB-INF 下,避免外部直接訪問)
String realPath = context.getRealPath("/WEB-INF/counter.txt");
this.counterFilePath = realPath;
// 3. 啟動時讀取歷史計數
File file = new File(counterFilePath);
String count = "0";
if (file.exists()) {
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
count = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
} else {
// 首次啟動,建立檔案並寫入 0
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
writer.write("0");
} catch (IOException e) {
e.printStackTrace();
}
}
context.setAttribute("visitCount", count);
System.out.println("計數器初始化完成,當前計數:" + count);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestPath = httpRequest.getServletPath();
// 僅統計登入動作(或首頁等核心路徑),避免靜態資源、AJAX 輪詢干擾
if (requestPath.endsWith("/login.action") || requestPath.equals("/")) {
// 從 ServletContext 取得計數並加 1
Integer current = Integer.parseInt(context.getAttribute("visitCount").toString());
context.setAttribute("visitCount", String.valueOf(current + 1));
}
// 統一設定編碼
request.setCharacterEncoding(charset);
response.setCharacterEncoding(charset);
chain.doFilter(request, response);
}
@Override
public void destroy() {
// 關閉時將最新計數寫入檔案
String count = (String) context.getAttribute("visitCount");
try (BufferedWriter writer = new BufferedWriter(new FileWriter(counterFilePath))) {
writer.write(count);
System.out.println("計數器持久化完成,最終計數:" + count);
} catch (IOException e) {
e.printStackTrace();
}
}
}
<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>com.example.filter.EncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<!-- 容器啟動時即載入 -->
<load-on-startup>1</load-on-startup>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
AtomicInteger 或資料庫序列替代 ServletContext 字串轉換。doFilter 中過濾 .css、.js、.jpg 等靜態請求,避免計數膨脹。此方案結構清晰、擴展性強,適合中小型專案快速接入訪客統計功能。