SpringBoot 拦截器 (Interceptor) 与切面 (AOP):示例、作用、及适用场景

SpringBoot 拦截器 (Interceptor) 与切面 (AOP):示例、作用、及适用场景

一、拦截器 (Interceptor):SpringMVC 层的请求拦截器

拦截器是SpringMVC 框架的组件 ,基于Java 动态代理 实现,作用于Web 层(Controller 层),仅能拦截 HTTP 请求的处理过程,生命周期与 SpringMVC 的请求处理流程绑定。

核心作用

  1. 拦截 Controller 层的 HTTP 请求(如 GET/POST 请求);
  2. 实现请求前的预处理(如登录校验、token 验证、跨域配置);
  3. 实现请求后的后置处理(如响应数据统一封装);
  4. 实现请求完成后的最终处理(如资源释放、请求日志记录)。

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. 提取通用横切逻辑(与业务无关、可复用的逻辑),如日志记录、事务管理、耗时统计、异常统一捕获;
  2. 实现业务代码与横切逻辑的解耦,无需在业务方法中硬编码通用逻辑;
  3. 可对任意方法进行增强(前置增强、后置增强、环绕增强、异常增强等),支持灵活的切点表达式配置。

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),无需额外处理:

  1. 登录 / 权限校验:基于请求头 / 参数验证 token、会话、用户身份;
  2. 跨域配置:统一设置响应头(Access-Control-Allow-Origin 等);
  3. 请求参数预处理:统一解析请求头、请求参数,如防 XSS 处理;
  4. 响应数据统一封装:对 Controller 返回的结果统一添加 code/msg/data 格式;
  5. Web 层日志记录:记录请求 URI、请求方式、IP、响应状态等 Web 相关信息;
  6. 接口限流:基于 IP / 请求频率限制 HTTP 请求(如每秒最大请求数)。

切面 (AOP):适用于通用、跨层的横切逻辑

所有与业务无关、需要在多层级复用的通用横切逻辑,优先使用切面,解耦效果最佳,无环境依赖:

  1. 全局日志记录:记录方法的入参、出参、执行耗时(适用于 Service/Dao 层);
  2. 事务管理 :通过@Transactional(底层基于 AOP)实现方法级事务控制;
  3. 方法级权限校验:基于自定义注解(如 @RequiresPermission)实现方法访问控制;
  4. 异常统一捕获:捕获指定层的异常,统一封装异常响应(如 Service 层业务异常);
  5. 缓存控制:实现方法级缓存(如查询方法结果缓存,避免重复查询);
  6. 性能监控:统计指定方法的执行耗时、调用次数,做系统性能分析;
  7. 非 Web 环境的横切逻辑:如定时任务、后台服务的方法增强(无 Web 上下文,拦截器无法使用)。

实际项目中,拦截器和切面并非互斥 ,而是协同工作,各自负责专属领域的横切逻辑,形成完整的横切体系:

例如:一个接口的完整处理流程中:

  1. 拦截器:在请求进入 Controller 前,完成登录校验、token 验证(Web 层专属);
  2. 切面:在 Service 层方法执行时,完成耗时统计、事务管理、异常捕获(通用跨层);
  3. 拦截器:在请求完成后,完成响应数据封装、Web 日志记录(Web 层专属)。

这种协同模式既保证了 Web 相关逻辑的高效处理,又实现了通用逻辑的跨层复用,是 SpringBoot 项目的标准实践。

相关推荐
不会c+2 小时前
Maven私服的搭建与使用
java·maven
weixin_436525072 小时前
若依多租户版: RuoYi-Vue-Plus
java
野生技术架构师2 小时前
深度拆解JVM垃圾回收:可达性分析原理+全类型回收器执行机制
java·开发语言·jvm
小六花s2 小时前
SQL注入笔记
数据库·笔记·sql
yufuu982 小时前
Python在金融科技(FinTech)中的应用
jvm·数据库·python
中科院提名者2 小时前
如何配置go环境并用vscode运行
开发语言·后端·golang
OnYoung2 小时前
持续集成/持续部署(CI/CD) for Python
jvm·数据库·python
2301_822377652 小时前
高级爬虫技巧:处理JavaScript渲染(Selenium)
jvm·数据库·python
qq_12498707532 小时前
基于springboot+vue的家乡特色旅游宣传推荐系统(源码+论文+部署+安装)
java·前端·vue.js·spring boot·毕业设计·计算机毕设·计算机毕业设计