SpringBoot 拦截器 (Interceptor) 与切面 (AOP):示例、作用、及适用场景
一、拦截器 (Interceptor):SpringMVC 层的请求拦截器
拦截器是SpringMVC 框架的组件 ,基于Java 动态代理 实现,作用于Web 层(Controller 层),仅能拦截 HTTP 请求的处理过程,生命周期与 SpringMVC 的请求处理流程绑定。
核心作用
- 拦截 Controller 层的 HTTP 请求(如 GET/POST 请求);
- 实现请求前的预处理(如登录校验、token 验证、跨域配置);
- 实现请求后的后置处理(如响应数据统一封装);
- 实现请求完成后的最终处理(如资源释放、请求日志记录)。
1.自定义拦截器:实现HandlerInterceptor接口
java
@Component
@Slf4j
public class CompanyContextInterceptor implements HandlerInterceptor {
@Resource
private RedisTemplate<String, Object> redisTemplate;
private static final String REDIS_TOKEN_KEY_PREFIX = "login:";
// 请求开始前:解析 Header,设置公司编码到上下文
@Override
public boolean preHandle(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) throws Exception {
// 1. 从请求头获取Token(Bearer Token格式)
String authHeader = request.getHeader("Authorization");
if (StringUtils.isBlank(authHeader) || !authHeader.startsWith("Bearer ")) {
// 未携带Token,直接通过(或返回401,根据业务需求)
return true;
}
String token = authHeader.substring(7); // 截取Bearer后的Token
// 2. 从Redis查询登录用户信息
String redisKey = REDIS_TOKEN_KEY_PREFIX + token;
LoginController.LoginWxUser loginWxUser = (LoginController.LoginWxUser) redisTemplate.opsForValue().get(redisKey);
if (loginWxUser == null || StringUtils.isBlank(loginWxUser.getPhone())) {
// Token无效/过期,直接通过(或返回401)
return true;
}
// 3. 核心:将loginWxUser存入request,key必须是「loginWxUser」(和接口一致!)
request.setAttribute("loginWxUser", loginWxUser);
// 可选:Token续期
redisTemplate.expire(redisKey, 7, TimeUnit.DAYS);
// 获取请求路径
String requestURI = request.getRequestURI();
// 检查是否为需要忽略的登录接口
if (requestURI.equals("/workaffairs/api/user/loginByPhone") ||
requestURI.startsWith("/workaffairs/api/user/loginByPhone")) {
return true; // 直接放行,不设置公司编码
}
// 从 Header 获取公司编码
String companyCode = request.getHeader("x-Company-Code");
if (companyCode != null && !companyCode.isEmpty()) {
CompanyContextHolder.setCompanyCode(companyCode);
log.info("获取到公司编码----" + companyCode);
}
return true; // 放行请求
}
// 请求结束后:清理上下文(必须执行,避免内存泄漏)
@Override
public void afterCompletion(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler, Exception ex) throws Exception {
CompanyContextHolder.clear();
}
}
2.拦截器配置类:实现WebMvcConfigurer接口
java
/**
* 拦截器配置类:注册拦截器并配置拦截规则
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
// 注入自定义拦截器
@Resource
private CompanyContextInterceptor companyContextInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(companyContextInterceptor)
// 拦截所有需要切换数据源的业务接口(根据你的实际路径调整)
.addPathPatterns("/purchase/**", "/delivery/**","/api/user/getSupplierByCompany")
// 排除不需要拦截的接口(比如登录接口)
.excludePathPatterns("/api/user/loginByPhone");
}
}
二、切面 (AOP):面向切面的通用横切逻辑实现
核心概念
切面是Spring AOP 框架的核心 ,基于JDK 动态代理(针对接口)+CGLIB 动态代理(针对类) 实现,是面向切面编程 的落地方式,可作用于Spring 容器管理的所有 Bean(Controller、Service、Dao、普通组件等),无层级限制。
核心作用
- 提取通用横切逻辑(与业务无关、可复用的逻辑),如日志记录、事务管理、耗时统计、异常统一捕获;
- 实现业务代码与横切逻辑的解耦,无需在业务方法中硬编码通用逻辑;
- 可对任意方法进行增强(前置增强、后置增强、环绕增强、异常增强等),支持灵活的切点表达式配置。
1.自定义切面
java
@Aspect // 声明为切面类
@Component // 交给Spring容器管理
@Slf4j
public class DbOperationLogAspect {
/**
* 定义切点:指定切面作用的目标方法
* 表达式规则:execution(访问修饰符 返回值 包名.类名.方法名(参数类型))
* 示例:匹配com.tgerp.workaffairs.mapper包下所有类的所有方法
*/
@Pointcut("execution(* com.tgerp.workaffairs.mapper..*(..))")
public void dbOperationPointCut() {
}
@Around("dbOperationPointCut()")
public Object aroundDbOperation(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 此时controller已经执行了setDbName,能拿到正确的数据库名
String currentDbName = DynamicDataSourceContextHolder.peek(); // 从数据源上下文拿
// }
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
// 打印日志
log.info("[workaffairs-数据库: {}] 执行数据库操作 - 类:{},方法:{}", currentDbName, className, methodName);
try {
return joinPoint.proceed();
} catch (Exception e) {
log.error("[workaffairs-数据库: {}] 数据库操作失败 - 类:{},方法:{},异常信息:{}",
currentDbName, className, methodName, e.getMessage(), e);
throw e;
}
}
}
三、拦截器与切面的适用场景
选型核心原则:根据横切逻辑的「作用范围」和「是否与 Web 请求相关」判断
拦截器 (Interceptor):仅适用于Web 相关的横切逻辑
所有与 HTTP 请求强绑定的场景,优先使用拦截器,因为可直接获取 Web 上下文(Request/Response),无需额外处理:
- 登录 / 权限校验:基于请求头 / 参数验证 token、会话、用户身份;
- 跨域配置:统一设置响应头(Access-Control-Allow-Origin 等);
- 请求参数预处理:统一解析请求头、请求参数,如防 XSS 处理;
- 响应数据统一封装:对 Controller 返回的结果统一添加 code/msg/data 格式;
- Web 层日志记录:记录请求 URI、请求方式、IP、响应状态等 Web 相关信息;
- 接口限流:基于 IP / 请求频率限制 HTTP 请求(如每秒最大请求数)。
切面 (AOP):适用于通用、跨层的横切逻辑
所有与业务无关、需要在多层级复用的通用横切逻辑,优先使用切面,解耦效果最佳,无环境依赖:
- 全局日志记录:记录方法的入参、出参、执行耗时(适用于 Service/Dao 层);
- 事务管理 :通过
@Transactional(底层基于 AOP)实现方法级事务控制; - 方法级权限校验:基于自定义注解(如 @RequiresPermission)实现方法访问控制;
- 异常统一捕获:捕获指定层的异常,统一封装异常响应(如 Service 层业务异常);
- 缓存控制:实现方法级缓存(如查询方法结果缓存,避免重复查询);
- 性能监控:统计指定方法的执行耗时、调用次数,做系统性能分析;
- 非 Web 环境的横切逻辑:如定时任务、后台服务的方法增强(无 Web 上下文,拦截器无法使用)。
实际项目中,拦截器和切面并非互斥 ,而是协同工作,各自负责专属领域的横切逻辑,形成完整的横切体系:
例如:一个接口的完整处理流程中:
- 拦截器:在请求进入 Controller 前,完成登录校验、token 验证(Web 层专属);
- 切面:在 Service 层方法执行时,完成耗时统计、事务管理、异常捕获(通用跨层);
- 拦截器:在请求完成后,完成响应数据封装、Web 日志记录(Web 层专属)。
这种协同模式既保证了 Web 相关逻辑的高效处理,又实现了通用逻辑的跨层复用,是 SpringBoot 项目的标准实践。