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、使用场景对比
特性 | Filter | Interceptor | AOP |
---|---|---|---|
作用范围 | 所有请求 | 匹配的URL请求 | 指定的方法 |
实现层面 | Servlet容器 | Spring MVC | Spring容器 |
能否获取方法及类信息 | 否 | 是 | 是 |
应用场景 | 请求过滤、字符编码等 | 用户认证、日志记录等 | 事务、日志、权限等 |
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 实现
参考资料