3-2 拦截器与过滤器
概念解析
Filter vs Interceptor vs AOP
| 组件 | 执行位置 | 作用范围 | 实现方式 |
|---|---|---|---|
| Filter | Servlet 容器 | 容器层面 | Servlet API |
| Interceptor | Spring MVC | Controller 层 | Spring 框架 |
| AOP | Spring IoC | Service/Repository | Spring AOP |
执行顺序
请求 → Filter1 → Filter2 → DispatcherServlet → Interceptor1 → Interceptor2 → Controller
↓
执行 Controller
↓
响应 ← Filter1 ← Filter2 ← DispatcherServlet ← Interceptor1 ← Interceptor2 ←
代码示例
1. Filter(过滤器)
// 方式一:注解方式(Spring Boot 3.x)
@WebFilter(urlPatterns = "/api/*", filterName = "myFilter")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("MyFilter 初始化");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
// 前置处理
long start = System.currentTimeMillis();
// 放行
chain.doFilter(request, response);
// 后置处理
long cost = System.currentTimeMillis() - start;
log.info("请求 {} 耗时 {}ms", req.getRequestURI(), cost);
}
@Override
public void destroy() {
log.info("MyFilter 销毁");
}
}
// 方式二:配置类方式(Spring Boot 3.x 推荐)
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<MyFilter> myFilter() {
FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new MyFilter());
registration.addUrlPatterns("/api/*");
registration.setOrder(1); // 执行顺序,数字越小越先执行
return registration;
}
}
2. HandlerInterceptor(拦截器)
@Component
@Slf4j
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 前置处理:返回 true 放行,false 拦截
String token = request.getHeader("Authorization");
if (token == null || !validateToken(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":401,\"message\":\"未登录\"}");
return false;
}
// 将用户信息存入请求属性
request.setAttribute("userId", getUserId(token));
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
// 后置处理:Controller 执行后,视图渲染前
log.info("postHandle: {}", request.getRequestURI());
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
// 完成后处理:请求完成后(无论成功失败)
if (ex != null) {
log.error("请求异常", ex);
}
}
}
3. 注册拦截器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;
@Autowired
private LoggingInterceptor loggingInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 日志拦截器
registry.addInterceptor(loggingInterceptor)
.addPathPatterns("/**") // 拦截所有
.excludePathPatterns("/static/**", "/error"); // 排除静态资源
// 认证拦截器
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/**") // 只拦截 /api/*
.excludePathPatterns(
"/api/auth/login",
"/api/auth/register",
"/api/public/**"
);
}
}
4. 字符编码过滤器
@Configuration
public class EncodingConfig {
@Bean
public FilterRegistrationBean<CharacterEncodingFilter> encodingFilter() {
FilterRegistrationBean<CharacterEncodingFilter> registration =
new FilterRegistrationBean<>();
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
filter.setForceRequestEncoding(true);
filter.setForceResponseEncoding(true);
registration.setFilter(filter);
registration.addUrlPatterns("/*");
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registration;
}
}
5. CORS 跨域过滤器
// 方式一:CorsFilter(Spring Boot 3.x)
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOriginPattern("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
// 方式二:WebMvcConfigurer
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
6. Filter 解决 XSS 注入
@Component
@Slf4j
public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(new XssHttpServletRequestWrapper(
(HttpServletRequest) request), response);
}
}
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
return XssUtil.clean(super.getParameter(name));
}
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values == null) return null;
return Arrays.stream(values)
.map(XssUtil::clean)
.toArray(String[]::new);
}
@Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> params = super.getParameterMap();
Map<String, String[]> cleanParams = new HashMap<>();
params.forEach((key, value) ->
cleanParams.put(key, Arrays.stream(value)
.map(XssUtil::clean)
.toArray(String[]::new)));
return cleanParams;
}
}
源码解读
Filter 执行链
// ApplicationFilterChain 源码简化
public final class ApplicationFilterChain implements FilterChain {
private ApplicationFilterConfig[] filters;
private int pos = 0;
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if (pos < filters.length) {
// 执行下一个 Filter
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
filter.doFilter(request, response, this);
} else {
// 所有 Filter 执行完毕,调用 Servlet
servlet.service(request, response);
}
}
}
常见坑点
⚠️ 坑 1:拦截器中获取请求 body
// ❌ 请求 body 只能读取一次
@PostMapping("/api/test")
public Result test(HttpServletRequest request) throws IOException {
// 读取 body 后,流已关闭
BufferedReader reader = request.getReader();
// Controller 层无法再次读取
}
// ✅ 使用 ContentCachingRequestWrapper
public class CachedBodyFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain)
throws ServletException, IOException {
ContentCachingRequestWrapper wrappedRequest =
new ContentCachingRequestWrapper(request);
chain.doFilter(wrappedRequest, response);
// 可以在 Filter 中读取 body
byte[] body = wrappedRequest.getContentAsByteArray();
}
}
⚠️ 坑 2:拦截器执行顺序
// ❌ 错误的顺序配置
registry.addInterceptor(interceptorA).addPathPatterns("/**");
registry.addInterceptor(interceptorB).addPathPatterns("/**");
// 实际执行:B 先执行(后添加)
// ✅ 正确做法
registry.addInterceptor(interceptorA)
.addPathPatterns("/**")
.order(1); // 明确指定顺序
registry.addInterceptor(interceptorB)
.addPathPatterns("/**")
.order(2);
⚠️ 坑 3:Filter 中注入 Bean
// ❌ 直接注入无效
@Component
public class MyFilter implements Filter {
@Autowired
private UserService userService; // 可能为 null
}
// ✅ 使用延迟获取
@Component
public class MyFilter implements Filter {
@Autowired
private ApplicationContext context;
@Override
public void doFilter(...) {
UserService userService = context.getBean(UserService.class);
// 使用
}
}
面试题
Q1:Filter、Interceptor、AOP 的区别?
参考答案:
| 维度 | Filter | Interceptor | AOP |
|---|---|---|---|
| 位置 | Servlet 容器 | Spring MVC | Spring 容器 |
| 接口 | javax.servlet.Filter | HandlerInterceptor | @Aspect |
| 作用范围 | 所有请求 | Controller 层 | Service/Repository |
| 能否获取 Spring Bean | 否 | 是 | 是 |
| 能否拦截 Filter | 能 | 否 | 否 |
| 能否拦截静态资源 | 能 | 否 | 否 |
| 典型场景 | 字符编码、CORS | 认证、日志 | 事务、性能监控 |
Q2:Filter 如何处理中文乱码?
参考答案:
@Component
public class EncodingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
// 设置请求编码
req.setCharacterEncoding("UTF-8");
// 设置响应编码和 Content-Type
resp.setCharacterEncoding("UTF-8");
resp.setContentType("application/json;charset=UTF-8");
chain.doFilter(req, resp);
}
}
Q3:拦截器如何实现登录校验?
参考答案:
-
定义拦截器
@Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.getHeader("Authorization"); if (token == null || !JwtUtil.validate(token)) { response.setStatus(401); return false; } // 验证通过,解析用户信息存 request request.setAttribute("userId", JwtUtil.getUserId(token)); return true; } } -
注册拦截器
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/api/**") .excludePathPatterns( "/api/auth/**", "/api/public/**" ); } }