Spring 拦截器详解
2025/11/30大约 5 分钟
拦截器
一、什么是拦截器
拦截器(Interceptor)是Spring MVC框架提供的一种机制,用于在请求处理前后进行拦截和处理。它可以在请求到达Controller之前或之后执行特定的逻辑。
拦截器的常见应用场景
- 登录验证和权限检查
- 日志记录
- 性能监控
- 请求参数预处理
- 响应数据统一处理
- 跨域处理
二、拦截器的执行流程
请求 → 过滤器 → DispatcherServlet → 拦截器 → Controller → 拦截器 → 视图 → 响应拦截器的三个核心方法执行顺序:
- preHandle:Controller方法执行前调用
- postHandle:Controller方法执行后、视图渲染前调用
- afterCompletion:视图渲染后、请求完成后调用
三、创建拦截器
3.1 实现HandlerInterceptor接口
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginInterceptor implements HandlerInterceptor {
/**
* 在Controller方法执行前调用
* @return true:继续执行;false:中断请求
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("preHandle: 请求处理之前");
// 获取session中的用户信息
Object user = request.getSession().getAttribute("user");
if (user == null) {
// 未登录,重定向到登录页
response.sendRedirect("/login");
return false;
}
return true;
}
/**
* 在Controller方法执行后、视图渲染前调用
* 只有preHandle返回true时才会执行
*/
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("postHandle: 请求处理之后");
}
/**
* 在整个请求完成后调用,用于资源清理
* 无论preHandle返回true还是false都会执行
*/
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
System.out.println("afterCompletion: 请求完成之后");
}
}3.2 继承HandlerInterceptorAdapter(已过时)
注意:HandlerInterceptorAdapter在Spring 5.3之后已被弃用,推荐直接实现HandlerInterceptor接口。
四、注册拦截器(重要!)
4.1 配置类方式注册
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns( // 排除路径
"/login",
"/register",
"/static/**",
"/error"
)
.order(1); // 设置拦截器执行顺序,数字越小优先级越高
// 可以注册多个拦截器
registry.addInterceptor(new LogInterceptor())
.addPathPatterns("/**")
.order(2);
}
}注意!!!
拦截逻辑是先.addPathPatterns("/**") // 拦截所有路径,排除掉.excludePathPatterns,然后再进入拦截器,进入拦截器在后面,根据拦截器的判断逻辑进行拦截。不是先进入拦截器,再排除路径。
4.2 注入Bean的方式
如果拦截器中需要使用其他Bean(如Service),可以这样配置:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login", "/register");
}
}
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService; // 可以注入其他Bean
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 使用userService
return true;
}
}五、实际应用示例
5.1 登录验证拦截器
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 如果是静态资源或方法,直接放行
if (!(handler instanceof HandlerMethod)) {
return true;
}
// 获取token
String token = request.getHeader("Authorization");
if (token == null || token.isEmpty()) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("未授权访问");
return false;
}
// 验证token
Object user = redisTemplate.opsForValue().get("token:" + token);
if (user == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("token已过期");
return false;
}
// 将用户信息存入request
request.setAttribute("currentUser", user);
return true;
}
}5.2 日志记录拦截器
@Slf4j
@Component
public class LogInterceptor implements HandlerInterceptor {
private static final String START_TIME = "startTime";
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
long startTime = System.currentTimeMillis();
request.setAttribute(START_TIME, startTime);
log.info("请求开始 - URI: {}, Method: {}",
request.getRequestURI(),
request.getMethod());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
long startTime = (Long) request.getAttribute(START_TIME);
long endTime = System.currentTimeMillis();
long executeTime = endTime - startTime;
log.info("请求完成 - URI: {}, 耗时: {}ms, 状态码: {}",
request.getRequestURI(),
executeTime,
response.getStatus());
if (ex != null) {
log.error("请求异常", ex);
}
}
}5.3 跨域处理拦截器
@Component
public class CorsInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
response.setHeader("Access-Control-Max-Age", "3600");
// 处理OPTIONS预检请求
if ("OPTIONS".equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return false;
}
return true;
}
}5.4 防重复提交拦截器
@Component
public class RepeatSubmitInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 检查是否有@RepeatSubmit注解
RepeatSubmit annotation = handlerMethod.getMethodAnnotation(RepeatSubmit.class);
if (annotation == null) {
return true;
}
// 生成唯一key
String token = request.getHeader("Authorization");
String url = request.getRequestURI();
String key = "repeat_submit:" + token + ":" + url;
// 检查是否重复提交
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, "1", annotation.interval(), TimeUnit.SECONDS);
if (Boolean.FALSE.equals(success)) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().write("请勿重复提交");
return false;
}
return true;
}
}
// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
int interval() default 5; // 防重复提交间隔时间(秒)
}六、拦截器与过滤器的区别
| 特性 | 拦截器(Interceptor) | 过滤器(Filter) |
|---|---|---|
| 规范 | Spring MVC提供 | Servlet规范 |
| 拦截范围 | 只拦截Controller请求 | 拦截所有请求 |
| Spring容器 | 由Spring管理,可注入Bean | 由Servlet容器管理 |
| 执行顺序 | 在DispatcherServlet之后 | 在DispatcherServlet之前 |
| 使用场景 | 业务逻辑处理 | 字符编码、安全过滤等 |
七、多个拦截器的执行顺序
当有多个拦截器时,执行顺序如下:
Interceptor1.preHandle
Interceptor2.preHandle
Controller方法执行
Interceptor2.postHandle
Interceptor1.postHandle
Interceptor2.afterCompletion
Interceptor1.afterCompletion可以通过order()方法设置优先级,数字越小优先级越高。
八、注意事项
preHandle返回false:当preHandle返回false时,后续的拦截器和Controller都不会执行,但已执行的拦截器的afterCompletion方法仍会被调用
异常处理:拦截器中抛出的异常可以被全局异常处理器捕获
静态资源:建议将静态资源路径排除在拦截器之外,提高性能
Bean注入:如果拦截器需要使用Spring Bean,必须将拦截器注册为Spring Bean
响应已提交:在preHandle中如果已经通过response输出内容,应返回false并确保流已关闭
线程安全:拦截器是单例的,注意线程安全问题
九、总结
Spring Boot拦截器是实现横切关注点的重要工具,通过合理使用拦截器可以优雅地实现登录验证、日志记录、性能监控等功能。掌握拦截器的使用是Spring Boot开发中的必备技能。
