当前位置: 首页 > news >正文

Spring AOP 和 Spring 拦截器

Spring AOP 和 Spring 拦截器

  • 一、SpringBoot 拦截器
    • 1. 拦截器的定义与使用
      • 1.1 自定义拦截器
      • 1.2 注册拦截器
    • 2. 拦截路径配置
    • 3. 拦截器执行流程
    • 4. 登录校验功能实现
    • 5. Controller 示例:RequestMapping 用法
    • 6. Spring MVC 源码解析
    • 7. 设计模式中的适配器模式
      • 7.1 生活中的例子
      • 7.2 slf4j / log4j 桥接示例
  • 二、Spring AOP
    • 1. AOP 基本概念
      • 1.1 AOP 的应用场景
    • 2. Spring AOP 快速入门
      • 2.1 引入依赖
      • 2.2 示例:记录方法耗时
      • 参数与注解详解
    • 3. AOP 核心概念
      • 代理类型简述
    • 4. 通知类型(Advice)详解
      • 4.1 前置通知 - `@Before`
      • 4.2 后置通知 - `@After`
      • 4.3 返回后通知 - `@AfterReturning`
      • 4.4 异常通知 - `@AfterThrowing`
      • 4.5 环绕通知 - `@Around`
    • 5. 切点表达式详解
    • 6. 自定义注解 + @annotation
      • 6.1 定义注解
      • 6.2 定义切面
      • 6.3 使用注解
    • 7. 多切面优先级
    • 8. 实战案例
      • 8.1 方法耗时统计
      • 8.2 异常拦截
      • 8.3 通用日志打印
    • 9. 总结
  • 三、Spring AOP 和 Spring 拦截器有什么关系?
    • 1、Spring AOP
    • 2、Spring 拦截器
    • 3、执行链路对比
    • 4、关系总结
    • 5、结语

一、SpringBoot 拦截器

在日常的 SpringBoot 开发中,我们经常需要对请求做一些“统一功能处理”,比如:

  • 登录校验
  • 权限验证
  • 日志打印
  • 统一返回数据

如果每个接口里都写一遍判断逻辑,会非常麻烦且冗余。
这时就可以用 拦截器(Interceptor) 来统一处理。

本文带你从零开始学习 Spring 拦截器,逐步过渡到源码分析,并结合 适配器模式 理解 SpringMVC 的设计思想。


1. 拦截器的定义与使用

拦截器(Interceptor)是 Spring MVC 提供的核心功能之一,主要作用是在 请求到达 Controller 前后 进行拦截和处理。

1.1 自定义拦截器

实现 HandlerInterceptor 接口,重写其方法:

@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {log.info("preHandle - 目标方法执行前");return true; // 返回 true 表示放行,false 表示拦截}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {log.info("postHandle - 目标方法执行后");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {log.info("afterCompletion - 视图渲染完成后");}
}

1.2 注册拦截器

实现 WebMvcConfigurer 接口,重写 addInterceptors 方法:

@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**")        // 拦截所有请求.excludePathPatterns("/user/login", "/**/*.js", "/**/*.css", "/**/*.png", "/**/*.html"); // 排除路径}
}

2. 拦截路径配置

常见拦截路径规则如下:

拦截路径含义示例
/*一级路径/user /book
/**任意级路径/user/login /book/add
/book/*/book 下的一层路径/book/addBook
/book/**/book 下任意级路径/book/addBook/1 /book/list

3. 拦截器执行流程

拦截器的执行顺序如下图:

请求进入 → preHandle()↓ (放行=true)
Controller 方法执行↓
postHandle()↓
视图渲染↓
afterCompletion()
  • preHandle:Controller 方法执行前
  • postHandle:Controller 方法执行后
  • afterCompletion:整个请求完成后(包括视图渲染)

如果 preHandle 返回 false,请求将被拦截,后续方法不会执行。


4. 登录校验功能实现

在实际开发中,常见的需求是 拦截未登录用户

@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HttpSession session = request.getSession(false);if (session != null && session.getAttribute("USER") != null) {return true; // 已登录,放行}response.setStatus(401); // 未认证return false;}
}
  • 401 Unauthorized:表示请求未通过身份认证。

配置拦截器时,排除 /user/login 接口,避免死循环。


5. Controller 示例:RequestMapping 用法

拦截器配置好以后,我们来写一个简单的 Controller,测试是否生效:

@RestController
@RequestMapping("/user")
public class UserController {// 模拟登录接口,不拦截@PostMapping("/login")public String login(HttpSession session, @RequestParam String username) {session.setAttribute("USER", username);return "登录成功,欢迎 " + username;}// 被拦截的接口@GetMapping("/profile")public String profile(HttpSession session) {String user = (String) session.getAttribute("USER");return "当前登录用户: " + user;}// 普通测试接口@RequestMapping("/hello")public String hello(@RequestParam String name) {return "hello " + name + " , 时间戳: " + System.currentTimeMillis();}
}

👉 访问 /user/hello 时会被拦截器处理;
👉 访问 /user/login 时不会被拦截;
👉 登录成功后再访问 /user/profile,才能获取到用户信息。


6. Spring MVC 源码解析

所有请求都会先经过 DispatcherServlet,再分发到 Controller。
拦截器就是在 DispatcherServlet#doDispatch() 里生效的。

源码关键流程(简化):

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {// 1. 获取 Handler(执行链)HandlerExecutionChain mappedHandler = getHandler(request);// 2. 获取 HandlerAdapter(适配器)HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// 3. 执行拦截器 preHandleif (!mappedHandler.applyPreHandle(request, response)) {return; // 拦截返回}// 4. 执行目标 Controller 方法ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler());// 5. 执行拦截器 postHandlemappedHandler.applyPostHandle(request, response, mv);// 6. 渲染视图 + 执行 afterCompletionprocessDispatchResult(request, response, mappedHandler, mv, null);
}

可以看到:

  • 拦截器preHandle 在 Controller 之前执行
  • 拦截器postHandle 在 Controller 之后执行
  • 拦截器afterCompletion 在请求完成后执行

7. 设计模式中的适配器模式

DispatcherServlet 中,为什么要有 HandlerAdapter
因为不同的 Handler 类型(Controller、HttpRequestHandler、Servlet 等)需要一个 统一的适配入口

👉 这就是 适配器模式(Adapter Pattern)

7.1 生活中的例子

  • 出国旅行需要 电源转换插头
  • 旧手机耳机(3.5mm)需要 Type-C 转接头

7.2 slf4j / log4j 桥接示例

// slf4j API
interface Slf4jApi {void log(String message);
}// log4j 原始类(不兼容)
class Log4j {void log4jLog(String message) {System.out.println("Log4j打印: " + message);}
}// 适配器
class Slf4jLog4jAdapter implements Slf4jApi {private Log4j log4j;public Slf4jLog4jAdapter(Log4j log4j) {this.log4j = log4j;}@Overridepublic void log(String message) {log4j.log4jLog(message);}
}// 客户端
public class Slf4jDemo {public static void main(String[] args) {Slf4jApi logger = new Slf4jLog4jAdapter(new Log4j());logger.log("使用slf4j打印日志");}
}

通过适配器,不需要改动 log4j 的代码,就能让它兼容 slf4j 的接口。

Spring MVC 的 HandlerAdapter 就是这种思想的应用:

  • 目标类:各种 Handler(Controller)
  • 适配器:HandlerAdapter
  • 调用方:DispatcherServlet

二、Spring AOP

在日常开发中,我们经常遇到一些“横切关注点”的需求,例如:

  • 日志打印(记录请求参数、返回值、耗时)
  • 权限验证
  • 异常拦截与统一处理
  • 事务控制
  • 性能监控

如果这些代码散落在每一个业务方法里,既冗余又难维护。AOP(面向切面编程) 就是为了解决这类问题而生的。


1. AOP 基本概念

AOP(Aspect Oriented Programming):面向切面编程。
核心思想是将与业务逻辑无关的“共性问题”抽取出来,形成一个“切面(Aspect)”,并在需要的地方织入(Weaving)到业务代码中。

AOP 可以看作是 OOP(面向对象编程)的补充。OOP 关注“对象纵向结构”,AOP 关注“横向切面逻辑”。

1.1 AOP 的应用场景

  • 日志记录
  • 权限校验
  • 缓存处理
  • 异常监控
  • 性能统计
  • 事务管理

2. Spring AOP 快速入门

2.1 引入依赖

在 Spring Boot 项目 pom.xml 中添加:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Spring Boot 默认开启 AOP(底层基于 AspectJ 动态代理实现)。


2.2 示例:记录方法耗时

@Slf4j
@Aspect
@Component
public class TimeAspect {@Around("execution(* com.example.demo.service.*.*(..))")public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {long start = System.currentTimeMillis();Object result = pjp.proceed(); // 执行目标方法,可能抛异常long end = System.currentTimeMillis();log.info("方法 {} 执行耗时: {} ms", pjp.getSignature(), (end - start));return result; // 必须返回结果}
}

参数与注解详解

注解/参数作用
@Slf4jLombok 注解,自动生成日志对象 log
@Aspect声明当前类是切面类
@Component注入 Spring 容器,使切面生效
@Around("execution(...)")环绕通知,包裹目标方法,可在方法执行前后插入逻辑,可控制目标方法是否执行
ProceedingJoinPoint pjp环绕通知专用参数,可获取方法签名、参数,并执行目标方法
pjp.proceed()执行目标方法,必须调用,否则方法不会被执行,可能抛出异常
pjp.getSignature()获取方法签名(类名.方法名)
pjp.getArgs()获取方法参数

3. AOP 核心概念

名称解释示例
切点 (Pointcut)定义拦截规则execution(* com.example..service.*(..))
连接点 (JoinPoint)程序运行中的某个方法执行点UserService.getUser()
通知 (Advice)增强逻辑,在连接点执行的动作@Before@After@Around
切面 (Aspect)切点 + 通知的组合日志切面、权限切面
织入 (Weaving)把切面应用到目标对象的过程动态代理(JDK Proxy、CGLIB)

代理类型简述

  • JDK 动态代理:只能拦截接口方法,目标类必须实现接口。
  • CGLIB 代理:通过生成子类拦截类方法,也能拦截接口方法,但 final 类/方法无法拦截

Spring AOP 默认优先 JDK 代理,无接口时自动使用 CGLIB。


4. 通知类型(Advice)详解

4.1 前置通知 - @Before

在方法执行之前执行,无法阻止目标方法执行。

@Before("execution(* com.example.demo.service.*.*(..))")
public void beforeAdvice(JoinPoint jp) {log.info("Before - 方法开始: {}", jp.getSignature());
}

4.2 后置通知 - @After

在方法执行之后执行(无论正常返回或抛异常都会执行),可用于资源清理。

@After("execution(* com.example.demo.service.*.*(..))")
public void afterAdvice(JoinPoint jp) {log.info("After - 方法执行完成: {}", jp.getSignature());
}

4.3 返回后通知 - @AfterReturning

在方法正常返回后执行,可获取返回值。

@AfterReturning(value = "execution(* com.example.demo.service.*.*(..))", returning = "result")
public void afterReturningAdvice(Object result) {log.info("AfterReturning - 返回值: {}", result);
}

🔹 注意:returning 属性名必须与方法参数名一致,否则 Spring 找不到对应参数。

4.4 异常通知 - @AfterThrowing

在方法抛出异常后执行,可获取异常对象。

@AfterThrowing(value = "execution(* com.example.demo.service.*.*(..))", throwing = "ex")
public void afterThrowingAdvice(Exception ex) {log.error("AfterThrowing - 捕获异常: {}", ex.getMessage());
}

4.5 环绕通知 - @Around

包裹目标方法,可在执行前后添加逻辑,可控制目标方法是否执行。

@Around("execution(* com.example.demo.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {log.info("Around - 方法执行前");Object result = pjp.proceed(); // 执行目标方法,可能抛异常log.info("Around - 方法执行后");return result;
}

🔹 Tip:环绕通知执行顺序前于 @Before,其“后逻辑”晚于 @AfterReturning,实际日志顺序可能因切面顺序或代理类型不同略有差异。


5. 切点表达式详解

execution 用于匹配方法执行,其基本格式为:

execution([访问修饰符] 返回类型 包名.类名.方法名(参数列表) [异常])

拆解说明:

部分含义
访问修饰符(可选)publicprivate
返回类型* 表示任意返回类型
包名.类名.方法名(方法参数)指定要拦截的类和方法,可用 *.. 通配
异常(可选)匹配方法抛出的异常类型

示例:

@Pointcut("execution(* com.example.demo.controller.*.*(..))")
  • * → 任意返回类型
  • com.example.demo.controller.*.*(..) → 拦截 controller 包下所有类的所有方法,参数不限

🔹 Tip:

  • .. 可匹配任意层级包或任意数量参数
  • * 可匹配任意返回类型或任意方法名

6. 自定义注解 + @annotation

6.1 定义注解

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

6.2 定义切面

@Slf4j
@Aspect
@Component
public class AnnotationAspect {@Around("@annotation(com.example.demo.annotation.LogExecutionTime)")public Object logTime(ProceedingJoinPoint pjp) throws Throwable {long start = System.currentTimeMillis();Object result = pjp.proceed();long end = System.currentTimeMillis();log.info("方法 {} 执行耗时 {} ms", pjp.getSignature(), (end - start));return result;}
}

6.3 使用注解

@Service
public class UserService {@LogExecutionTimepublic void getUserList() {try { Thread.sleep(200); } catch (InterruptedException ignored) {}}
}

调用 getUserList() 时会自动统计方法耗时。


7. 多切面优先级

使用 @Order 控制执行顺序,数值越小优先级越高:

@Order(1)
@Aspect
@Component
public class FirstAspect { ... }@Order(2)
@Aspect
@Component
public class SecondAspect { ... }

执行顺序:

  1. FirstAspect @Before
  2. SecondAspect @Before
  3. 方法执行
  4. SecondAspect @After
  5. FirstAspect @After

8. 实战案例

8.1 方法耗时统计

@Around("execution(* com.example.demo.service.*.*(..))")
public Object timeCost(ProceedingJoinPoint pjp) throws Throwable {long start = System.nanoTime();Object result = pjp.proceed();long end = System.nanoTime();log.info("[耗时监控] {} 执行耗时: {} ms", pjp.getSignature(), (end - start)/1_000_000);return result;
}

8.2 异常拦截

@AfterThrowing(pointcut = "execution(* com.example.demo.service.*.*(..))", throwing = "ex")
public void handleException(Exception ex) {log.error("[异常拦截] 捕获到异常: {}", ex.getMessage());
}

8.3 通用日志打印

@Before("execution(* com.example.demo.controller.*.*(..))")
public void logRequest(JoinPoint jp) {log.info("[请求日志] 方法: {}, 参数: {}", jp.getSignature(), Arrays.toString(jp.getArgs()));
}

9. 总结

本文系统介绍了 Spring AOP

  1. AOP 概念与应用场景
  2. Spring AOP 快速入门(耗时统计示例)
  3. 核心概念:切点、连接点、通知、切面
  4. 通知类型详解(@Before / @After / @AfterReturning / @AfterThrowing / @Around)
  5. 切点表达式(execution、@annotation)
  6. 自定义注解的使用方式
  7. 多个切面的优先级控制(@Order)
  8. 实战案例:耗时监控、异常拦截、日志打印
  9. 日志运行效果示例

三、Spring AOP 和 Spring 拦截器有什么关系?

在实际开发中,我们经常会同时接触 Spring AOPSpring MVC 拦截器,它们都能实现“横切逻辑”,比如日志、权限校验、异常处理。但两者之间经常被混淆。本文简单梳理一下它们的关系和区别。


1、Spring AOP

Spring AOP 属于 Spring 核心功能,本质是通过 动态代理(JDK Proxy / CGLIB)方法调用的前后 织入额外逻辑。

  • 作用范围:任意 Spring 管理的 Bean 方法(Controller、Service、DAO)。
  • 常见场景:日志打印、事务控制、性能监控、统一异常处理。
  • 特点:与 Web 无关,任何 Spring 项目都能用。

2、Spring 拦截器

Spring 拦截器(HandlerInterceptor)属于 Spring MVC 模块,主要作用在 Web 请求处理链 上。

  • 拦截时机

    1. 请求进入 Controller 之前(preHandle
    2. Controller 方法执行之后(postHandle
    3. 请求完成后(afterCompletion
  • 常见场景:登录校验、权限验证、接口限流、请求日志、跨域处理。

  • 特点:依赖 SpringMVC,仅 Web 项目可用。


3、执行链路对比

来看一条典型的请求处理链路:

HTTP 请求↓
Filter (Servlet 过滤器)      ← 拦截最早↓
Interceptor (Spring 拦截器) ← 拦截 Controller 前后↓
Controller 方法执行↓       ↑└── AOP 可以在方法前后织入逻辑↓
Service / DAO 方法↓
返回响应

👉 可以看到:

  • 拦截器 更偏向 请求级别 的控制;
  • AOP 更偏向 方法级别 的增强;
  • 两者位置不同,但都能实现“横切逻辑”。

4、关系总结

  • 共同点

    • 都能实现非业务逻辑的统一处理。
    • 都是“拦截 + 增强”的思想。
  • 不同点

    特性Spring AOPSpring 拦截器
    所属模块Spring AOPSpring MVC
    作用范围Bean 方法(任意层)Web 请求(Controller 前后)
    实现原理动态代理(JDK Proxy/CGLIB)责任链模式(HandlerInterceptor)
    常用场景日志、事务、异常、性能监控登录校验、权限验证、接口限流

5、结语

Spring AOP 和 Spring 拦截器没有直接继承或实现关系,它们是 两个平行但互补的机制

  • 想拦截 请求入口 → 用拦截器;
  • 想拦截 方法调用 → 用 AOP;
  • 想拦截 所有请求(包括静态资源) → 用 Filter。
http://www.xdnf.cn/news/1327645.html

相关文章:

  • LeetCode 100 -- Day2
  • JVM垃圾收集器
  • ts 引入类型 type 可以省略吗
  • sfc_os!SfcValidateDLL函数分析之cache文件版本
  • python的社区互助养老系统
  • 【实时Linux实战系列】实时平台下的图像识别技术
  • 微软AD国产化替换倒计时——不是选择题,而是生存题
  • 初识线段树
  • 电影购票+票房预测系统 - 后端项目介绍(附源码)
  • 114. 二叉树展开为链表
  • 华为云之开发者空间云主机使用体验【玩转华为云】
  • RH134 运行容器知识点
  • 【QT入门到晋级】进程间通信(IPC)-socket(包含性能优化案例)
  • 面试题储备-MQ篇 3-说说你对Kafka的理解
  • 如何使用DeepSeek解析长pdf的文本
  • 需求开发广告系列 Gmail广告投放教程
  • 跨域信息结构:四界统一的动态机制
  • 大模型 + 垂直场景:搜索/推荐/营销/客服领域开发新范式与技术实践
  • 机器学习概念(面试题库)
  • 智慧校园中IPTV融合对讲:构建高效沟通新生态
  • [激光原理与应用-305]:光学设计 - 单个光学元件(纯粹的光学元件)的设计图纸的主要内容、格式与示例
  • 北京国标调查:以科学民意调查赋能决策,架起沟通与信任的桥梁(满意度调查)
  • PicoShare 文件共享教程:cpolar 内网穿透服务实现跨设备极速传输
  • 数控滑台的功能与应用范围
  • 如何用给各种IDE配置R语言环境
  • 大数据云原生是什么
  • 如何计算 PCM 音频与 YUV/RGB 原始视频文件大小?
  • 【AI】算法环境-显卡、GPU、Cuda、NVCC和cuDNN的区别与联系
  • JVM垃圾回收(GC)深度解析:原理、调优与问题排查
  • 牛津大学xDeepMind 自然语言处理(2)