1. 过滤器在SpringBoot中的核心作用
在Web开发中,过滤器(Filter)是Java Servlet规范中的重要组件,它像一道安检门一样对所有进入应用的HTTP请求进行预处理,也能对响应做后处理。SpringBoot作为Spring框架的"约定大于配置"实现,自然继承了这一强大特性。
我见过不少刚接触SpringBoot的开发者容易混淆过滤器和拦截器(Interceptor)。简单来说,过滤器更底层,工作在Servlet层面,能处理所有请求;而拦截器是Spring MVC的组件,只能处理进入Controller的请求。过滤器适合做通用的、与业务无关的处理,比如:
- 请求日志记录
- 字符编码设置
- 敏感词过滤
- 简单的权限校验
- 跨域请求处理
2. 实现过滤器的三种方式
2.1 传统Servlet方式
最基础的方式是实现javax.servlet.Filter接口:
java复制public class OldSchoolFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {
// 初始化逻辑
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
long start = System.currentTimeMillis();
// 前置处理
chain.doFilter(request, response); // 放行请求
// 后置处理
System.out.println("耗时:"+(System.currentTimeMillis()-start));
}
@Override
public void destroy() {
// 资源清理
}
}
然后在配置类中用@Bean注册:
java复制@Bean
public FilterRegistrationBean<OldSchoolFilter> oldSchoolFilter() {
FilterRegistrationBean<OldSchoolFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new OldSchoolFilter());
bean.addUrlPatterns("/*"); // 拦截所有路径
bean.setOrder(1); // 多个过滤器时的执行顺序
return bean;
}
2.2 使用@WebFilter注解
更简洁的方式是使用Servlet 3.0的@WebFilter注解:
java复制@WebFilter(urlPatterns = "/*")
public class AnnotationFilter implements Filter {
// ...实现同上
}
但需要主类添加@ServletComponentScan注解来启用扫描:
java复制@SpringBootApplication
@ServletComponentScan
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
2.3 继承OncePerRequestFilter
Spring提供的OncePerRequestFilter是个抽象类,确保每个请求只过滤一次(避免某些转发场景下的重复执行):
java复制public class SpringStyleFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String token = request.getHeader("X-Token");
if (!"secret".equals(token)) {
response.sendError(401, "Invalid token");
return;
}
chain.doFilter(request, response);
}
}
注册方式与第一种相同。这种方式更适合集成Spring生态的功能。
3. 过滤器的高级应用场景
3.1 跨域处理过滤器
虽然SpringBoot提供了@CrossOrigin注解,但在微服务架构中,更推荐使用统一的过滤器:
java复制public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with, Content-Type");
chain.doFilter(req, res);
}
// ...其他方法
}
3.2 请求日志过滤器
记录请求和响应基本信息对于调试和监控很有帮助:
java复制public class LoggingFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(LoggingFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
long startTime = System.currentTimeMillis();
chain.doFilter(requestWrapper, responseWrapper);
long duration = System.currentTimeMillis() - startTime;
String requestBody = new String(requestWrapper.getContentAsByteArray(),
request.getCharacterEncoding());
String responseBody = new String(responseWrapper.getContentAsByteArray(),
response.getCharacterEncoding());
log.info("\n===请求日志===\n" +
"URI: {}\nMethod: {}\n" +
"请求参数: {}\n" +
"响应状态: {}\n" +
"响应内容: {}\n" +
"耗时: {}ms",
request.getRequestURI(), request.getMethod(),
requestBody,
response.getStatus(),
responseBody,
duration);
responseWrapper.copyBodyToResponse(); // 重要!将缓存内容写回响应
}
}
注意:这里使用了
ContentCachingRequestWrapper和ContentCachingResponseWrapper来缓存请求/响应体,否则输入流只能读取一次。
3.3 接口耗时统计过滤器
性能监控是生产环境的重要需求:
java复制public class TimingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
long start = System.currentTimeMillis();
chain.doFilter(request, response);
long end = System.currentTimeMillis();
if (request instanceof HttpServletRequest) {
String uri = ((HttpServletRequest) request).getRequestURI();
Metrics.record(uri, end - start); // 自定义的指标记录工具
}
}
}
4. 过滤器链与执行顺序
当存在多个过滤器时,它们的执行顺序由FilterRegistrationBean的setOrder()方法决定,数值越小优先级越高。例如:
java复制@Bean
public FilterRegistrationBean<FilterA> filterA() {
FilterRegistrationBean<FilterA> bean = new FilterRegistrationBean<>();
bean.setFilter(new FilterA());
bean.setOrder(1); // 最先执行
return bean;
}
@Bean
public FilterRegistrationBean<FilterB> filterB() {
FilterRegistrationBean<FilterB> bean = new FilterRegistrationBean<>();
bean.setFilter(new FilterB());
bean.setOrder(2); // 第二个执行
return bean;
}
过滤器链的执行流程如下:
- FilterA的前置处理
- FilterB的前置处理
- 实际业务处理(Controller)
- FilterB的后置处理
- FilterA的后置处理
5. 常见问题与解决方案
5.1 过滤器不生效的可能原因
- 未正确注册:确保通过
@Bean或@WebFilter正确注册 - 路径匹配问题:检查
urlPatterns是否覆盖目标路径 - 顺序冲突:高优先级的过滤器未调用
chain.doFilter() - 异常被吞没:在过滤器中捕获异常后未正确处理
5.2 获取请求Body的注意事项
由于HTTP请求的输入流只能读取一次,直接读取会导致Controller获取不到参数。解决方案:
- 使用
ContentCachingRequestWrapper包装请求 - 在过滤器中缓存请求体内容
5.3 性能优化建议
- 避免在过滤器中做耗时操作(如远程调用)
- 对于静态资源,考虑排除过滤
- 使用
@ConditionalOnProperty实现按需启用
java复制@Bean
@ConditionalOnProperty(name = "app.logging.enabled", havingValue = "true")
public FilterRegistrationBean<LoggingFilter> loggingFilter() {
// ...
}
6. 过滤器与拦截器的对比选择
| 特性 | 过滤器(Filter) | 拦截器(Interceptor) |
|---|---|---|
| 作用范围 | Servlet容器层面 | Spring MVC层面 |
| 依赖 | Servlet API | Spring框架 |
| 执行时机 | 在DispatcherServlet之前 | 在DispatcherServlet之后 |
| 获取Spring Bean | 需要额外配置 | 直接注入 |
| 适用场景 | 通用处理(编码、CORS、日志等) | 业务相关处理(权限、参数校验等) |
在实际项目中,我通常这样分工:
- 过滤器处理与业务无关的横切关注点
- 拦截器处理业务相关的预处理/后处理
- AOP处理特定业务模块的切面逻辑
7. 实战:构建可配置的安全过滤器
下面演示一个完整的安全过滤器实现,支持通过配置文件动态设置白名单:
java复制@Configuration
@ConfigurationProperties(prefix = "security.filter")
public class SecurityFilterConfig {
private List<String> ipWhitelist = new ArrayList<>();
private List<String> pathExclusions = new ArrayList<>();
// getters & setters
@Bean
public FilterRegistrationBean<SecurityFilter> securityFilter() {
FilterRegistrationBean<SecurityFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new SecurityFilter(this));
bean.addUrlPatterns("/*");
return bean;
}
}
public class SecurityFilter extends OncePerRequestFilter {
private final SecurityFilterConfig config;
public SecurityFilter(SecurityFilterConfig config) {
this.config = config;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String ip = request.getRemoteAddr();
String path = request.getRequestURI();
// 检查排除路径
if (config.getPathExclusions().stream().anyMatch(path::startsWith)) {
chain.doFilter(request, response);
return;
}
// IP白名单检查
if (!config.getIpWhitelist().contains(ip)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "IP not allowed");
return;
}
chain.doFilter(request, response);
}
}
然后在application.yml中配置:
yaml复制security:
filter:
ip-whitelist:
- 192.168.1.100
- 10.0.0.15
path-exclusions:
- /public/
- /health
这个设计体现了SpringBoot的配置化思想,无需修改代码即可调整安全策略。
