Spring AOP原理简析

AspectJ用法 里介绍了 spring AOP 的用法。这里以切面LogAspect的生效流程,简单分析下SpringAOP原理。

切面LogAspect 定义如下:

java 复制代码
@Aspect
@Component
public class LogAspect {
    @Before(value = "execution(String org.example.topic.aop.demo.StudentServiceImpl.*(String))")
    public void logBeforeProceed(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
 
        String output = String.format("LogAspect.logBefore: methodName=%s, args=%s", methodName, args);
        System.out.println(output);
    }

LogAspect 类是 Spring AOP(面向切面编程)的一个典型应用,其工作原理涉及 Spring 容器的 Bean 生命周期、代理机制以及 AOP 的织入过程。


1. @Aspect 和 @Component 的作用

java 复制代码
@Aspect
@Component
public class LogAspect { ... }
  • @Component:告诉 Spring 容器这是一个普通的 Spring Bean,Spring 会在启动时通过组件扫描(如 @ComponentScan)将其注册为一个 Bean。
  • @Aspect:表明这个类是一个切面(Aspect),即包含通知(Advice)和切点(Pointcut)的逻辑单元。Spring AOP 会特别处理带有 @Aspect 注解的 Bean。

✅ 所以 LogAspect 本身就是一个 Spring 管理的 Bean,并且被识别为切面。


2. 切点(Pointcut)定义

java 复制代码
@Before(value = "execution(String org.example.topic.aop.demo.StudentServiceImpl.*(String))")

这是一个 前置通知(Before Advice),其切点表达式含义如下:

  • execution(...):匹配方法执行连接点。
  • String:返回类型必须是 String
  • org.example.topic.aop.demo.StudentServiceImpl.*:目标类是 StudentServiceImpl,任意方法名(*)。
  • (String):方法参数必须是单个 String 类型。

✅ 这个切点会匹配 StudentServiceImpl 中所有返回 String、接受一个 String 参数的方法。


3. Spring 如何创建代理(Proxy)

Spring AOP 是基于 代理模式 实现的。当容器发现某个 Bean 需要被 AOP 切面增强时,会为其创建一个 代理对象,而不是直接返回原始 Bean。

3.1 代理类型选择:
  • 如果目标类(如 StudentServiceImpl实现了接口 ,Spring 默认使用 JDK 动态代理(基于接口)。
  • 如果目标类 没有实现接口 ,Spring 会使用 CGLIB 代理(通过继承目标类生成子类)。

📌 假设 StudentServiceImpl 没有实现接口,Spring 会用 CGLIB 生成一个 StudentServiceImpl$$EnhancerBySpringCGLIB 子类作为代理。

3.2 代理的作用:
  • 当你从 Spring 容器获取 StudentServiceImpl 的 Bean 时,实际拿到的是 代理对象
  • 调用代理对象的方法时,会先经过 AOP 拦截器链(包括你的 @Before 通知),再调用原始方法。

4. AOP 织入(Weaving)过程

在 Spring 容器启动过程中,会执行以下关键步骤:

  1. Bean 扫描与注册

    • LogAspect@Component 注册为 Bean。
    • StudentServiceImpl 也被注册为 Bean(假设它也有 @Service@Component)。
  2. BeanPostProcessor 处理

    Spring 使用 AnnotationAwareAspectJAutoProxyCreator(一个特殊的 BeanPostProcessor)来处理 AOP:

    • 它会扫描所有 @Aspect Bean(如 LogAspect)。
    • 分析其中的切点表达式。
    • 检查其他普通 Bean(如 StudentServiceImpl)是否匹配这些切点。
  3. 创建代理

    • 如果 StudentServiceImpl 匹配 LogAspect 中的切点,AnnotationAwareAspectJAutoProxyCreator 会在 postProcessAfterInitialization 阶段替换原始 Bean 为代理对象
  4. 方法调用时的拦截

    • 当你调用 studentService.someMethod("hello") 时:
      • 实际调用的是代理对象的方法。
      • 代理对象内部会构建一个 拦截器链(Interceptor Chain)
      • 首先执行 logBeforeProceed(因为是 @Before)。
      • 然后调用原始 StudentServiceImpl 的目标方法。

5. JoinPoint 的作用

java 复制代码
public void logBeforeProceed(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();
    List<Object> args = Arrays.asList(joinPoint.getArgs());
    ...
}
  • JoinPoint 是 AOP 中的"连接点",代表程序执行过程中的一个点(如方法调用)。
  • Spring AOP 会自动将当前方法调用的上下文(方法名、参数等)封装到 JoinPoint 对象中,并传入通知方法。

6. 流程总结

java 复制代码
Spring 启动
   ↓
扫描 @Component → 注册 LogAspect 和 StudentServiceImpl 为 Bean
   ↓
AnnotationAwareAspectJAutoProxyCreator 发现 @Aspect
   ↓
解析切点表达式:execution(String StudentServiceImpl.*(String))
   ↓
检查 StudentServiceImpl 是否匹配 → 是!
   ↓
为 StudentServiceImpl 创建代理(JDK/CGLIB)
   ↓
容器中注入的是代理对象
   ↓
调用 studentService.xxx("arg")
   ↓
代理拦截 → 执行 LogAspect.logBeforeProceed(JoinPoint)
   ↓
输出日志
   ↓
调用原始 StudentServiceImpl.xxx("arg")

补充说明

  • 仅对 Spring Bean 有效 :AOP 只对 Spring 容器管理的 Bean 生效。如果 new StudentServiceImpl() 手动创建对象,AOP 不会生效。
  • 代理限制 :CGLIB 无法代理 final 方法;JDK 代理只能代理接口方法。
  • 性能:AOP 通过动态代理实现,有一定开销,但通常可忽略。

通过以上机制,LogAspect 就能在不修改 StudentServiceImpl 代码的情况下,透明地在方法调用前插入日志逻辑,体现了 AOP 的"横切关注点"分离思想。

7. 相关文档

  1. AspectJ用法: https://blog.csdn.net/taotiezhengfeng/article/details/149515395

  2. .Spring AOP切点表达式的关键词梳理: https://blog.csdn.net/taotiezhengfeng/article/details/155744545

  3. HandlerInterceptor 与 AOP 对比:https://blog.csdn.net/taotiezhengfeng/article/details/155452970

相关推荐
okseekw2 小时前
Maven从入门到实战:核心概念+配置详解+避坑指南
java·后端
tryxr2 小时前
ReentrantLock 与 synchronized 的区别
java··reentrantlock
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ2 小时前
日志打印配置:logback-spring.xml配置;info和error完全区分了,并且按时间拆分了
xml·spring·logback
Java爱好狂.2 小时前
Java面试Redis核心知识点整理!
java·数据库·redis·分布式锁·java面试·后端开发·java八股文
sheji34163 小时前
【开题答辩全过程】以 基于Java的应急安全学习平台的设计与实现为例,包含答辩的问题和答案
java·开发语言·学习
程序员小假3 小时前
我们来说一下消息的可靠性投递
java·后端
席之郎小果冻3 小时前
【04】【创建型】【聊一聊,建造者模式】
java·前端·建造者模式
原来是好奇心3 小时前
深入Spring Boot源码(四):Starter机制与依赖管理深度解析
java·源码·springboot·starter