Appearance
拦截器与过滤器
Spring MVC 提供了两种强大的请求处理组件:拦截器(Interceptor)和过滤器(Filter)。这两个组件都允许在请求处理的不同阶段执行自定义代码,但它们有不同的工作方式、作用范围和使用场景。本文将详细介绍这两种组件的特点以及如何有效使用它们。
拦截器(Interceptor)
拦截器是 Spring MVC 框架的一部分,它允许在请求处理的不同阶段执行代码,实现了 AOP(面向切面编程)的思想。
拦截器的工作原理
拦截器基于 Java 的反射机制,只能拦截 Controller 中的方法。拦截器在请求处理过程中有三个执行点:
- preHandle:在 Controller 处理请求前执行
- postHandle:在 Controller 处理请求后、视图渲染前执行
- afterCompletion:在整个请求处理完成后执行,包括视图渲染结束后
拦截器的实现方式
实现 HandlerInterceptor 接口
java
public class LoggingInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
request.setAttribute("startTime", System.currentTimeMillis());
logger.info("Incoming request to: {} {}", request.getMethod(), request.getRequestURI());
// 如果返回false,请求将被中断
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
logger.info("Request processed, ModelAndView: {}", modelAndView);
// 这里可以修改ModelAndView
if (modelAndView != null) {
modelAndView.addObject("serverTime", LocalDateTime.now().toString());
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
long startTime = (Long) request.getAttribute("startTime");
long executionTime = System.currentTimeMillis() - startTime;
logger.info("Completed {} {} in {}ms",
request.getMethod(), request.getRequestURI(), executionTime);
if (ex != null) {
logger.error("Exception during request processing: ", ex);
}
}
}
继承 HandlerInterceptorAdapter 类(已弃用)
在 Spring 5.3 之前,还可以通过继承 HandlerInterceptorAdapter 类来实现拦截器。但从 Spring 5.3 开始,这个类已被标记为弃用,因为 HandlerInterceptor 接口现在有了默认方法实现。
注册拦截器
在 Spring MVC 中注册拦截器需要实现 WebMvcConfigurer 接口:
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoggingInterceptor loggingInterceptor;
@Autowired
private SecurityInterceptor securityInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 添加日志拦截器,应用于所有请求
registry.addInterceptor(loggingInterceptor)
.addPathPatterns("/**");
// 添加安全拦截器,只应用于特定路径
registry.addInterceptor(securityInterceptor)
.addPathPatterns("/admin/**", "/api/secured/**")
.excludePathPatterns("/api/public/**");
}
}
拦截器的执行顺序
当多个拦截器应用于同一个请求时,它们的执行顺序遵循以下规则:
- preHandle 方法按照拦截器注册的顺序执行(从前到后)
- postHandle 方法按照拦截器注册的顺序逆序执行(从后到前)
- afterCompletion 方法按照拦截器注册的顺序逆序执行(从后到前)
如果某个拦截器的 preHandle 方法返回 false,则后续拦截器不会执行,并且该拦截器和之前已执行的拦截器的 postHandle 方法都不会被调用,但之前执行的拦截器的 afterCompletion 方法会被调用。
过滤器(Filter)
过滤器是 Servlet 规范的一部分,不属于 Spring 框架。它能够拦截和处理所有进入应用的请求,不仅限于 Controller 方法。
过滤器的工作原理
过滤器基于函数回调,在请求到达 Servlet 容器之前和响应离开 Servlet 容器之后执行。过滤器可以修改 HTTP 请求和响应对象,甚至可以阻止请求继续传递,直接返回响应。
过滤器的实现方式
实现 javax.servlet.Filter 接口
java
public class RequestResponseLoggingFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(RequestResponseLoggingFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 过滤器初始化代码
logger.info("Filter initialized");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
logger.info("Filter processing: {} {}", req.getMethod(), req.getRequestURI());
// 包装请求或响应对象(可选)
// HttpServletRequestWrapper customRequest = new CustomRequestWrapper(req);
long startTime = System.currentTimeMillis();
// 继续过滤器链
chain.doFilter(request, response);
long endTime = System.currentTimeMillis();
logger.info("Request processed in {}ms", endTime - startTime);
// 后处理,此时请求已完成
}
@Override
public void destroy() {
// 过滤器销毁代码
logger.info("Filter destroyed");
}
}
注册过滤器
在 Spring Boot 中,注册过滤器可以通过以下几种方式:
使用 @Component 注解(适用于自定义过滤器)
java
@Component
public class RequestResponseLoggingFilter implements Filter {
// 实现代码
}
使用 FilterRegistrationBean 注册
java
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<RequestResponseLoggingFilter> loggingFilter() {
FilterRegistrationBean<RequestResponseLoggingFilter> registrationBean =
new FilterRegistrationBean<>();
registrationBean.setFilter(new RequestResponseLoggingFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(1); // 设置过滤器顺序
registrationBean.setName("loggingFilter");
return registrationBean;
}
@Bean
public FilterRegistrationBean<ContentSecurityFilter> securityFilter() {
FilterRegistrationBean<ContentSecurityFilter> registrationBean =
new FilterRegistrationBean<>();
registrationBean.setFilter(new ContentSecurityFilter());
registrationBean.addUrlPatterns("/admin/*", "/secure/*");
registrationBean.setOrder(2); // 会在 loggingFilter 之后执行
return registrationBean;
}
}
在 web.xml 中配置(传统 Spring MVC 项目)
xml
<filter>
<filter-name>loggingFilter</filter-name>
<filter-class>com.example.filters.RequestResponseLoggingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>loggingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
过滤器的执行顺序
过滤器的执行顺序由其注册顺序或指定的 order 值决定。order 值越小,过滤器执行顺序越靠前。当请求进入应用时,过滤器按照顺序从前往后执行;当响应离开应用时,过滤器按照相反的顺序从后往前执行。
拦截器与过滤器的比较
特性 | 拦截器 (Interceptor) | 过滤器 (Filter) |
---|---|---|
归属 | Spring MVC 框架 | Servlet 规范 |
作用范围 | 只拦截 Controller 请求 | 拦截所有进入容器的请求 |
实现原理 | 基于反射机制 | 基于函数回调 |
执行顺序 | 在 Filter 之后 | 在 Interceptor 之前 |
获取参数 | 能获取 Handler 和 ModelAndView | 无法获取 Handler 和 ModelAndView |
执行时机 | preHandle, postHandle, afterCompletion | 请求前和响应后 |
IoC 支持 | 能通过注入获取 Spring 容器中的其他 Bean | 不能直接获取 Spring Bean(除非使用 DelegatingFilterProxy) |
使用场景 | 权限检查、日志记录、性能监控 | 字符编码设置、CORS 处理、敏感信息过滤 |
常见应用场景
拦截器的常见应用
身份验证和授权
javapublic class AuthenticationInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 检查用户是否已登录 if (!isUserAuthenticated(request)) { response.sendRedirect("/login"); return false; // 中断请求处理 } return true; // 继续处理请求 } private boolean isUserAuthenticated(HttpServletRequest request) { // 验证逻辑 return request.getSession().getAttribute("user") != null; } }
本地化和国际化
javapublic class LocaleInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String language = request.getParameter("lang"); if (language != null) { Locale locale = new Locale(language); LocaleContextHolder.setLocale(locale); } return true; } }
应用程序性能监控
javapublic class PerformanceInterceptor implements HandlerInterceptor { private static final Logger logger = LoggerFactory.getLogger(PerformanceInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { request.setAttribute("startTime", System.currentTimeMillis()); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { long startTime = (Long) request.getAttribute("startTime"); long endTime = System.currentTimeMillis(); long executionTime = endTime - startTime; if (executionTime > 1000) { logger.warn("Slow request: {} {} took {}ms", request.getMethod(), request.getRequestURI(), executionTime); } } }
过滤器的常见应用
请求和响应参数修改
javapublic class EncodingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); chain.doFilter(request, response); } }
CORS(跨域资源共享)处理
javapublic 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", "GET, POST, PUT, DELETE, OPTIONS"); response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); response.setHeader("Access-Control-Max-Age", "3600"); chain.doFilter(req, res); } }
请求和响应日志记录
javapublic class LoggingFilter implements Filter { private static final Logger logger = LoggerFactory.getLogger(LoggingFilter.class); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; logger.info("Request: {} {}", req.getMethod(), req.getRequestURI()); chain.doFilter(request, response); HttpServletResponse res = (HttpServletResponse) response; logger.info("Response: {} for {} {}", res.getStatus(), req.getMethod(), req.getRequestURI()); } }
XSS攻击防御
javapublic class XssFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request); chain.doFilter(xssRequest, response); } } class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { // 包装request,对参数进行清洁处理 // ... @Override public String getParameter(String name) { return cleanXss(super.getParameter(name)); } private String cleanXss(String value) { // 对输入进行过滤,防止XSS攻击 // ... return value; } }
最佳实践
何时使用拦截器
拦截器适合处理与业务逻辑相关的横切关注点,例如:
- 检查用户是否已登录或有权限访问特定资源
- 记录请求处理时间,进行性能监控
- 在请求处理过程中准备或修改模型数据
何时使用过滤器
过滤器适合处理与请求/响应对象本身相关的操作,例如:
- 设置字符编码
- 处理跨域请求
- 对请求参数进行清洁或转换
- 压缩响应内容
- 实现安全策略(如 CSRF 保护)
何时同时使用两者
在复杂应用中,通常需要同时使用过滤器和拦截器:
- 使用过滤器处理请求和响应的全局特性(如字符编码、跨域、压缩)
- 使用拦截器处理与业务逻辑相关的横切关注点(如验证、审计、日志)
例如,在一个典型的安全Web应用中:
- 使用过滤器设置字符编码、处理CORS和防止XSS攻击
- 使用拦截器检查用户认证、授权和记录业务操作日志
示例:综合使用过滤器和拦截器
以下是一个综合使用过滤器和拦截器的示例配置:
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public FilterRegistrationBean<EncodingFilter> encodingFilter() {
FilterRegistrationBean<EncodingFilter> registrationBean =
new FilterRegistrationBean<>();
registrationBean.setFilter(new EncodingFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(1); // 最先执行
return registrationBean;
}
@Bean
public FilterRegistrationBean<XssFilter> xssFilter() {
FilterRegistrationBean<XssFilter> registrationBean =
new FilterRegistrationBean<>();
registrationBean.setFilter(new XssFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(2);
return registrationBean;
}
@Bean
public LoggingInterceptor loggingInterceptor() {
return new LoggingInterceptor();
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loggingInterceptor())
.addPathPatterns("/**");
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/admin/**", "/api/secured/**")
.excludePathPatterns("/api/public/**", "/login", "/register");
}
}
通过合理组合使用过滤器和拦截器,可以构建出灵活、安全且高性能的 Web 应用程序。根据具体需求选择合适的工具,将有助于保持代码的清晰和可维护性。