首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java Web Filter 過濾器核心原理與 5 大經典應用場景實戰

     編輯:Java 相關知識

前言

Filter (過濾器) 是 Java Servlet 規範定義的核心組件,能在請求到達 Servlet 前、響應返回客戶端前進行**攔截、預處理、後處理**。它基於**責任鏈模式**,構成 Filter Chain,是實現横切關注點 (Cross-cutting Concerns) 的標準化方式。本文深度解析 Filter 生命週期、配置方式、核心 API,並詳解 5 大高頻實戰場景完整代碼。

一、Filter 核心機制

生命週期 (由容器管理)

  1. init(FilterConfig config):應用啟動時實例化,僅執行一次,可讀取初始化參數。
  2. doFilter(request, response, chain):每次請求經過時調用,**核心業務邏輯在此**。
    必須調用 `chain.doFilter(req, resp)` 將請求傳遞給下一個 Filter 或目標 Servlet,否則鏈路中斷。
  3. destroy():應用關閉/重新部署時調用,釋放資源 (連線池、線程池、緩存)。

配置方式

  • web.xml:`` + `` (URL 模式 / Servlet 名稱)。
  • @WebFilter (Servlet 3.0+):註解式配置,支援 `urlPatterns`、`servletNames`、`dispatcherTypes` (REQUEST/ERROR/ASYNC/FORWARD/INCLUDE)。
  • Spring Boot:`@Component` + `@Order` 或 `FilterRegistrationBean` 註冊,整合 Spring 容器支援注入。

二、核心 API 技巧

  • 包裝請求/響應:繼承 `HttpServletRequestWrapper` / `HttpServletResponseWrapper` 覆寫方法 (如讀取 Body 多次、壓縮響應、添加 Header)。
  • 異步支援:`request.startAsync()` 獲得 `AsyncContext`,Filter 中完成 `chain.doFilter()` 後異步處理,最後 `asyncContext.complete()`。
  • 排序控制:`@Order` (數值越小越優先) 或 web.xml 定義順序。Spring Security Filter Chain 依賴精確排序。

三、5 大經典應用場景實戰

場景 1:請求響應日誌與執行時間監控

@WebFilter(urlPatterns = "/*")
@Order(1)
public class LoggingFilter implements Filter {
    private static final Logger log = LoggerFactory.getLogger(LoggingFilter.class);

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        long start = System.currentTimeMillis();
        try {
            chain.doFilter(request, resp);
        } finally {
            long cost = System.currentTimeMillis() - start;
            log.info("{} {} {}ms UA:{}", request.getMethod(), request.getRequestURI(), cost, request.getHeader("User-Agent"));
        }
    }
}

場景 2:統一字符編碼處理 (解決 POST 中文亂碼)

@WebFilter(urlPatterns = "/*")
@Order(2)
public class EncodingFilter implements Filter {
    private String encoding = "UTF-8";
    private boolean forceEncoding = true;

    @Override
    public void init(FilterConfig config) {
        encoding = config.getInitParameter("encoding");
        forceEncoding = Boolean.parseBoolean(config.getInitParameter("forceEncoding"));
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        if (forceEncoding || request.getCharacterEncoding() == null) {
            request.setCharacterEncoding(encoding);
        }
        if (forceEncoding || response.getCharacterEncoding() == null) {
            response.setCharacterEncoding(encoding);
        }
        chain.doFilter(request, response);
    }
}

場景 3:登入驗證與權限攔截 (Session/Token)

@WebFilter(urlPatterns = {"/api/*", "/admin/*"})
@Order(10)
public class AuthFilter implements Filter {
    private static final Set WHITE_LIST = Set.of("/api/login", "/api/register", "/error");

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        String uri = request.getRequestURI();

        if (WHITE_LIST.stream().anyMatch(uri::startsWith)) {
            chain.doFilter(request, response);
            return;
        }

        // JWT Token 驗證示例
        String token = request.getHeader("Authorization");
        if (token == null || !JwtUtil.validate(token.replace("Bearer ", ""))) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("{\"code\":401,\"msg\":\"未登入或Token無效\"}");
            return;
        }
        // 可選:將用戶信息放入 Request Attribute 供後續使用
        request.setAttribute("currentUser", JwtUtil.getUser(token));
        chain.doFilter(request, response);
    }
}

場景 4:XSS 防護與請求參數清洗

@WebFilter(urlPatterns = "/*")
@Order(20)
public class XssFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        // 包裝 Request,覆寫 getParameter/Map/Values 進行清洗
        chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) req), resp);
    }

    static class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
        public XssHttpServletRequestWrapper(HttpServletRequest request) { super(request); }
        private String clean(String value) {
            if (value == null) return null;
            return value.replaceAll("<", "<").replaceAll(">", ">")
                        .replaceAll("\\(", "(").replaceAll("\\)", ")")
                        .replaceAll("'", "'").replaceAll("\"", """);
        }
        @Override public String getParameter(String name) { return clean(super.getParameter(name)); }
        @Override public String[] getParameterValues(String name) {
            String[] vals = super.getParameterValues(name);
            if (vals == null) return null;
            return Arrays.stream(vals).map(this::clean).toArray(String[]::new);
        }
        @Override public Map getParameterMap() {
            Map map = super.getParameterMap();
            Map cleanMap = new HashMap<>();
            map.forEach((k, v) -> cleanMap.put(k, Arrays.stream(v).map(this::clean).toArray(String[]::new)));
            return cleanMap;
        }
    }
}

場景 5:響應壓縮 (GZIP) 與快取控制

@WebFilter(urlPatterns = "/*")
@Order(30)
public class GzipFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;

        // 檢查 Client 支援 gzip
        String acceptEncoding = request.getHeader("Accept-Encoding");
        if (acceptEncoding != null && acceptEncoding.contains("gzip")) {
            GzipHttpServletResponseWrapper wrapper = new GzipHttpServletResponseWrapper(response);
            try {
                chain.doFilter(request, wrapper);
            } finally {
                wrapper.finish(); // 刷新 GZIPOutputStream,寫入 Footer
            }
        } else {
            chain.doFilter(request, response);
        }
    }

    static class GzipHttpServletResponseWrapper extends HttpServletResponseWrapper {
        private GZIPOutputStream gzipOS;
        private ServletOutputStream sos;
        private PrintWriter pw;

        public GzipHttpServletResponseWrapper(HttpServletResponse response) { super(response); }

        @Override public ServletOutputStream getOutputStream() throws IOException {
            if (pw != null) throw new IllegalStateException("getWriter() already called");
            if (sos == null) {
                sos = getResponse().getOutputStream();
                gzipOS = new GZIPOutputStream(sos);
            }
            return new ServletOutputStream() {
                @Override public void write(int b) throws IOException { gzipOS.write(b); }
                @Override public void write(byte[] b, int off, int len) throws IOException { gzipOS.write(b, off, len); }
                @Override public void flush() throws IOException { gzipOS.flush(); }
                @Override public void close() throws IOException { gzipOS.finish(); }
                @Override public boolean isReady() { return sos.isReady(); }
                @Override public void setWriteListener(WriteListener l) { sos.setWriteListener(l); }
            };
        }
        @Override public PrintWriter getWriter() throws IOException {
            if (sos != null) throw new IllegalStateException("getOutputStream() already called");
            if (pw == null) {
                pw = new PrintWriter(new OutputStreamWriter(getOutputStream(), getResponse().getCharacterEncoding()));
            }
            return pw;
        }
        public void finish() throws IOException {
            if (gzipOS != null) gzipOS.finish();
            if (pw != null) pw.flush();
        }
        @Override public void setContentLength(int len) { /* 忽略,壓縮後長度未知 */ }
        @Override public void setContentLengthLong(long len) { /* 忽略 */ }
    }
}

四、Filter vs Interceptor vs AOP 選型指南

維度Filter (Servlet)Interceptor (Spring MVC)AOP (Spring)
作用範圍所有請求 (含靜態資源、JSP、Servlet)僅 Spring MVC Controller 請求Spring Bean 方法 (Service、Repository 等)
依賴容器Servlet 容器 (Tomcat/Jetty)Spring MVC DispatcherServletSpring 容器
獲取 Spring Bean需手動從 WebApplicationContext 獲取直接注入直接注入
適用場景編碼、日誌、壓縮、安全攔截、XSS、靜態資源Controller 級權限、參數驗證、日誌、事務 (配合)業務邏輯切面、事務、緩存、鎖、監控、異常統一處理

五、結語

Filter 是 Java Web 基礎設施的基石,掌握其生命週期、鏈路傳遞、包裝模式與排序控制,配合 5 大經典場景模板,能解決 80% 以上的横切關注點需求。實務中建議:通用基礎設施 (編碼、日誌、壓縮、安全) 用 Filter;Controller 級邏輯用 Interceptor;業務級切面用 AOP,分層清晰、職責單一、易於維護擴展。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved