Spring Boot Filter、Interceptor、AOP 的使用选型

8 阅读5分钟

Spring Boot Filter、Interceptor、AOP 的使用选型

在 Spring Boot 应用程序开发中,Filter(过滤器)、Interceptor(拦截器)和 AOP(面向切面编程)是三种常见的横切关注点实现方式。

1、基本概念与执行顺序

1.1、 Filter(过滤器)

Filter 是 Servlet 规范中定义的组件,位于 Web 容器中,在请求到达 Servlet 之前和响应返回客户端之前进行处理。

@Component
@Slf4j
public class LogFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
​
        log.info("当前请求URL: {}", req.getRequestURI());
​
        // 继续执行过滤器链
        chain.doFilter(request, response);
​
        log.info("请求响应已完成");
    }
}

1.2、Interceptor(拦截器)

Interceptor 是 Spring MVC 框架提供的组件,位于 DispatcherServlet 之后,Controller 处理之前。

@Component
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
        // 在Controller方法执行前调用
        String token = request.getHeader("token");
        if (StringUtils.isEmpty(token)) {
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            return false;
        }
        return true;
    }
​
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response,
                           Object handler, ModelAndView modelAndView) throws Exception {
        // 在Controller方法执行后,视图渲染前调用
    }
​
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        // 在整个请求完成后调用
    }
}

1.3、 AOP(面向切面编程)

AOP 是 Spring 框架提供的一种编程范式,可以在不修改源代码的情况下为方法添加功能。

@Aspect
@Component
@Slf4j
public class PerformanceAspect {
    @Around("execution(* com.lin.service.*.*(..))")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
​
        Object result = joinPoint.proceed();
​
        long executionTime = System.currentTimeMillis() - start;
        log.info("{} 执行耗时 {} ms", joinPoint.getSignature(), executionTime);
​
        return result;
    }
}

1.4、执行顺序

请求处理的顺序为:Filter -> Interceptor -> AOP -> Controller -> AOP -> Interceptor -> Filter

2、使用场景对比

特性FilterInterceptorAOP
作用范围所有请求匹配的URL请求指定的方法
实现层面Servlet容器Spring MVCSpring容器
能否获取方法及类信息
应用场景请求过滤、字符编码等用户认证、日志记录等事务、日志、权限等

3、使用场景分析

3.1、 Filter 适用场景

  • 请求/响应预处理:如字符编码设置、CORS跨域处理
  • 请求内容转换:如请求体解析、加解密
  • 身份验证:如JWT令牌验证
  • 日志记录:记录所有请求的访问日志
  • 敏感信息过滤:过滤敏感词汇
@Component
public 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);
    }
}

3.2、 Interceptor 适用场景

  • 用户认证:检查用户是否已登录
  • 权限控制:检查用户是否有权限访问特定资源
  • 性能监控:监控控制器方法执行时间
  • 日志记录:记录请求处理过程中的详细信息
  • 国际化处理:根据请求设置语言环境

以用户权限为例,首先自定义注解

@Target(ElementType.METHOD) // 只能用在方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时保留
public @interface RequiresPermission {
    String role() default "user"; // 注解属性
}

在 Controller 上使用注解

public class TestController {
    // 使用自定义注解
    @RequiresPermission(role = "user")
    @GetMapping("/secure")
    public String secureEndpoint() {
        return "Admin Access Granted!";
    }
​
    // 未使用注解的方法
    @GetMapping("/public")
    public String publicEndpoint() {
        return "Public Access";
    }
}

创建拦截器 PermissionInterceptor

@Component
public class PermissionInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
​
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        RequiresPermission annotation = handlerMethod.getMethodAnnotation(RequiresPermission.class);
​
        if (annotation != null) {
            String requiredPermission = annotation.role();
            // 检查用户权限
            if (!hasPermission(request, requiredPermission)) {
                response.setStatus(HttpStatus.FORBIDDEN.value());
                return false;
            }
        }
​
        return true;
    }
​
    private boolean hasPermission(HttpServletRequest request, String permission) {
        // 权限检查逻辑
        return true;
    }
}

3.3、AOP 适用场景

  • 事务管理:方法执行前开启事务,执行后提交或回滚
  • 方法性能监控:记录方法执行时间
  • 日志记录:记录方法调用的参数和返回值
  • 缓存处理:方法执行前查询缓存,执行后更新缓存
  • 异常处理:统一处理特定异常

以日志记录为例,与 interceptor 类似,同样自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {
    String value() default "";
    boolean logParams() default true;   // 是否记录参数
    boolean logResult() default false;  // 是否记录返回值
}

创建订单相关实体以及 Service

Order 实体类

// 简单订单类
public class Order {
    private String orderId;
    private String customerId;
    private double amount;
​
    public Order(String orderId, String customerId, double amount) {
        this.orderId = orderId;
        this.customerId = customerId;
        this.amount = amount;
    }
​
    @Override
    public String toString() {
        return "Order{orderId='" + orderId + "', amount=" + amount + "}";
    }
}

订单处理类 Service

@Service
public class OrderService {
​
    @Loggable(value = "创建订单", logParams = true, logResult = true)
    public Order createOrder(String customerId, double amount) {
        // 模拟业务处理
        try {
            Thread.sleep(150); // 模拟处理时间
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
​
        // 返回模拟订单对象
        return new Order("ORD-" + System.currentTimeMillis(), customerId, amount);
    }
​
    @Loggable("处理订单支付")
    public boolean processPayment(String orderId, double amount) {
        // 模拟支付处理
        try {
            Thread.sleep(100); // 模拟处理时间
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
​
        // 模拟支付结果(金额大于100支付成功)
        return amount > 100;
    }
​
    @Loggable(value = "更新库存", logParams = false)
    public void updateInventory(String productId, int quantity) {
        // 模拟库存更新
        try {
            Thread.sleep(80); // 模拟处理时间
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
​
        // 可能抛出异常
        if (quantity < 0) {
            throw new IllegalArgumentException("库存数量不能为负数");
        }
    }
}

创建日志切面

@Aspect
@Component
public class LoggingAspect {
    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
​
    @Around("@annotation(com.lin.annotation.Loggable)")
    public Object logMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取方法签名和注解
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Loggable loggable = signature.getMethod().getAnnotation(Loggable.class);
​
        // 准备日志信息
        String methodName = signature.getDeclaringType().getSimpleName() + "." + signature.getName();
        String annotationValue = loggable.value();
        String logPrefix = annotationValue.isEmpty() ? methodName : annotationValue;
​
        // 记录方法入参
        if (loggable.logParams()) {
            String params = Arrays.toString(joinPoint.getArgs());
            logger.info("{} - 开始执行 | 参数: {}", logPrefix, params);
        } else {
            logger.info("{} - 开始执行", logPrefix);
        }
​
        // 创建计时器
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
​
        try {
            // 执行目标方法
            Object result = joinPoint.proceed();
​
            // 记录方法结果
            stopWatch.stop();
            if (loggable.logResult()) {
                logger.info("{} - 执行成功 | 耗时: {}ms | 结果: {}",
                        logPrefix, stopWatch.getTotalTimeMillis(), result);
            } else {
                logger.info("{} - 执行成功 | 耗时: {}ms",
                        logPrefix, stopWatch.getTotalTimeMillis());
            }
​
            return result;
        } catch (Exception e) {
            // 记录异常
            stopWatch.stop();
            logger.error("{} - 执行失败 | 耗时: {}ms | 异常: {} - {}",
                    logPrefix, stopWatch.getTotalTimeMillis(),
                    e.getClass().getSimpleName(), e.getMessage());
            throw e;
        }
    }
}

4、最佳实践

4.1、 组合使用

在实际项目中,这三种技术往往会组合使用:

Filter:处理通用的 Web 请求处理,如编码设置、CORS 配置 Interceptor:处理与用户认证、授权相关的功能 AOP:处理业务层面的横切关注点,如日志、性能监控、事务等

4.2、 性能考虑
  • Filter 和 Interceptor 主要用于 Web 层,每个请求只会经过一次
  • AOP 可能会作用于多个方法,影响更多的调用点
  • 在高并发场景下,应尽量减少 AOP 切面的数量和复杂度
4.3、 代码组织

将不同横切关注点的实现分离:

  • 认证授权相关的逻辑放在 Interceptor 中
  • 日志、监控等通用功能可以使用 AOP 实现
  • 请求预处理、编码设置等通用功能使用 Filter 实现

参考资料