🍃Spring源码阅读-AOP
type
status
date
slug
summary
tags
category
icon
password
原文
一、Spring AOP组成和核心注解1.1 Spring AOP 组成引入(Introduction) 2. 核心注解2.1 切面类与目标方法注解2.2 配置与扩展注解2.3 切入点表达式二、整体流程简述1. 启动阶段2. 切面的扫描与解析什么是Advisor3. 创建代理触发时机三、动态代理对象包含了什么信息四、执行流程方法的修饰什么静态切点和动态切点静态切点动态切点全流程总结Spring AOP的局限性
一、Spring AOP组成和核心注解
1.1 Spring AOP 组成
一个完整的切面通常有以下元素组成
- 切点(Pointcut):定义代码中哪些位置需要被拦截(如特定方法、注解或类),意思就是哪些方法需要被增强(通过 类、方明名等规则匹配)
表达式匹配:
execution(* com.example.service.*.*(..))
注解匹配:
@annotation(com.example.Loggable)
- 通知(Advice):定义在目标方法执行时触发的逻辑(如日志、事务等)
通知类型有前置通知、后置通知等。
- 切面类(Aspect):承载切点和通知的容器类,使用@Aspect注解标记。
- 引入(Introduction): 为目标类动态添加新的接口实现(属于特殊的通知类型)
- 织入(Weaving): 将切面逻辑插入目标位置的实现过程。
- 编译时织入(AspectJ):在编译阶段直接将切面逻辑写入字节码,无运行时性能损耗
- 类加载时织入(LTW):在类加载时动态增强字节码(需配合 Java Agent)
- 运行时织入:Spring AOP 默认行为
织入方式有:
引入(Introduction)
引入是一种特殊的通知类型,允许动态地为目标对象添加的接口实现,从而扩展其功能。它不同于常规通知(例如Before、Around),而是通过代理机制为对象“引入”新的行为和状态。
目标类无须显示实现接口,在不修改原有代码的基础上,可以通过AOP代理动态实现新接口,引入的方法由切面提供具体的实现。
这里的关键是:
- 目标对象必须由 Spring容器管理,否则无法应用引入这个功能,
- 只能引入接口不能引入具体的类(接口相当于标记作用)
- 只有通过代理调用时,引入才生效。
引入使用示例
2. 核心注解
2.1 切面类与目标方法注解
@Aspect
定义切面类
@Before
前置通知,在目标方法执行前执行
@After
最终通知,在目标方法后执行(无论是否抛出异常)
@AfterReturning
目标方法成功返回后
@
AfterThrowing
异常通知,在方法抛出异常后执行,可捕获异常对象
@Around
环绕通知,包裹目标方法执行,需调用proceed()
@Pointcut
定义可重用的切入点表达式。
2.2 配置与扩展注解
@EnableAspectJAutoProxy
使 Aspect 注解生效
@EnableAspectJAutoProxy
//
使 Aspect注解生效@EnableAspectJAutoProxy(proxyTargetClass = true)
//
默认为 jdk
动态代理 [
proxyTargetClass
= true
使用cglib
动态代理]
默认情况下使用 Jdk 动态代理的情况下,如果目标类没有实现接口,会用 Cglib 来生成。
如果使用 Cglib,即使目标类实现了接口,也会使用 Cglib 来生成代理对象
@Order
控制多个切面的执行顺序,数值越小执行优先级越高
2.3 切入点表达式
- execution: 匹配方法执行,如
execution(* com.example.service.*.*(..))
。
- @annotation: 匹配带有指定注解的方法。
二、整体流程简述
1. 启动阶段
使用
@
EnableAspectJAutoProxy
时,Spring容器会注册AnnotationAwareAspectJAutoProxyCreator
,它是 BeanPostProcessor
的实现类。启动阶段AOP这块主要是将相关AOP处理的容器Bean 注册到容器中,方便后面对应用自定义切面的处理,这也体现了一个按需加载的设计。EnableAspectJAutoProxy
注解源码
在容器启动完成后是可以看到这个
AnnotationAwareAspectJAutoProxyCreator
会存在容器当中,它的主要作用是自动为符合条件的 Bean 创建动态代理对象。在 Spring 中以Enable为开头的注解都有按需加载的意思,根据该种注解创建内置所需的处理类。
2. 切面的扫描与解析
扫描
@Aspect
注解的类,通过BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors()
(BeanFactoryAspectJAdvisorsBuilder
在上一步已经注入到了AnnotationAwareAspectJAutoProxyCreator
当中) 扫描所有 Bean,识别带有@Aspect 的类,解析类中@PointCut
和通知方法,将它们转换成Advisor
并存入缓存。构建缓存是会根据 Bean 的 Scope 来放入不同的缓存中。扫描与解析源码(BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors
)
这一步会在IOC 容器完成初始(
refresh#finishBeanFactoryInitialization(beanFactory)
)的过程中初始化第一个 Bean 触发上述流程。什么是Advisor
Advisor 是 Spring Aop的核心概念,用于将通知(advice)和切入点(Pointcut)组合成一个完成的切面(Aspect),从而定义在何处(切入点)执行何种逻辑(通知)。
Advisor的作用是让 Spring AOP 知道:
- 在哪些方法上(
Pointcut
)
- 执行什么操作(
Advice
)
Advisor中了用户自定义的切入类(@Aspect 修饰的类),切入点,方法名等信息
3. 创建代理
触发时机
代理对象的创建主要发生在 Bean 的初始化阶段(post-process after initialization),具体通过 BeanPostProcessor 实现。
实例化 Bean → 填充属性 → 执行初始化方法 → 执行BeanPostProcessor 后置处理。
经过前面的步骤,切面类相关注解已经处理完成。当 Bean 初始化完成后,开始为需要创建动态代理对象。
创建动态代理入口,这里所有 Bean 初始化完成后会触发,入口会判断当前 Bean 是否需要创建动态代理。
动态代理的创建是通过ProxyFactory 创建,先配置好 ProxyFactory,这些配置包括目标源(需要被代理的完成初始化的 Bean)、拦截器数组(前面流程中解析完成的 Advisor列表)、是否强制使用CGLIB(显示配置是否启用何种代理)、需要被代理实现的接口等信息。工厂设置完成好以后会调用
DefaultAopProxyFactory#createAopProxy()
方法,这里会根据PorxyFactory
配置的信息创建最终的动态代理对象。动态代理的对象指的是 JdkDynamicAopProxy
或ObjenesisCglibAopProxy
包装的对象。配置 ProxyFactory
hints: 这里配置好的 ProxyFactory会根据配置生成一个
AopProxy
,AopProxy有两个具体实现JdkDynamicAopProxy
、ObjenesisCglibAopProxy
,然后调用 AopProxy
的 getProxy 方法获得最终的代理对象。选择 AopProxy创建最终代理代理对象
三、动态代理对象包含了什么信息
动态代理对象是“包装”过的对象,它包含了构建这个代理对象的一些元数据,这些元数据都是基于扫描注解解析而来的,信息都封装在
AdvisedSupport
这个对象中(这一步是在最终生成代理对象时会将 AdvisedSupport 设置代理对象中,上述代码 最终创建代理对象代码)

其中 methodCache 是需要拦截的方法,targetSource 是源对象,advisors 是该动态代理的Advisor链。
四、执行流程
代理对象的方法调用会触发拦截器链路的执行。拦截器链路由
Advisor
转换而来,这里要筛选出当前方法匹配的Advisor
转换成MethodInterceptor
。拦截器链路完成后,然后会去递归调用执行。这里是由数组组成的拦路器链路,多个匹配的 Advisor 就是转换成MethodInterceptor数组,数组是有顺序的(顺序指的是显示指定@Order和注册顺序),
这里有个细节就是动态切点和静态切点,这里是返回的MethodInterceptor 对象是不一样的。返回的数组为 List<Object>包含了动态和静态切点。
方法的修饰
代理对象执行目标方法时,目标方法的修饰符会直接影响是否有增强(也就是执行通知),如果代理类执行目标的方法是事务方法(@Transtation注解的方法),决定它是否有增强逻辑的规则只有一条,目标方法必须是 public。
- JDK 动态代理:
JDK 动态代理基于接口实现的(实现目标类实现的接口),无法代理类中的方法。接口中的方法默认都是public的,所以无法声明其他修饰符的。(jdk9+ 虽然能声明private 的,但是仍然无法代理的。)这里的无法代理确切的说是代理对象根本就不会有这些非 public方法,它只有目标类实现的接口方法。
- Cglib
Cglib 是直接通过继承目标类生成代理子类,可代理非
final
、非 static
、非 private
的方法,因为子类能重写所以排除以上这些都可以使用。
Cglib 也是支持protected
的,Spring 官方的建议是优先使用 public。什么静态切点和动态切点
静态切点
在编译器或容器启动时即可确定是否匹配目标方法,其逻辑匹配仅基于方法签名(类名、方法名、注解等),不依赖运行时的的参数值。
静态切点的匹配规则是程序启动时即可确定的,如
@Pointcut("execution(* org.springframework.example.service.*.*(..))")
动态切点
根据运行时条件动态决定哪些方法需要被拦截。与静态切点(如
@Pointcut
注解定义的固定表达式)不同,动态切点可以根据参数值、环境变量、配置状态等动态因素灵活匹配目标方法,适合高度灵活拦截逻辑处理的场景。在 Spring 实现动态切点的方式有多种。
- 继承
StaticMethodMatcherPointcut
或DynamicMethodMatcherPointcut
- 实现
MethodMatcher
接口
实现的主要逻辑也同 Spring 的切面处理一样,定义匹配逻辑,将切点与通知结合生成 Advisor,将 Advisor 注册到容器。
全流程总结
- 激活代理:通过@EnableAspectJAutoPoxy,注册AnnotationAwareAspectJAutoProxyCreator
- 切面解析:扫描@Aspect类,解析@Pointcut 和通知方法,生成 Advisor
- 代理决策:在 Bean 初始化后,判断是否需要创建代理
- 代理生成:根据目标类选择 JDK 或 CGLIB 动态代理
- 拦截执行:通过责任链模式调用拦截器链。
Spring AOP的局限性
仅支持方法级别的增强,无法拦截字段访问、构造方法、静态方法等。(Spring支持Asjept来完成这种更细粒度的级别的增强)
运行时织入性能较低:基于动态代理的拦截会在每次方法调用时产生额外开销。(动态切点,无法缓存就需要每次构建拦截链路)
依赖 Spring 容器:只能增强 Spring 管理的 Bean。
Prev
Spring源码阅读-IOC
Next
Spring源码阅读-事务
Loading...