1. 为什么我们需要Filter过滤器?
在JavaWeb开发中,Filter就像是一个尽职的"门卫",它站在Servlet容器和实际业务逻辑之间,对所有进出请求进行把关。想象一下你管理着一栋办公大楼,Filter就是那个在入口处检查每个人证件的前台人员。
我曾在实际项目中遇到过这样的场景:一个电商系统需要对所有请求进行登录验证、敏感词过滤和请求日志记录。如果没有Filter,我们不得不在每个Servlet中重复编写这些代码。这不仅违反了DRY原则(Don't Repeat Yourself),还让代码维护变得异常困难。
Filter的核心价值在于:
- 预处理请求(Pre-processing)
- 后处理响应(Post-processing)
- 拦截请求或响应
- 修改请求头和数据
- 实现跨切面功能(AOP)
提示:Filter是Servlet规范的一部分,这意味着它可以在任何支持Servlet的Web容器中工作,如Tomcat、Jetty等。
2. Filter的工作原理与生命周期
2.1 Filter的执行流程
Filter的执行顺序就像是一个"洋葱模型"——请求从外层向内层传递,响应则从内层向外层返回。具体流程如下:
- 客户端发送HTTP请求
- 容器根据URL匹配Filter链
- 按web.xml中定义的顺序执行Filter的doFilter()方法
- 最后一个Filter调用chain.doFilter()将请求传递给Servlet
- Servlet处理请求并生成响应
- 响应按相反顺序再次经过Filter链
- 最终响应返回给客户端
java复制// 典型Filter方法签名
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 预处理逻辑
chain.doFilter(request, response); // 关键点:放行请求
// 后处理逻辑
}
2.2 Filter的生命周期
Filter的生命周期由容器管理,包含三个阶段:
-
初始化阶段:Web应用启动时,容器调用init()方法
- 只会执行一次
- 适合加载配置、初始化资源
-
服务阶段:每次匹配到请求时调用doFilter()
- 核心业务逻辑所在
- 可能被并发调用,需注意线程安全
-
销毁阶段:Web应用停止时调用destroy()
- 用于释放资源
- 同样只执行一次
java复制public class MyFilter implements Filter {
private String configParam;
public void init(FilterConfig config) {
this.configParam = config.getInitParameter("paramName");
}
public void doFilter(...) { /* 处理逻辑 */ }
public void destroy() { /* 清理工作 */ }
}
3. Filter的配置方式详解
3.1 传统web.xml配置
虽然现在流行注解方式,但了解传统配置仍有价值:
xml复制<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>com.example.EncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
关键配置项:
<url-pattern>:匹配规则(支持/*、/admin/*等)<dispatcher>:指定拦截的请求类型(REQUEST, FORWARD, INCLUDE, ERROR)<init-param>:初始化参数,可通过FilterConfig获取
3.2 注解方式配置
Servlet 3.0+支持注解配置,更加简洁:
java复制@WebFilter(
filterName = "myFilter",
urlPatterns = {"/*"},
initParams = {
@WebInitParam(name = "encoding", value = "UTF-8")
},
dispatcherTypes = {DispatcherType.REQUEST, DispatcherType.FORWARD}
)
public class MyFilter implements Filter {
// 实现代码
}
注意:注解方式虽然方便,但在复杂项目中可能不如XML配置灵活,特别是当需要动态调整Filter顺序时。
3.3 编程式动态注册
对于需要运行时动态添加Filter的场景,可以使用ServletContext的addFilter方法:
java复制FilterRegistration.Dynamic registration = servletContext
.addFilter("dynamicFilter", DynamicFilter.class);
registration.addMappingForUrlPatterns(
EnumSet.of(DispatcherType.REQUEST), true, "/*");
registration.setInitParameter("key", "value");
这种方式特别适合框架集成,比如Spring Boot自动配置Filter时就是采用这种机制。
4. 实战:解决中文乱码问题
4.1 乱码问题的根源
中文乱码是JavaWeb开发中的经典问题,主要发生在:
- GET请求参数:Tomcat默认使用ISO-8859-1解码URL
- POST请求体:如果未设置正确编码,容器可能使用默认编码解析
- 响应输出:未设置Content-Type导致浏览器误判编码
4.2 通用解决方案
下面是一个完整的字符编码Filter实现:
java复制@WebFilter("/*")
public class EncodingFilter implements Filter {
private String encoding = "UTF-8";
public void init(FilterConfig config) {
String param = config.getInitParameter("encoding");
if (param != null) {
encoding = param;
}
}
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
// 设置请求编码 - 对POST请求体有效
req.setCharacterEncoding(encoding);
// 包装请求对象以处理GET参数
HttpServletRequest request = (HttpServletRequest) req;
if ("GET".equalsIgnoreCase(request.getMethod())) {
request = new EncodingWrapper(request, encoding);
}
// 设置响应编码
resp.setCharacterEncoding(encoding);
resp.setContentType("text/html;charset=" + encoding);
chain.doFilter(request, resp);
}
public void destroy() {}
}
// 自定义请求包装器处理GET参数
class EncodingWrapper extends HttpServletRequestWrapper {
private final String encoding;
public EncodingWrapper(HttpServletRequest request, String encoding) {
super(request);
this.encoding = encoding;
}
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if (value != null) {
try {
// 将ISO-8859-1编码的字符串转换为UTF-8
return new String(value.getBytes("ISO-8859-1"), encoding);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
return null;
}
}
4.3 测试与验证
验证Filter是否生效的简单方法:
- 创建测试Servlet:
java复制@WebServlet("/test")
public class TestServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String name = req.getParameter("name");
resp.getWriter().println("GET参数: " + name);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String name = req.getParameter("name");
resp.getWriter().println("POST参数: " + name);
}
}
- 测试步骤:
- 访问
/test?name=中文检查GET参数 - 使用Postman发送POST请求,表单数据包含
name=中文 - 检查控制台和页面显示是否正常显示中文
5. Filter高级应用场景
5.1 权限控制Filter
实现基于角色的访问控制:
java复制@WebFilter("/admin/*")
public class AuthFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("user") == null) {
response.sendRedirect(request.getContextPath() + "/login");
return;
}
User user = (User) session.getAttribute("user");
if (!"ADMIN".equals(user.getRole())) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
chain.doFilter(request, response);
}
}
5.2 日志记录Filter
记录请求耗时和基本信息:
java复制public class LoggingFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(LoggingFilter.class);
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
long startTime = System.currentTimeMillis();
String requestURI = request.getRequestURI();
String clientIP = request.getRemoteAddr();
try {
chain.doFilter(req, resp);
} finally {
long duration = System.currentTimeMillis() - startTime;
logger.info("{} from {} took {} ms", requestURI, clientIP, duration);
}
}
}
5.3 XSS防护Filter
防止跨站脚本攻击:
java复制public class XSSFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(new XSSRequestWrapper((HttpServletRequest) req), resp);
}
}
class XSSRequestWrapper extends HttpServletRequestWrapper {
public XSSRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
return cleanXSS(value);
}
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values == null) return null;
String[] cleanedValues = new String[values.length];
for (int i = 0; i < values.length; i++) {
cleanedValues[i] = cleanXSS(values[i]);
}
return cleanedValues;
}
private String cleanXSS(String value) {
if (value == null) return null;
// 简单的XSS清理逻辑
return value.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll("\"", """)
.replaceAll("'", "'");
}
}
6. Filter的常见陷阱与优化
6.1 Filter执行顺序问题
Filter的执行顺序由以下规则决定:
- web.xml中定义的顺序(从上到下)
- 注解方式按类名字母顺序
- 编程式注册按注册顺序
提示:对于有依赖关系的Filter,建议使用web.xml显式控制顺序,或者使用@Order注解(在Spring环境中)。
6.2 性能优化建议
- 避免在Filter中做耗时操作:如数据库查询、远程调用等
- 合理使用Filter链:将不同功能的Filter拆分开,而不是全部堆在一个Filter中
- 注意线程安全:Filter实例通常是单例的,不要在成员变量中保存状态
- 合理设置urlPatterns:避免过于宽泛的/*匹配,精确控制Filter的作用范围
6.3 常见错误排查
-
Filter不生效:
- 检查urlPatterns是否匹配
- 确认Filter类是否被容器加载
- 查看是否有异常被吞没
-
循环重定向:
- 检查Filter逻辑是否导致无限重定向
- 确保对静态资源放行
-
编码设置无效:
- 确认Filter是否在所有Filter的最前面
- 检查是否有其他Filter或Servlet覆盖了编码设置
7. Filter与相关技术的对比
7.1 Filter vs Interceptor
| 特性 | Filter | Interceptor (如Spring) |
|---|---|---|
| 作用层次 | Servlet容器层面 | 框架层面 |
| 依赖 | Servlet规范 | 特定框架(如Spring) |
| 执行时机 | 更早(在Servlet之前) | 稍晚(在DispatcherServlet之后) |
| 访问范围 | 只能访问ServletRequest | 可以访问HandlerMethod等 |
| 异常处理 | 只能通过web.xml配置 | 可以通过框架机制处理 |
7.2 Filter vs Servlet
| 特性 | Filter | Servlet |
|---|---|---|
| 主要职责 | 预处理和后处理 | 处理业务逻辑 |
| 生命周期 | 可以拦截请求不传递给Servlet | 通常是请求处理的终点 |
| 配置方式 | 可以映射多个URL | 通常一对一映射 |
| 典型用途 | 横切关注点(日志、安全等) | 具体业务处理 |
7.3 Filter vs AOP
| 特性 | Filter | AOP |
|---|---|---|
| 作用层次 | Web请求级别 | 方法调用级别 |
| 粒度 | 较粗(基于URL) | 较细(基于方法) |
| 适用场景 | HTTP相关处理 | 业务逻辑处理 |
| 性能影响 | 较小(在容器层) | 较大(需要动态代理) |
8. 实际项目中的Filter设计模式
8.1 责任链模式的应用
Filter本质上是责任链模式的实现,我们可以利用这一点构建灵活的过滤系统:
java复制// 自定义Filter链示例
public class CustomFilterChain {
private List<Filter> filters = new ArrayList<>();
private int index = 0;
public CustomFilterChain addFilter(Filter filter) {
filters.add(filter);
return this;
}
public void doFilter(ServletRequest req, ServletResponse resp)
throws IOException, ServletException {
if (index < filters.size()) {
Filter filter = filters.get(index++);
filter.doFilter(req, resp, this);
}
}
}
// 使用方式
CustomFilterChain chain = new CustomFilterChain()
.addFilter(new LoggingFilter())
.addFilter(new AuthFilter())
.addFilter(new EncodingFilter());
chain.doFilter(request, response);
8.2 装饰器模式的应用
通过装饰器模式增强请求/响应对象:
java复制public class GzipFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String acceptEncoding = request.getHeader("Accept-Encoding");
if (acceptEncoding != null && acceptEncoding.contains("gzip")) {
response.setHeader("Content-Encoding", "gzip");
chain.doFilter(request, new GzipResponseWrapper(response));
} else {
chain.doFilter(request, response);
}
}
}
class GzipResponseWrapper extends HttpServletResponseWrapper {
private GZIPOutputStream gzipStream;
public GzipResponseWrapper(HttpServletResponse response) throws IOException {
super(response);
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
if (gzipStream == null) {
gzipStream = new GZIPOutputStream(super.getOutputStream());
}
return new ServletOutputStreamWrapper(gzipStream);
}
@Override
public void flushBuffer() throws IOException {
if (gzipStream != null) {
gzipStream.finish();
}
super.flushBuffer();
}
}
8.3 策略模式的应用
根据不同条件应用不同的过滤策略:
java复制public class DynamicFilter implements Filter {
private Map<String, Filter> strategyMap = new HashMap<>();
public void init(FilterConfig config) {
// 初始化策略映射
strategyMap.put("admin", new AdminAuthFilter());
strategyMap.put("api", new ApiKeyFilter());
strategyMap.put("mobile", new MobileFilter());
}
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
String path = request.getRequestURI();
Filter strategy = determineStrategy(path);
if (strategy != null) {
strategy.doFilter(req, resp, chain);
} else {
chain.doFilter(req, resp);
}
}
private Filter determineStrategy(String path) {
if (path.startsWith("/admin")) return strategyMap.get("admin");
if (path.startsWith("/api")) return strategyMap.get("api");
if (path.contains("mobile")) return strategyMap.get("mobile");
return null;
}
}
9. Filter在主流框架中的应用
9.1 Spring中的Filter
Spring Boot自动配置了几个常用Filter:
- CharacterEncodingFilter:字符编码处理
- HiddenHttpMethodFilter:支持PUT/DELETE等HTTP方法
- HttpPutFormContentFilter:处理PUT请求表单数据
自定义Filter的Spring方式:
java复制@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<MyFilter> myFilter() {
FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new MyFilter());
registration.addUrlPatterns("/*");
registration.setOrder(Ordered.HIGHEST_PRECEDENCE); // 设置最高优先级
return registration;
}
}
9.2 JAX-RS中的Filter
JAX-RS (如Jersey)提供了ContainerFilter和ClientFilter:
java复制@Provider
@PreMatching // 在匹配资源方法前执行
public class JaxRsFilter implements ContainerRequestFilter {
public void filter(ContainerRequestContext ctx) throws IOException {
// 修改请求头、验证权限等
}
}
9.3 微服务中的Filter模式
在微服务架构中,Filter的概念演化为:
- API Gateway的Filter(如Spring Cloud Gateway)
- 服务网格的Sidecar Filter(如Istio)
- 客户端负载均衡Filter
例如Spring Cloud Gateway的Filter:
java复制@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("path_route", r -> r.path("/api/**")
.filters(f -> f.addRequestHeader("X-Request-ID", UUID.randomUUID().toString())
.addResponseHeader("X-Response-Time", Instant.now().toString()))
.uri("http://backend-service"))
.build();
}
10. Filter性能监控与调优
10.1 监控Filter执行时间
使用Filter链监控每个Filter的性能:
java复制public class MonitoringFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
long start = System.nanoTime();
try {
chain.doFilter(req, resp);
} finally {
long duration = System.nanoTime() - start;
Metrics.record("filter." + getClass().getSimpleName(), duration);
}
}
}
10.2 异步Filter优化
Servlet 3.0+支持异步Filter,适合处理耗时操作:
java复制@WebFilter(asyncSupported = true, urlPatterns = "/*")
public class AsyncFilter implements Filter {
private ExecutorService executor = Executors.newFixedThreadPool(10);
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
AsyncContext context = req.startAsync();
executor.execute(() -> {
try {
// 执行耗时操作
chain.doFilter(context.getRequest(), context.getResponse());
} catch (Exception e) {
context.complete();
}
});
}
}
10.3 Filter缓存策略
对于静态资源,实现缓存Filter:
java复制public class CacheFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String path = request.getRequestURI();
if (path.endsWith(".js") || path.endsWith(".css")) {
response.setHeader("Cache-Control", "public, max-age=86400");
}
chain.doFilter(request, response);
}
}
11. Filter测试策略
11.1 单元测试Filter
使用Mock对象测试Filter逻辑:
java复制public class AuthFilterTest {
@Test
public void testUnauthorizedAccess() throws Exception {
AuthFilter filter = new AuthFilter();
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
FilterChain chain = mock(FilterChain.class);
when(request.getSession(false)).thenReturn(null);
filter.doFilter(request, response, chain);
verify(response).sendRedirect(anyString());
verify(chain, never()).doFilter(any(), any());
}
}
11.2 集成测试Filter
使用嵌入式容器测试Filter:
java复制@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class FilterIntegrationTest {
@LocalServerPort
private int port;
@Test
public void testEncodingFilter() {
RestTemplate rest = new RestTemplate();
String url = "http://localhost:" + port + "/test?name=中文";
String response = rest.getForObject(url, String.class);
assertThat(response).contains("中文");
}
}
11.3 性能测试Filter
使用JMeter测试Filter性能影响:
- 创建测试计划:模拟100并发用户
- 添加HTTP请求:访问包含Filter的端点
- 添加监听器:查看响应时间和吞吐量
- 对比有无Filter的性能差异
12. Filter的未来发展趋势
12.1 云原生时代的Filter
随着云原生和Serverless架构兴起,Filter演变为:
- 函数计算中的中间件
- Service Mesh中的Wasm Filter
- 边缘计算中的CDN Edge Functions
12.2 声明式Filter配置
趋势是向声明式、配置化的Filter发展,如:
yaml复制# 假设的声明式Filter配置
filters:
- name: logging
type: before
pattern: /*
config:
level: INFO
- name: auth
type: before
pattern: /api/*
config:
provider: oauth2
12.3 AI驱动的动态Filter
未来可能出现:
- 基于流量模式自动调整的Filter
- 智能限流和熔断Filter
- 自适应安全策略Filter
13. 从Filter看软件设计思想
13.1 关注点分离(Separation of Concerns)
Filter完美体现了这一原则:
- 业务逻辑:由Servlet处理
- 横切关注点:由Filter处理
- 各司其职,互不干扰
13.2 开闭原则(Open/Closed Principle)
通过Filter可以:
- 对现有功能扩展(开放)
- 不修改原有代码(关闭)
- 如添加日志、监控等新功能
13.3 中间件思想
Filter是中间件思想的早期实践:
- 标准化处理管道
- 可插拔的组件
- 统一的处理接口
这种思想后来演化为现代中间件架构,如Express.js的中间件、Spring的拦截器等。
14. Filter的最佳实践总结
经过多年项目实践,我总结了以下Filter使用原则:
- 单一职责:每个Filter只做一件事(如只处理编码或只做认证)
- 明确作用域:精确控制urlPatterns,避免过度拦截
- 注意顺序:关键Filter(如编码Filter)应该放在最前面
- 性能意识:避免在Filter中做耗时操作
- 异常处理:妥善处理异常,避免吞没重要错误
- 线程安全:不要使用实例变量保存状态
- 合理使用包装器:需要修改请求/响应时使用Wrapper模式
- 文档完善:为每个Filter编写清晰的文档说明其用途和配置
15. 常见问题解答
Q1: Filter和Servlet有什么区别?
A: 主要区别在于职责和调用时机:
- Filter用于预处理和后处理,可以拦截请求不传递给Servlet
- Servlet用于处理业务逻辑,通常是请求处理的终点
- 一个请求可以经过多个Filter,但通常只由一个Servlet处理
Q2: 为什么我的Filter没有被调用?
A: 可能原因包括:
- urlPatterns配置不正确,没有匹配到目标URL
- Filter类没有被正确加载(检查包扫描路径)
- 有异常被抛出但未被记录
- 在注解配置中类名排序靠后,且前面的Filter没有调用chain.doFilter()
Q3: 如何处理Filter中的异常?
A: 推荐做法:
- 捕获特定异常并转换为合适的HTTP错误码
- 使用RequestDispatcher转发到错误页面
- 对于API可以返回结构化的错误JSON
- 记录详细的错误日志供排查
Q4: 如何在Filter中获取Spring Bean?
A: 在Spring环境中可以通过以下方式:
- 使用DelegatingFilterProxy(传统方式)
- 通过WebApplicationContextUtils获取上下文
- 最佳实践是使用FilterRegistrationBean注册Filter
java复制@Bean
public FilterRegistrationBean<MyFilter> myFilterRegistration(MyService service) {
FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new MyFilter(service)); // 注入依赖
// 其他配置...
return registration;
}
Q5: Filter能否修改请求参数?
A: 可以,但需要通过HttpServletRequestWrapper:
java复制public class ModifyParamsFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
chain.doFilter(new ModifyParamsWrapper(request), resp);
}
}
class ModifyParamsWrapper extends HttpServletRequestWrapper {
public ModifyParamsWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
if ("secret".equals(name)) return "REDACTED";
return super.getParameter(name);
}
}
