Spring MVC 拦截器 和 Servlet 过滤器,并提供完整的代码示例。

核心概念
- 目标: 在用户访问某个需要权限的页面(如
/user/profile)之前,检查他是否已经登录。 - 判断: 如果未登录,则拦截该请求,并跳转到登录页面(
/login)。 - 实现手段:
- 转发:
request.getRequestDispatcher("/login").forward(request, response);- 特点: 浏览器地址栏不会改变,仍然是原始请求的 URL,属于服务器内部行为。
- 重定向:
response.sendRedirect("/login");- 特点: 浏览器地址栏会改变为新的 URL (
/login),服务器告诉浏览器:“你去访问这个新地址吧”,属于客户端行为。
- 特点: 浏览器地址栏会改变为新的 URL (
- 转发:
Spring MVC 拦截器
这是在 Spring 项目中最推荐、最优雅的方式,Spring MVC 提供了 HandlerInterceptor 接口,让我们可以方便地实现拦截逻辑。
实现拦截器类
创建一个类,实现 HandlerInterceptor 接口。
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component // 将拦截器交给 Spring 管理
public class LoginInterceptor implements HandlerInterceptor {
/**
* 在请求处理之前进行调用(Controller 方法调用之前)
* @return true: 继续流程 (如调用下一个拦截器或Controller)
* false: 终止流程,不会调用后续的拦截器和Controller,此时我们需要自己通过 response 来产生响应
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("LoginInterceptor.preHandle() 被调用...");
// 1. 从 session 中获取用户信息
Object user = request.getSession().getAttribute("user");
// 2. 判断用户是否已登录
if (user == null) {
System.out.println("用户未登录,进行拦截并跳转到登录页...");
// 使用转发
// request.getRequestDispatcher("/WEB-INF/views/login.jsp").forward(request, response);
// 使用重定向 (推荐,因为重定向后 URL 会变,符合登录页的语义)
// 注意:如果使用了 Thymeleaf 等模板引擎,路径可能是 /login
response.sendRedirect(request.getContextPath() + "/login");
// 返回 false,表示拦截请求,不再调用 Controller
return false;
}
// 3. 用户已登录,放行
System.out.println("用户已登录,放行请求...");
return true;
}
/**
* 请求处理之后进行调用(Controller 方法调用之后,视图渲染之前)
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 通常用于对 ModelAndView 进行处理,比如修改数据、添加公共信息等
System.out.println("LoginInterceptor.postHandle() 被调用...");
}
/**
* 整个请求结束之后被调用(主要用于资源清理工作)
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("LoginInterceptor.afterCompletion() 被调用...");
}
}
代码解释:
@Component: 将这个类声明为 Spring 的一个 Bean,这样 Spring 才能扫描并管理它。preHandle(): 这是拦截器的核心方法,我们在这里进行登录判断。request.getSession().getAttribute("user"): 尝试从 Session 中获取登录用户信息,这个user对象通常是在用户成功登录后放入 Session 的。response.sendRedirect(...): 如果用户未登录,执行重定向。request.getContextPath()可以获取当前项目的根路径(如/my-project),避免了硬编码。return false;: 关键! 返回false会告诉 Spring 停止后续流程,即不再调用 Controller 方法。return true;: 返回true表示放行,请求会继续向下传递。
注册拦截器
光有拦截器类还不行,你需要告诉 Spring 哪些 URL 需要被这个拦截器拦截,这通常在配置类中完成。

如果你使用的是 Spring Boot,可以创建一个配置类:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/user/**") // 拦截所有以 /user/ 开头的请求
.excludePathPatterns("/user/login", "/user/register"); // 排除登录和注册的请求,否则登录请求也会被拦截
}
}
代码解释:
addInterceptor(): 注册我们创建的LoginInterceptor。.addPathPatterns("/user/**"): 设置拦截规则,这里表示拦截所有以/user/开头的路径。.excludePathPatterns(...): 设置排除规则,这些路径不会被拦截。非常重要,否则你的登录请求和注册请求也会被拦截,导致死循环。
Servlet 过滤器
过滤器是 Servlet 规范的一部分,它工作在更底层的请求处理流程中,在 Spring 项目中,它通常在 Spring 拦截器之前执行。
实现过滤器类
创建一个类,实现 javax.servlet.Filter 接口。

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
// @WebFilter(urlPatterns = "/user/*") // 也可以直接用注解配置,但推荐在 web.xml 或注册类中配置
public class LoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 过滤器初始化时调用
System.out.println("LoginFilter 初始化...");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("LoginFilter.doFilter() 被调用...");
// 将 ServletRequest 转换为 HttpServletRequest,以便使用 session 等功能
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 1. 获取请求的 URI
String requestURI = request.getRequestURI();
System.out.println("当前请求 URI: " + requestURI);
// 2. 获取 session
HttpSession session = request.getSession();
// 3. 判断是否是登录或注册相关的请求,如果是则直接放行
if (requestURI.contains("/login") || requestURI.contains("/register")) {
System.out.println("是登录/注册请求,直接放行...");
filterChain.doFilter(request, response); // 继续执行后续的过滤器或 Servlet
return;
}
// 4. 判断用户是否已登录
Object user = session.getAttribute("user");
if (user == null) {
System.out.println("用户未登录,进行拦截并跳转到登录页...");
// 使用转发
// request.getRequestDispatcher("/WEB-INF/views/login.jsp").forward(request, response);
// 使用重定向
response.sendRedirect(request.getContextPath() + "/login");
return; // 终止执行
}
// 5. 用户已登录,放行
System.out.println("用户已登录,放行请求...");
filterChain.doFilter(request, response); // 继续执行后续的过滤器或 Servlet
}
@Override
public void destroy() {
// 过滤器销毁时调用
System.out.println("LoginFilter 销毁...");
}
}
代码解释:
doFilter(): 这是过滤器的核心方法。filterChain.doFilter(request, response): 关键! 这行代码的作用是“放行”,将请求传递给下一个过滤器或目标 Servlet(Controller),如果不调用,请求链就会被中断。return;: 在执行了重定向/转发后,一定要return,否则代码会继续向下执行,错误地调用filterChain.doFilter()。
注册过滤器
在 Spring Boot 中,你可以通过两种方式注册过滤器:
方式 A:使用 @WebFilter 注解 (简单)
在过滤器类上直接添加 @WebFilter 注解。
import javax.servlet.annotation.WebFilter;
@WebFilter(urlPatterns = "/user/*") // 拦截所有 /user/* 下的请求
public class LoginFilter implements Filter {
// ... 上面 doFilter 中的代码 ...
}
这种方式简单,但不够灵活,比如很难设置多个过滤器的执行顺序。
方式 B:编程式注册 (推荐)
创建一个配置类,实现 WebMvcConfigurer 或 ServletListenerRegistrationBean。
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<LoginFilter> loginFilterRegistration() {
FilterRegistrationBean<LoginFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new LoginFilter());
registrationBean.addUrlPatterns("/user/*"); // 设置拦截路径
registrationBean.setOrder(1); // 设置过滤器执行顺序,数字越小越先执行
return registrationBean;
}
}
这种方式更灵活,可以精确控制过滤器的顺序和参数。
总结与对比
| 特性 | Spring MVC 拦截器 | Servlet 过滤器 |
|---|---|---|
| 规范 | Spring 框架特有 | Java EE (Servlet) 规范,通用 |
| 作用范围 | 仅在 Spring MVC 环境下工作 | 在任何支持 Servlet 的容器下工作 |
| 执行时机 | 在过滤器之后,在 Controller 之前/之后执行 | 在拦截器之前,在请求到达应用时最早执行 |
| 获取信息 | 可以获取到 Handler (Controller 方法实例) |
无法获取到 Handler |
| 主要用途 | 权限验证、日志记录(针对 Controller 方法)、数据预处理等 | 字符编码转换、敏感词过滤、通用日志记录(针对所有请求) |
| 配置方式 | 实现 WebMvcConfigurer 的 addInterceptors 方法 |
@WebFilter 注解或 FilterRegistrationBean |
如何选择?
- 优先选择 Spring MVC 拦截器:如果你的项目是 Spring/Spring Boot 项目,并且你的拦截逻辑与具体的 Controller 方法相关(比如根据方法上的注解判断权限),那么拦截器是最佳选择,它与 Spring 生态结合更紧密,使用更方便。
- 考虑使用 Servlet 过滤器:如果你的拦截逻辑是全局的、与框架无关的,比如设置字符集(
CharacterEncodingFilter)、处理跨域、或者你想在 Spring 容器初始化之前就处理请求,那么应该使用过滤器。
对于登录验证这种典型的业务逻辑,Spring MVC 拦截器 是更常见和推荐的做法。
