原创

Spring Boot 拦截器使用全指南 | 详细教程与示例代码

在Spring Boot应用中,拦截器(Interceptor)是一种强大的机制,用于在请求处理的生命周期中插入自定义逻辑,如日志记录、权限验证或性能监控。拦截器基于Spring MVC框架,允许开发者在控制器方法执行前后介入处理,而无需修改业务代码。与过滤器(Filter)不同,拦截器专注于Spring MVC的请求处理流程,提供更细粒度的控制。本文将深入探讨拦截器的概念、实现方式、常见用例及最佳实践,帮助你全面掌握其在Spring Boot中的应用。

什么是拦截器?

拦截器是Spring MVC的核心组件,实现了HandlerInterceptor接口。它在请求到达控制器前、控制器执行后,以及请求完成后的不同阶段执行自定义代码。拦截器的主要目的是实现横切关注点(Cross-Cutting Concerns),如安全检查或日志记录,而无需在多个控制器中重复代码。

拦截器 vs. 过滤器

理解拦截器前,需区分它与Servlet过滤器的区别:

  • 作用范围:过滤器作用于整个Servlet生命周期(包括静态资源),而拦截器仅针对Spring MVC处理的请求(如Controller方法)。
  • 执行时机:过滤器在请求进入Servlet容器时触发;拦截器在Spring MVC的DispatcherServlet处理请求时介入。
  • 依赖关系:拦截器依赖于Spring上下文,可访问Bean和注解;过滤器独立于Spring,无法直接使用Spring功能。

例如,如果你需要记录Controller方法的执行时间,拦截器是理想选择。但如果要处理所有HTTP请求(包括静态文件),则使用过滤器更合适。

为什么使用拦截器?

拦截器提供以下优势:

  • 代码重用:将通用逻辑(如认证)集中到拦截器,避免控制器代码膨胀。
  • 灵活性:可在请求处理的不同阶段(preHandle, postHandle, afterCompletion)注入逻辑。
  • 集成性:与Spring生态无缝集成,支持依赖注入和AOP。
  • 性能优化:轻量级实现,不影响应用性能。

常见使用场景包括:

  • 日志记录:记录请求参数、响应时间。
  • 权限控制:验证用户权限,拒绝未授权请求。
  • 跨域处理:设置CORS头。
  • 性能监控:测量方法执行耗时。
  • 输入验证:在控制器前校验参数。

Spring Boot中实现拦截器

在Spring Boot中,实现拦截器需两步:创建拦截器类和注册拦截器。Spring Boot自动配置了Spring MVC,简化了这一过程。

步骤1: 创建拦截器类

拦截器必须实现HandlerInterceptor接口,该接口定义了三个方法:

  • preHandle(HttpServletRequest request, HttpServletResponse response, Object handler):在控制器方法执行前调用。返回booleantrue继续流程,false中断请求(常用于权限拒绝)。
  • postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView):在控制器方法执行后、视图渲染前调用。可修改ModelAndView。
  • afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex):在请求完成后调用,用于资源清理或异常处理。

以下是一个基础拦截器示例,记录请求信息:

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
public class LoggingInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);
        System.out.println("Request URL: " + request.getRequestURL());
        System.out.println("Start Time: " + startTime);
        return true; // 继续执行
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        long startTime = (Long) request.getAttribute("startTime");
        long endTime = System.currentTimeMillis();
        long executeTime = endTime - startTime;
        System.out.println("Request processed in: " + executeTime + "ms");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        if (ex != null) {
            System.out.println("Request failed with exception: " + ex.getMessage());
        } else {
            System.out.println("Request completed successfully");
        }
    }
}

此拦截器在preHandle中记录请求开始时间,在postHandle中计算执行时间,在afterCompletion中处理异常。@Component注解使其成为Spring Bean,便于依赖注入。

步骤2: 注册拦截器

创建拦截器后,需通过配置类注册到Spring MVC。使用WebMvcConfigurer接口的addInterceptors方法:

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 InterceptorConfig implements WebMvcConfigurer {

    @Autowired
    private LoggingInterceptor loggingInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loggingInterceptor)
                .addPathPatterns("/**") // 应用于所有路径
                .excludePathPatterns("/static/**"); // 排除静态资源
    }
}

这里,InterceptorRegistry用于添加拦截器并指定路径模式。addPathPatterns("/**")表示拦截所有请求,excludePathPatterns排除特定路径(如静态文件)。

详细解释拦截器方法

每个HandlerInterceptor方法有特定用途和参数,理解它们对有效使用拦截器至关重要。

preHandle 方法

preHandle在控制器方法执行前调用,是最常用的方法。

  • 参数
    • HttpServletRequest request:当前HTTP请求对象。
    • HttpServletResponse response:HTTP响应对象,可用于设置头或状态码。
    • Object handler:处理请求的处理器(通常是HandlerMethod)。
  • 返回值boolean。返回true继续流程;返回false中断请求,常用于权限验证失败。
  • 用途:执行前置检查,如认证或参数验证。
  • 示例:实现一个简单认证拦截器:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    String authToken = request.getHeader("Authorization");
    if (authToken == null || !authToken.equals("valid-token")) {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
        return false; // 中断请求
    }
    return true; // 继续
}

此代码检查Authorization头,无效则返回401错误。

postHandle 方法

postHandle在控制器方法执行后调用,但在视图渲染前。

  • 参数:包括ModelAndView modelAndView,可修改模型数据或视图名称。
  • 返回值:无。
  • 用途:修改响应数据或添加通用属性。
  • 示例:添加全局属性到模型:
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    if (modelAndView != null) {
        modelAndView.addObject("serverTime", new Date());
    }
}

这为所有视图添加当前服务器时间。

afterCompletion 方法

afterCompletion在请求完成后调用,无论是否异常。

  • 参数:包括Exception ex,捕获控制器抛出的异常。
  • 返回值:无。
  • 用途:资源清理或异常日志记录。
  • 示例:记录异常信息:
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    if (ex != null) {
        logger.error("Request error: ", ex);
    }
}

常见用例与代码示例

拦截器适用于多种场景。以下是几个实用例子,每个都附完整代码。

用例1: 日志记录拦截器

记录请求详情和执行时间,帮助调试和监控。

@Component
public class PerformanceLoggingInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(PerformanceLoggingInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        request.setAttribute("startTime", System.currentTimeMillis());
        logger.info("Request started: {} {}", request.getMethod(), request.getRequestURI());
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        long startTime = (Long) request.getAttribute("startTime");
        long duration = System.currentTimeMillis() - startTime;
        logger.info("Request completed: {}ms, Status: {}", duration, response.getStatus());
    }
}

注册时,可排除健康检查端点:

registry.addInterceptor(performanceLoggingInterceptor)
       .addPathPatterns("/api/**")
       .excludePathPatterns("/actuator/health");

用例2: 认证拦截器

验证JWT令牌,保护敏感API。

@Component
public class AuthInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtUtil jwtUtil; // 假设JwtUtil是自定义工具类

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Missing token");
            return false;
        }
        try {
            String username = jwtUtil.validateToken(token.substring(7));
            request.setAttribute("username", username); // 存储用户信息供控制器使用
            return true;
        } catch (Exception e) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid token");
            return false;
        }
    }
}

此拦截器提取并验证JWT,无效则拒绝请求。

用例3: 跨域拦截器

设置CORS头,解决跨域问题。

@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", "GET, POST, PUT, DELETE");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
        return true;
    }
}

虽然Spring Boot有内置CORS配置,但拦截器提供更细粒度控制。

高级主题

掌握基础后,探索高级用法能提升拦截器效能。

多个拦截器的顺序

当注册多个拦截器时,执行顺序由注册顺序决定。preHandle按注册顺序执行,postHandleafterCompletion逆序执行。

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(loggingInterceptor).order(1); // 先执行
    registry.addInterceptor(authInterceptor).order(2); // 后执行
}

顺序重要:例如,先日志后认证,确保日志记录所有请求。

路径模式匹配

使用Ant风格路径模式:

  • addPathPatterns("/api/**"):匹配所有/api路径。
  • excludePathPatterns("/public/**"):排除公共路径。 支持通配符:*(单段路径),**(多段路径)。

使用注解驱动拦截器

结合自定义注解,实现更灵活的控制。例如,创建@RequireAuth注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireAuth {
}

在拦截器中检查注解:

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    if (handler instanceof HandlerMethod) {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        if (handlerMethod.getMethodAnnotation(RequireAuth.class) != null) {
            // 执行认证逻辑
        }
    }
    return true;
}

然后在控制器方法上使用@RequireAuth,仅拦截带注解的方法。

异常处理

afterCompletion中处理异常,但注意:拦截器不替代全局异常处理器(@ControllerAdvice)。结合使用:

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    if (ex != null) {
        // 记录日志,但实际处理交给@ControllerAdvice
    }
}

最佳实践

遵循这些实践确保拦截器高效可靠:

  1. 保持轻量:避免在拦截器中执行耗时操作(如数据库查询),以免影响性能。
  2. 单一职责:每个拦截器只处理一个关注点(如只做日志或只做认证)。
  3. 异常处理:在afterCompletion中记录异常,但用全局机制处理。
  4. 路径排除:总是排除静态资源或不需拦截的路径。
  5. 测试覆盖:编写单元测试验证拦截器行为。
  6. 避免状态存储:不在拦截器中存储请求状态;用request.setAttribute传递数据。

测试拦截器

测试是确保拦截器正确的关键。使用Spring的MockMvc模拟HTTP请求:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
public class AuthInterceptorTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testUnauthorizedAccess() throws Exception {
        mockMvc.perform(get("/secure"))
               .andExpect(status().isUnauthorized());
    }

    @Test
    public void testAuthorizedAccess() throws Exception {
        mockMvc.perform(get("/secure").header("Authorization", "Bearer valid-token"))
               .andExpect(status().isOk());
    }
}

此测试验证认证拦截器:无令牌时返回401,有有效令牌时通过。

常见问题与解决

  1. 拦截器不生效:检查是否注册正确;确保路径模式匹配;确认类上有@Component和配置类有@Configuration
  2. 顺序问题:多个拦截器时,显式设置order值。
  3. 性能影响:优化逻辑,避免阻塞操作。
  4. 与过滤器冲突:如果同时使用过滤器和拦截器,确保它们逻辑不重叠。

结论

Spring Boot拦截器是处理横切关注点的强大工具,通过实现HandlerInterceptor接口和注册配置,可轻松集成日志、认证等功能。本文覆盖了从基础概念到高级用法的全指南,包括代码示例、测试方法和最佳实践。掌握这些,你将能构建更健壮、可维护的Spring Boot应用。记住,拦截器应与Spring的其他特性(如AOP)结合,以实现最佳架构。

正文到此结束
评论插件初始化中...
Loading...
本文目录