SpringMVC 拦截器与过滤器
2025/9/17大约 11 分钟
SpringMVC 拦截器与过滤器
学习目标
通过本教程,您将掌握:
- 拦截器与过滤器的区别
- 自定义拦截器的实现
- 过滤器的配置和使用
- 权限控制和登录验证
- 日志记录和性能监控
拦截器与过滤器概述
基本概念对比
特性 | 过滤器 (Filter) | 拦截器 (Interceptor) |
---|---|---|
作用范围 | Servlet 容器级别 | SpringMVC 框架级别 |
执行时机 | 请求到达 Servlet 前后 | 请求到达 Controller 前后 |
配置方式 | web.xml 或 @WebFilter | Spring 配置文件 |
依赖注入 | 不支持 | 支持 Spring 依赖注入 |
访问 Spring 上下文 | 困难 | 容易 |
处理范围 | 所有请求 | 仅 SpringMVC 处理的请求 |
执行顺序
sequenceDiagram
participant Client as 客户端
participant Filter as 过滤器
participant DS as DispatcherServlet
participant Interceptor as 拦截器
participant Controller as 控制器
Client->>Filter: 1. 请求
Filter->>DS: 2. 过滤后的请求
DS->>Interceptor: 3. preHandle
Interceptor->>Controller: 4. 处理请求
Controller-->>Interceptor: 5. 返回结果
Interceptor->>DS: 6. postHandle
DS->>Interceptor: 7. afterCompletion
Interceptor-->>Filter: 8. 响应
Filter-->>Client: 9. 最终响应
SpringMVC 拦截器
1. 拦截器接口
public interface HandlerInterceptor {
/**
* 预处理方法
* @param request 请求对象
* @param response 响应对象
* @param handler 处理器对象
* @return true继续执行,false中断执行
*/
default boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
return true;
}
/**
* 后处理方法(Controller执行后,视图渲染前)
* @param request 请求对象
* @param response 响应对象
* @param handler 处理器对象
* @param modelAndView 模型和视图对象
*/
default void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
}
/**
* 完成处理方法(视图渲染后)
* @param request 请求对象
* @param response 响应对象
* @param handler 处理器对象
* @param ex 异常对象
*/
default void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
}
}
2. 登录验证拦截器
点击展开登录验证拦截器
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
// 不需要登录验证的路径
private static final Set<String> EXCLUDE_PATHS = Set.of(
"/login", "/register", "/captcha", "/css", "/js", "/images"
);
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String requestURI = request.getRequestURI();
String contextPath = request.getContextPath();
String path = requestURI.substring(contextPath.length());
log.info("登录拦截器 - 请求路径: {}", path);
// 检查是否为排除路径
if (isExcludePath(path)) {
log.info("路径 {} 无需登录验证", path);
return true;
}
// 检查用户是否已登录
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("user") != null) {
log.info("用户已登录,继续执行");
return true;
}
// 未登录,重定向到登录页面
log.warn("用户未登录,重定向到登录页面");
// AJAX 请求返回 JSON
if (isAjaxRequest(request)) {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write(
"{\"code\":401,\"message\":\"请先登录\",\"data\":null}"
);
return false;
}
// 普通请求重定向
response.sendRedirect(contextPath + "/login?redirect=" +
URLEncoder.encode(requestURI, "UTF-8"));
return false;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
// 为所有视图添加用户信息
if (modelAndView != null) {
HttpSession session = request.getSession(false);
if (session != null) {
Object user = session.getAttribute("user");
if (user != null) {
modelAndView.addObject("currentUser", user);
}
}
}
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
if (ex != null) {
log.error("请求处理异常: {}", ex.getMessage(), ex);
}
log.info("请求处理完成: {} - 状态码: {}",
request.getRequestURI(), response.getStatus());
}
/**
* 检查是否为排除路径
*/
private boolean isExcludePath(String path) {
return EXCLUDE_PATHS.stream().anyMatch(path::startsWith);
}
/**
* 判断是否为 AJAX 请求
*/
private boolean isAjaxRequest(HttpServletRequest request) {
String xRequestedWith = request.getHeader("X-Requested-With");
return "XMLHttpRequest".equals(xRequestedWith) ||
request.getHeader("Accept").contains("application/json");
}
}
3. 权限控制拦截器
点击展开权限控制拦截器
@Component
@Slf4j
public class AuthorityInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 只处理方法处理器
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 检查方法是否需要权限验证
RequireRole requireRole = handlerMethod.getMethodAnnotation(RequireRole.class);
if (requireRole == null) {
// 检查类级别注解
requireRole = handlerMethod.getBeanType().getAnnotation(RequireRole.class);
}
if (requireRole == null) {
return true; // 无需权限验证
}
// 获取当前用户
HttpSession session = request.getSession(false);
if (session == null) {
return handleNoPermission(request, response, "用户未登录");
}
User currentUser = (User) session.getAttribute("user");
if (currentUser == null) {
return handleNoPermission(request, response, "用户未登录");
}
// 检查用户角色
String[] requiredRoles = requireRole.value();
boolean hasPermission = userService.hasAnyRole(currentUser.getId(), requiredRoles);
if (!hasPermission) {
log.warn("用户 {} 尝试访问需要角色 {} 的资源: {}",
currentUser.getUsername(),
Arrays.toString(requiredRoles),
request.getRequestURI());
return handleNoPermission(request, response, "权限不足");
}
log.info("用户 {} 权限验证通过", currentUser.getUsername());
return true;
}
/**
* 处理无权限情况
*/
private boolean handleNoPermission(HttpServletRequest request,
HttpServletResponse response,
String message) throws IOException {
if (isAjaxRequest(request)) {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write(
String.format("{\"code\":403,\"message\":\"%s\",\"data\":null}", message)
);
} else {
response.sendRedirect(request.getContextPath() + "/error/403");
}
return false;
}
private boolean isAjaxRequest(HttpServletRequest request) {
String xRequestedWith = request.getHeader("X-Requested-With");
return "XMLHttpRequest".equals(xRequestedWith);
}
}
4. 权限注解定义
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequireRole {
/**
* 需要的角色列表
*/
String[] value() default {};
/**
* 是否需要所有角色(true:需要所有角色,false:需要任一角色)
*/
boolean requireAll() default false;
}
5. 性能监控拦截器
点击展开性能监控拦截器
@Component
@Slf4j
public class PerformanceInterceptor implements HandlerInterceptor {
private static final String START_TIME_ATTRIBUTE = "startTime";
private static final long SLOW_REQUEST_THRESHOLD = 1000; // 1秒
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
long startTime = System.currentTimeMillis();
request.setAttribute(START_TIME_ATTRIBUTE, startTime);
log.debug("请求开始: {} {}", request.getMethod(), request.getRequestURI());
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
Long startTime = (Long) request.getAttribute(START_TIME_ATTRIBUTE);
if (startTime != null) {
long duration = System.currentTimeMillis() - startTime;
log.debug("Controller 处理耗时: {}ms", duration);
}
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
Long startTime = (Long) request.getAttribute(START_TIME_ATTRIBUTE);
if (startTime != null) {
long totalDuration = System.currentTimeMillis() - startTime;
String method = request.getMethod();
String uri = request.getRequestURI();
int status = response.getStatus();
// 记录请求信息
if (totalDuration > SLOW_REQUEST_THRESHOLD) {
log.warn("慢请求检测: {} {} - 耗时: {}ms - 状态码: {}",
method, uri, totalDuration, status);
} else {
log.info("请求完成: {} {} - 耗时: {}ms - 状态码: {}",
method, uri, totalDuration, status);
}
// 可以在这里记录到监控系统
recordMetrics(method, uri, totalDuration, status);
}
}
/**
* 记录性能指标
*/
private void recordMetrics(String method, String uri, long duration, int status) {
// 这里可以集成 Micrometer、Prometheus 等监控系统
// 示例:记录到内存中的简单统计
PerformanceMetrics.record(method, uri, duration, status);
}
}
6. 拦截器配置
<!-- SpringMVC 配置文件中配置拦截器 -->
<mvc:interceptors>
<!-- 性能监控拦截器 - 最先执行 -->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/static/**"/>
<mvc:exclude-mapping path="/css/**"/>
<mvc:exclude-mapping path="/js/**"/>
<mvc:exclude-mapping path="/images/**"/>
<bean class="com.example.interceptor.PerformanceInterceptor"/>
</mvc:interceptor>
<!-- 登录验证拦截器 -->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/login"/>
<mvc:exclude-mapping path="/register"/>
<mvc:exclude-mapping path="/static/**"/>
<bean class="com.example.interceptor.LoginInterceptor"/>
</mvc:interceptor>
<!-- 权限控制拦截器 -->
<mvc:interceptor>
<mvc:mapping path="/admin/**"/>
<mvc:mapping path="/api/admin/**"/>
<bean class="com.example.interceptor.AuthorityInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
Servlet 过滤器
1. 字符编码过滤器
@WebFilter(filterName = "encodingFilter", urlPatterns = "/*")
public class CharacterEncodingFilter implements Filter {
private String encoding = "UTF-8";
private boolean forceEncoding = true;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String encodingParam = filterConfig.getInitParameter("encoding");
if (encodingParam != null) {
this.encoding = encodingParam;
}
String forceParam = filterConfig.getInitParameter("forceEncoding");
if (forceParam != null) {
this.forceEncoding = Boolean.parseBoolean(forceParam);
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 设置请求编码
if (forceEncoding || request.getCharacterEncoding() == null) {
request.setCharacterEncoding(encoding);
}
// 设置响应编码
if (forceEncoding || response.getCharacterEncoding() == null) {
response.setCharacterEncoding(encoding);
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
// 清理资源
}
}
2. CORS 跨域过滤器
点击展开跨域过滤器
@WebFilter(filterName = "corsFilter", urlPatterns = "/api/*")
@Slf4j
public class CorsFilter implements Filter {
private Set<String> allowedOrigins;
private Set<String> allowedMethods;
private Set<String> allowedHeaders;
private boolean allowCredentials;
private int maxAge;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 允许的源
String origins = filterConfig.getInitParameter("allowedOrigins");
this.allowedOrigins = origins != null ?
Set.of(origins.split(",")) :
Set.of("http://localhost:3000", "http://localhost:8080");
// 允许的方法
String methods = filterConfig.getInitParameter("allowedMethods");
this.allowedMethods = methods != null ?
Set.of(methods.split(",")) :
Set.of("GET", "POST", "PUT", "DELETE", "OPTIONS");
// 允许的头部
String headers = filterConfig.getInitParameter("allowedHeaders");
this.allowedHeaders = headers != null ?
Set.of(headers.split(",")) :
Set.of("Content-Type", "Authorization", "X-Requested-With");
// 是否允许凭证
String credentials = filterConfig.getInitParameter("allowCredentials");
this.allowCredentials = credentials != null ?
Boolean.parseBoolean(credentials) : true;
// 预检请求缓存时间
String maxAgeParam = filterConfig.getInitParameter("maxAge");
this.maxAge = maxAgeParam != null ?
Integer.parseInt(maxAgeParam) : 3600;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String origin = httpRequest.getHeader("Origin");
String method = httpRequest.getMethod();
log.debug("CORS 过滤器 - Origin: {}, Method: {}", origin, method);
// 检查源是否被允许
if (origin != null && (allowedOrigins.contains("*") || allowedOrigins.contains(origin))) {
httpResponse.setHeader("Access-Control-Allow-Origin", origin);
}
// 设置允许的方法
httpResponse.setHeader("Access-Control-Allow-Methods",
String.join(",", allowedMethods));
// 设置允许的头部
httpResponse.setHeader("Access-Control-Allow-Headers",
String.join(",", allowedHeaders));
// 设置是否允许凭证
if (allowCredentials) {
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
}
// 设置预检请求缓存时间
httpResponse.setHeader("Access-Control-Max-Age", String.valueOf(maxAge));
// 处理预检请求
if ("OPTIONS".equals(method)) {
httpResponse.setStatus(HttpServletResponse.SC_OK);
return;
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
// 清理资源
}
}
3. 请求日志过滤器
点击展开请求日志过滤器
@WebFilter(filterName = "requestLogFilter", urlPatterns = "/*")
@Slf4j
public class RequestLogFilter implements Filter {
private static final Set<String> EXCLUDE_PATHS = Set.of(
"/css", "/js", "/images", "/favicon.ico"
);
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String requestURI = httpRequest.getRequestURI();
// 跳过静态资源
if (shouldExclude(requestURI)) {
chain.doFilter(request, response);
return;
}
// 包装请求和响应以便记录
RequestWrapper requestWrapper = new RequestWrapper(httpRequest);
ResponseWrapper responseWrapper = new ResponseWrapper(httpResponse);
long startTime = System.currentTimeMillis();
String requestId = generateRequestId();
try {
// 记录请求信息
logRequest(requestWrapper, requestId);
// 继续过滤链
chain.doFilter(requestWrapper, responseWrapper);
} finally {
// 记录响应信息
long duration = System.currentTimeMillis() - startTime;
logResponse(responseWrapper, requestId, duration);
// 将响应写回原始响应
responseWrapper.copyBodyToResponse();
}
}
/**
* 记录请求信息
*/
private void logRequest(RequestWrapper request, String requestId) {
StringBuilder logBuilder = new StringBuilder();
logBuilder.append("\n=== 请求开始 [ID: ").append(requestId).append("] ===");
logBuilder.append("\n方法: ").append(request.getMethod());
logBuilder.append("\nURI: ").append(request.getRequestURI());
logBuilder.append("\n查询参数: ").append(request.getQueryString());
logBuilder.append("\nContent-Type: ").append(request.getContentType());
logBuilder.append("\nUser-Agent: ").append(request.getHeader("User-Agent"));
logBuilder.append("\nRemote-Addr: ").append(request.getRemoteAddr());
// 记录请求头
logBuilder.append("\n请求头:");
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = request.getHeader(headerName);
logBuilder.append("\n ").append(headerName).append(": ").append(headerValue);
}
// 记录请求体(仅对 POST/PUT 请求)
if ("POST".equals(request.getMethod()) || "PUT".equals(request.getMethod())) {
String body = request.getBody();
if (body != null && !body.isEmpty()) {
logBuilder.append("\n请求体: ").append(body);
}
}
log.info(logBuilder.toString());
}
/**
* 记录响应信息
*/
private void logResponse(ResponseWrapper response, String requestId, long duration) {
StringBuilder logBuilder = new StringBuilder();
logBuilder.append("\n=== 响应结束 [ID: ").append(requestId).append("] ===");
logBuilder.append("\n状态码: ").append(response.getStatus());
logBuilder.append("\nContent-Type: ").append(response.getContentType());
logBuilder.append("\n处理时间: ").append(duration).append("ms");
// 记录响应头
logBuilder.append("\n响应头:");
for (String headerName : response.getHeaderNames()) {
String headerValue = response.getHeader(headerName);
logBuilder.append("\n ").append(headerName).append(": ").append(headerValue);
}
// 记录响应体(限制长度)
String body = response.getBody();
if (body != null && !body.isEmpty()) {
if (body.length() > 1000) {
body = body.substring(0, 1000) + "... (truncated)";
}
logBuilder.append("\n响应体: ").append(body);
}
log.info(logBuilder.toString());
}
private boolean shouldExclude(String requestURI) {
return EXCLUDE_PATHS.stream().anyMatch(requestURI::contains);
}
private String generateRequestId() {
return UUID.randomUUID().toString().substring(0, 8);
}
}
4. 过滤器配置
web.xml 配置
<!-- 字符编码过滤器 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>com.example.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- CORS 过滤器 -->
<filter>
<filter-name>corsFilter</filter-name>
<filter-class>com.example.filter.CorsFilter</filter-class>
<init-param>
<param-name>allowedOrigins</param-name>
<param-value>http://localhost:3000,http://localhost:8080</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>corsFilter</filter-name>
<url-pattern>/api/*</url-pattern>
</filter-mapping>
实际应用示例
1. 用户管理控制器
点击展开示例
@Controller
@RequestMapping("/admin/users")
@RequireRole("ADMIN") // 类级别权限控制
public class UserManagementController {
@Autowired
private UserService userService;
/**
* 用户列表 - 需要管理员权限
*/
@GetMapping
public String userList(Model model) {
List<User> users = userService.findAll();
model.addAttribute("users", users);
return "admin/user-list";
}
/**
* 删除用户 - 需要超级管理员权限
*/
@PostMapping("/delete/{id}")
@RequireRole("SUPER_ADMIN") // 方法级别权限控制
@ResponseBody
public ResponseEntity<?> deleteUser(@PathVariable Long id) {
try {
userService.deleteById(id);
return ResponseEntity.ok(Map.of("message", "用户删除成功"));
} catch (Exception e) {
return ResponseEntity.badRequest()
.body(Map.of("error", "删除失败: " + e.getMessage()));
}
}
/**
* 用户详情 - 需要管理员或用户本人权限
*/
@GetMapping("/{id}")
public String userDetail(@PathVariable Long id, Model model, HttpSession session) {
User currentUser = (User) session.getAttribute("user");
// 检查是否为用户本人或管理员
if (!currentUser.getId().equals(id) && !userService.hasRole(currentUser.getId(), "ADMIN")) {
throw new AccessDeniedException("无权访问该用户信息");
}
User user = userService.findById(id);
model.addAttribute("user", user);
return "admin/user-detail";
}
}
2. 登录控制器
点击展开示例
@Controller
public class LoginController {
@Autowired
private UserService userService;
/**
* 登录页面
*/
@GetMapping("/login")
public String loginPage(@RequestParam(required = false) String redirect,
Model model) {
model.addAttribute("redirect", redirect);
return "login";
}
/**
* 处理登录
*/
@PostMapping("/login")
public String login(@RequestParam String username,
@RequestParam String password,
@RequestParam(required = false) String redirect,
HttpSession session,
RedirectAttributes redirectAttributes) {
try {
User user = userService.authenticate(username, password);
if (user != null) {
session.setAttribute("user", user);
session.setMaxInactiveInterval(30 * 60); // 30分钟
// 重定向到原始请求页面
if (redirect != null && !redirect.isEmpty()) {
return "redirect:" + redirect;
}
return "redirect:/dashboard";
} else {
redirectAttributes.addFlashAttribute("error", "用户名或密码错误");
return "redirect:/login";
}
} catch (Exception e) {
redirectAttributes.addFlashAttribute("error", "登录失败: " + e.getMessage());
return "redirect:/login";
}
}
/**
* 退出登录
*/
@PostMapping("/logout")
public String logout(HttpSession session) {
session.invalidate();
return "redirect:/login?logout=true";
}
}
最佳实践
1. 拦截器设计原则
设计建议
- 单一职责:每个拦截器只处理一种关注点
- 顺序重要:合理安排拦截器执行顺序
- 异常处理:妥善处理拦截器中的异常
- 性能考虑:避免在拦截器中执行耗时操作
2. 过滤器使用建议
// 过滤器执行顺序配置
@WebFilter(filterName = "orderFilter", urlPatterns = "/*")
@Order(1) // 数字越小优先级越高
public class OrderedFilter implements Filter {
// 过滤器实现
}
3. 性能优化
@Component
public class CacheableInterceptor implements HandlerInterceptor {
private final Cache<String, Object> cache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String cacheKey = generateCacheKey(request);
Object cachedResult = cache.getIfPresent(cacheKey);
if (cachedResult != null) {
// 返回缓存结果
response.getWriter().write(cachedResult.toString());
return false; // 中断执行
}
return true;
}
private String generateCacheKey(HttpServletRequest request) {
return request.getRequestURI() + "?" + request.getQueryString();
}
}
总结
本教程全面介绍了 SpringMVC 的拦截器和过滤器:
- ✅ 概念对比:理解拦截器与过滤器的区别和适用场景
- ✅ 拦截器实现:登录验证、权限控制、性能监控
- ✅ 过滤器应用:字符编码、CORS、请求日志
- ✅ 配置管理:XML 和注解两种配置方式
- ✅ 实际应用:完整的权限控制系统示例
- ✅ 最佳实践:性能优化和设计原则
下一步学习
- 学习 SpringMVC 异常处理机制
- 了解文件上传和下载
- 掌握国际化和本地化
掌握了拦截器和过滤器,您就能构建安全、高效的 Web 应用了!