框架源码专题-Spring-3、AOP
1. AOP
1.1. 分类比较
❕ ^1m5ekc
Spring AOP 和 AspectJ AOP(静态 AOP)
简而言之,Spring AOP 和 AspectJ 有不同的目标。
Spring AOP 旨在通过 Spring IoC 提供一个简单的 AOP 实现,以解决编码人员面临的最常出现的问题。这并不是完整的 AOP 解决方案,它只能用于 Spring 容器管理的 beans。
另一方面,AspectJ 是最原始的 AOP 实现技术,提供了完整的的 AOP 解决方案。AspectJ 更为健壮,相对于 Spring AOP 也显得更为复杂。值得注意的是,AspectJ 能够被应用于所有的领域对象
https://juejin.im/post/5a695b3cf265da3e47449471
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理 (Proxying),而 AspectJ 基于字节码操作 (Bytecode Manipulation)。
Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。
https://blog.csdn.net/weixin_44259720/article/details/95996541
1.2. 方案选择
- 框架:如果应用程序不使用 Spring 框架,那么我们别无选择,只能放弃使用 Spring AOP 的想法,因为它无法管理任何超出 spring 容器范围的东西。 但是,如果我们的应用程序完全是使用 Spring 框架创建的,那么我们可以使用 Spring AOP,因为它很直接便于学习和应用。
- 灵活性:鉴于有限的连接点支持,Spring AOP 并不是一个完整的 AOP 解决方案,但它解决了程序员面临的最常见的问题。 如果我们想要深入挖掘并利用 AOP 达到其最大能力,并希望获得来自各种可用连接点的支持,那么 AspectJ 是最佳选择。
- 性能:如果我们使用有限的切面,那么性能差异很小。 但是,有时候应用程序有数万个切面的情况。 在这种情况下,我们不希望使用运行时织入,所以最好选择 AspectJ。 已知 AspectJ 比 Spring AOP 快 8 到 35 倍。
- 共同优点:这两个框架是完全兼容的。 我们可以随时利用 Spring AOP,并且仍然使用 AspectJ 来获得前者不支持的连接点。
1.3. 术语解析
https://www.bilibili.com/video/av58225341?p=270
[[02_尚硅谷大数据技术之Spring(老师原版).docx]]
1.3.1. 配套代码
[[pages/002-schdule/001-Arch/001-Subject/013-DemoCode/Spring03/src/com/atguigu/spring/aspectJ/annotation/LoggingAspect.java]]
1.3.2. 目标 (Target)
业务逻辑:比如加、减、乘、除
1.3.3. 代理 (Proxy)
1.3.4. 横切关注点
在程序代码中的具体体现,对应程序执行的某个特定位置。例如:某个方法调用前、调用后、方法捕获到异常后等。
1.3.5. 通知 (Advice)
在横切关注点所设定的地方,所要完成的特定动作
1.3.6. 切面 (Aspect)
将相关通知汇总到一个类中,管理切点和通知
1.3.7. 连接点 (Joinpoint)
横切关注点与目标对象的目标方法的交叉点。比如方法执行前与add() ❕ ^6c4nvo
1.4. 实现方式
[[(209条消息) AOP的实现的几种方式_Ydoing的博客-CSDN博客_aop实现]]
2. SpringAOP 实现原理
2.1. 容器环境准备
因为任何 Bean 创建都需要 BeanDefinitionRegistryPostProcessor
先将 BD 注册到容器中,而在 Spring 中唯一的实现类就是 ConfigurationClassPostProcessor
,所以第一步就是它。
2.2. 注册 Creator BD
在 refresh() 中的第 5 步 执行BeanFactoryPostProcessor 的方法中,BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry
2.2.1. AspectJAutoProxyRegistrar(注册 CreatorBD)
2.2.1.1. @EnableAspectJAutoProxy
implements ImportBeanDefinitionRegistrar
利用重写的 registerBeanDefinitions 方法,给容器中注册一个自动代理创建器 AnnotationAwareAspectJAutoProxyCreator
,放到 beanDefinitionMap
中
2.2.1.2. AnnotationAwareAspectJAutoProxyCreator
2.2.1.2.1. 继承关系
AnnotationAwareAspectJAutoProxyCreator
->AspectJAwareAdvisorAutoProxyCreator
->AbstractAdvisorAutoProxyCreator
->AbstractAutoProxyCreator
implements
SmartInstantiationAwareBeanPostProcessor,BeanFactoryAware
关注后置处理器(在bean初始化完成前后做事情)、自动装配BeanFactory
2.2.1.2.2. 关键方法
AbstractAutoProxyCreator.setBeanFactory()
AbstractAutoProxyCreator.有后置处理器的逻辑;
AbstractAdvisorAutoProxyCreator.setBeanFactory() -> initBeanFactory()
AnnotationAwareAspectJAutoProxyCreator.initBeanFactory()->super.initBeanFactory
2.2.2. 注册 Creator BPP Bean
2.2.2.1. 创建 BPP Bean
来到刷新的第 6 步,registerBeanPostProcessors() 中,用 PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this)
注册 postprocessor。beanFactory.getBean(ppName, BeanPostProcessor.class)
会创建所有 postprocessor 对象。BPP 的 bean 也是 bean,在 getBean 的过程中,12 大步都会走。
2.2.2.2. 设置 BeanFactory
会在 initializeBean 中真正调用初始化方法之前,invokeAwareMethods(beanName, bean)
中调用 setBeanFactory()
;
- 将 BPP(AnnotationAwareAspectJAutoProxyCreator) 保存到
List<BeanPostProcessor>
beanPostProcessorsbeanFactory.addBeanPostProcessor(postProcessor)
2.3. 增强逻辑
https://www.processon.com/diagraming/63e4bfa27c423a1934f127a5
https://www.processon.com/view/link/5f1958a35653bb7fd24d0aad
2.3.1. postProcessBeforeInstantiation(解析切面并缓存通知)
❕ ^o0kxx7
在 11. finishBeanFactoryInitialization() 中,任何一个 bean 创建实例之前,会走到 resolveBeforeInstantiation
方法中,会执行类型为 InstantiationAwareBeanPostProcessor
的 postprocessor 的applyBeanPostProcessorsBeforeInstantiation方法。而我们的 AnnotationAwareAspectJAutoProxyCreator 正是此种类型。所以会执行其父类AbstractAutoProxyCreator重写的 postProcessBeforeInstantiation
方法AnnotationAwareAspectJAutoProxyCreator
是 InstantiationAwareBeanPostProcessor
类型的后置处理器,重写的方法为
2.3.1.1. getCustomTargetSource
这个方法主要解析用户配置的切面类,getCustomTargetSource 用来处理用户自定义 TargetSource 的场景,一般没人自定义,除非我们的容器中有 TargetSourceCreator 并且我们的 bean 需要实现
2.3.1.2. findCandidateAdvisors
通过 Spring IoC 容器找到系统中实现了 Advisor 接口的 bean,声明式事务的 advisor 就是在这里被查询到的。❕ ^q0d8rn
2.3.1.3. buildAspectJAdvisors⭐️🔴
通过 Spring IoC 容器获取所有标注 @AspectJ 注解的 Bean,再获取标注 AspectJ 注解(除了@Pointcut 其他的注解 @After、@Before、@AfterReturning 等)的方法,将这些方法封装为一个个 Advisor,缓存在 this.advisorsCache
中,以 beanName
为 key,List<Advisor>
为 value。
1 |
|
2.3.1.3.1. AspectJAdvisorFactory.getAdvisors
首先获取切面类型,然后获取除 Pointcut 注解的所有方法,根据方法注解来创建 Advisor 通知器。
判断当前方法是否标注有@Before,@After 或@Around 等注解,如果标注了,则将其封装为一个 Advisor,其中会调用 getPointcut 包含了切点信息
AspectJAdvisorFactory.getAdvisor
获取当前方法中@Before,@After 或者@Around 等标注的注解,并且获取该注解的值,将其封装为一个 AspectJExpressionPointcut 对象
getPointcut
将获取到的切点,切点方法等信息封装为一个 Advisor 对象,也就是说当前 Advisor 包含有所有当前切面进行环绕所需要的信息
new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,this, aspectInstanceFactory, declarationOrderInAspect, aspectName)
InstantiationModelAwarePointcutAdvisorImpl 中的 getAdvice()
是个同步方法
2.3.1.3.2. 事务相关内容⭐️🔴⭐️🔴
2.3.1.4. 结论
https://segmentfault.com/a/1190000041228253 我们的 aop 解析切面以及事务解析事务注解都是在这里完成的
AnnotationAwareAspectJAutoProxyCreator 的 postProcessBeforeInstantiation 方法的主要核心在于将容器中 所有的切面对应的通知都扫描出来并包装成 InstantiationModelAwarePointcutAdvisorImpl 类型的对象并添加到缓存中(这里要注意:不管是自定义的切面、还是实现了 Advisor 接口 (比如声明式事务注册进来的) 的切面都会被扫描出来)。这是一种预热机制,先把数据准备好,后续需要时直接再从缓存中拿。
https://juejin.cn/post/6978742789165187079
https://www.jianshu.com/p/b45bf3befa25
https://blog.csdn.net/qq_42419406/article/details/116640641
2.3.2. postProcessAfterInitialization(使用缓存创建代理)
https://www.processon.com/view/link/5f1e93f25653bb7fd2549b7c
时机:对象初始化完成之后
return `wrapIfNecessary(bean, beanName, cacheKey)
1)、获取当前 bean 的所有增强器(通知方法) Object[] specificInterceptors
1、找到候选的所有的增强器(找哪些通知方法是需要切入当前 bean 方法的)
2、获取到能在 bean 使用的增强器。
3、给增强器排序
2)、保存当前 bean 在 advisedBeans 中;
3)、如果当前 bean 需要增强,创建当前 bean 的代理对象;
1)、获取所有增强器(通知方法)
2)、保存到 proxyFactory
3)、创建代理对象:Spring 自动决定
JdkDynamicAopProxy(config);jdk 动态代理;
ObjenesisCglibAopProxy(config);cglib 的动态代理;
4)、给容器中返回当前组件使用 cglib 增强了的代理对象;
5)、以后容器中获取到的就是这个组件的代理对象,执行目标方法的时候,代理对象就会执行通知方法的流程;
2.3.2.1. wrapIfNecessary
2.3.2.2. getAdvicesAndAdvisorsForBean
https://blog.csdn.net/wang489687009/article/details/121099165
https://blog.csdn.net/yy_diego/article/details/118213155
2.3.2.3. findEligibleAdvisors
AbstractAdvisorAutoProxyCreator:findCandidateAdvisors→
AnnotationAwareAspectJAutoProxyCreator:advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
1 |
|
// 提供的 hook 方法,用于对目标 Advisor 进行扩展
extendAdvisors(eligibleAdvisors);
2.3.2.4. createProxy
1 |
|
2.4. 执行逻辑
https://www.processon.com/view/link/5f4dd513e0b34d1abc735998
容器中保存了组件的代理对象(cglib 增强后的对象),这个对象里面保存了详细信息(比如增强器,目标对象,xxx)
拦截器链:每一个通知方法又被包装为方法拦截器,利用 MethodInterceptor 机制执行方法逻辑。如果没有拦截器链,直接执行目标方法
2.4.1. 获取并封装拦截器链⭐️🔴
执行目标方法执行时,CglibAopProxy.intercept() 会拦截目标方法的执行,根据 ProxyFactory 对象 (this.advised
) 获取将要执行的目标方法的拦截器链
- 遍历所有的增强器,将其转换为 MethodInterceptor
如果是 MethodInterceptor 类型,直接加入到集合中。如果不是,使用 AdvisorAdapter 将增强器转为 MethodInterceptor 类型;转换完成返回 MethodInterceptor 数组; - 如果是 PointcutAdvisor 需要 match 切点,匹配成功则放入
List<Object>
返回 - 获得拦截器链之后,把需要执行的目标对象,目标方法,
拦截器链等信息封装起来,创建一个CglibMethodInvocation 对象
,并调用 Object retVal = mi.proceed();
2.4.2. 触发执行拦截器链
2.4.2.1. 执行入口
- 创建 CglibMethodInvocation 对象后调用
proceed()
方法new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed()
- 执行进入
proceed()
方法中,获取第++this.currentInterceptorIndex
个拦截器。this.currentInterceptorIndex
默认值是-1
- 调用拦截器的
invoke(this)
方法,this
就是当前CglibMethodInvocation
对象 - 在不同拦截器中的 invoke 方法中,会再次调用
mi.proceed()
,进入到第 2 步,依次递归,执行每个拦截器中的方法,直到递归调用的执行出口为止
❕ - 值得注意的是在第一个默认的拦截器
ExposeInvocationInterceptor
中将 CglibMethodInvocation 对象放到了 ThreadLocal 中以便共享使用
2.4.2.2. 执行出口
this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1
如果没有拦截器,或者拦截器的索引等于拦截器数组 -1 (即执行到了最后一个拦截器)就会执行目标方法;
2.4.2.3. 执行过程
链式获取每一个拦截器,拦截器执行 invoke 方法,每一个拦截器等待下一个拦截器执行完成返回以后再来执行;
拦截器链的触发过程,即 proceed 方法的执行过程。拦截器链的机制,保证了通知方法与目标方法的执行顺序
每种拦截器的执行逻辑不同,比如 AspectJAfterThrowingAdvice
,advice 方法在 catch 块里执行。其他拦截器请看 Spring-3、AOP实现原理-@EnableAspectJAutoProxy
而 this.advice.afterReturing 没有设置 catch 块,所以出错之后也就不会被执行到了
2.4.2.4. 执行顺序分析
流程图
❕
2.5. 简单概述
总结:
1)、 @ EnableAspectJAutoProxy 开启 AOP 功能
2)、 @ EnableAspectJAutoProxy 会给容器中注册一个组件 AnnotationAwareAspectJAutoProxyCreator
3)、AnnotationAwareAspectJAutoProxyCreator 是一个后置处理器;
4)、容器的创建流程:
1)、registerBeanPostProcessors()注册后置处理器;创建 AnnotationAwareAspectJAutoProxyCreator 对象
2)、finishBeanFactoryInitialization()初始化剩下的单实例 bean
1)、创建业务逻辑组件和切面组件
2)、AnnotationAwareAspectJAutoProxyCreator 拦截组件的创建过程
3)、组件创建完之后,判断组件是否需要增强
是:切面的通知方法,包装成增强器(Advisor); 给业务逻辑组件创建一个代理对象(cglib);
5)、执行目标方法:
1)、代理对象执行目标方法
2)、CglibAopProxy.intercept();
1)、得到目标方法的拦截器链(增强器包装成拦截器 MethodInterceptor)
2)、利用拦截器的链式机制,依次进入每一个拦截器进行执行;
3)、效果:
正常执行:前置通知 -》目标方法 -》后置通知 -》返回通知
出现异常:前置通知 -》目标方法 -》后置通知 -》异常通知
2.6. 顺序问题 -【两种顺序】⭐️🔴
❕ ^8u7u46
2.6.1. 通知执行与目标方法执行之间的顺序在哪确定的⭐️🔴
在各种通知的源码中写死的,这种顺序是不会变的,而拦截器链中通知的顺序可以通过 @Order
注解改变 ❕
2.6.1.1. 通知种类
https://blog.csdn.net/Ethan_199402/article/details/109491789
是否是 MethodInterceptor 类型的
ExposeInvocationInterceptor:是
AspectJAfterThrowingAdvice: 是
AspectJAfterReturningAdvice: 不是,转换为 AfterReturningAdviceInterceptor
AspectJAfterAdvice: 是
AspectJAroundAdvice 是
AspectJMethodBeforeAdvice 不是,转换为 MethodBeforeAdviceInterceptor
PS: 虽然有适配器,但 AspectJAfterThrowingAdvice 实现了 MethodInterceptor
https://www.vnjohn.com/aop-process-source.html
2.6.1.2. ExposeInvocationInterceptor
当生成代理对象之后,该进行逻辑方法的调用了;但此时,有 6 个 advisor,除了五种增强通知之外,还有一个是 ExposeInvocationInterceptor(将当前 methodInvocation 存入线程上下文)它起着一个桥梁的作用,它们在执行时是按照某个顺序来执行的,而且由一个通知跳转到另外一个通知,所以此时,我们需要构建一个拦截器链(责任链模式)只有创建好链式结构,才能顺序的往下执行.
在 findEligibleAdvisors
方法中调用 makeAdvisorChainAspectJCapableIfNecessary
创建增强器链时被加入到增强器链中
2.6.1.3. AspectJAfterThrowingAdvice
1 |
|
2.6.1.4. AfterReturningAdviceInterceptor⭐️
由 AfterReturningAdvice 适配而来
1 |
|
2.6.1.5. AspectJAfterAdvice
1 |
|
2.6.1.6. AspectJAroundAdvice
1 |
|
2.6.1.7. MethodBeforeAdviceInterceptor⭐️
由 AspectJMethodBeforeAdvice 适配而来
1 |
|
2.6.2. 拦截器链中通知的顺序是在哪里确定的⭐️🔴
❕ ^q5brru
shouldskip 流程
METHOD_COMPARATOR = adviceKindComparator.thenComparing(methodNameComparator);
是在 ReflectiveAspectJAdvisorFactory 中的静态代码块中定义的 ❕
findCandidateAdvisors
→ buildAspectJAdvisors
→ getAdvisors
→ getAdvisorMethods
→ getAdvisor
获取所有不包含 Pointcut 注解的方法,然后进行排序,根据对应的顺序 Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class
2.6.2.1. findEligibleAdvisors.sortAdvisors⭐️🔴
在使用时,除了用于创建链条的 ExposeInvocationInterceptor
,其他通知都做了倒序排列
不同优先级
compareTo → compare → doCompare
相同优先级且同属一个切面
AspectJPrecedenceComparator.comparePrecedenceWithinAspect →
getAspectDeclarationOrder
2.6.2.2. getAdvicesAndAdvisorsForBean
2.6.2.3. getInterceptorsAndDynamicInterceptionAdvice
得到的顺序与 getAdvicesAndAdvisorsForBean
是一致的,也就是说排序的地方就在 findEligibleAdvisors.sortAdvisors
2.6.2.4. 自定义顺序
❕ ^1mjgqz
https://blog.csdn.net/CSDN_KONGlX/article/details/125486683
https://cloud.tencent.com/developer/inventory/657/article/1526899
多个@Aspect、Advisor 排序规则
1、在 spring 容器中获取@Aspect、Advisor 类型的所有 bean,得到一个列表 list1
2、对 list1 按照 order 的值升序排序,得到结果 list2
3、然后再对 list2 中@Aspect 类型的 bean 内部的通知进行排序,规则
如上图所示,这 2 个 Aspect 就像 2 个圆圈在外面拦截,中间是目标方法。当一个请求进来要执行目标方法:
- 首先会被外圈的
@Order(1)
拦截器拦截 - 然后被内圈
@Order(2)
拦截器拦截 - 执行完目标方法后,先经过
@Order(2)
的后置拦截器 - 最后再通过
@Order(1)
的后置拦截器
3. 注解式开发
https://www.bilibili.com/video/BV1ME411o7Uu?p=8
3.1. 使用示例
Spring 注解驱动开发 - 尚硅谷 - 雷丰阳/spring-annotation/src/main/java/com/atguigu/config/MainConfigOfAOP.java
[[MainConfigOfAOP.java]]
3.2. 主要三步
- 1)、将业务逻辑组件和切面类都加入到容器中;告诉 Spring 哪个是切面类(@Aspect)
- 2)、在切面类上的每一个通知方法上标注通知注解,告诉 Spring 何时何地运行(切入点表达式)
- 3)、开启基于注解的 aop 模式;@EnableAspectJAutoProxy
3.3. 详细步骤
- 1、导入 aop 模块;Spring AOP:(spring-aspects)
- 2、定义一个业务逻辑类(MathCalculator);在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、方法出现异常,xxx)
- 3、定义一个日志切面类(LogAspects):切面类里面的方法需要动态感知 MathCalculator.div 运行到哪里然后执行;
通知方法:
前置通知(@Before):logStart:在目标方法(div)运行之前运行
后置通知(@After):logEnd:在目标方法(div)运行结束之后运行(无论方法正常结束还是异常结束)
返回通知(@AfterReturning):logReturn:在目标方法(div)正常返回之后运行
异常通知(@AfterThrowing):logException:在目标方法(div)出现异常以后运行
环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())
- 4、给切面类的目标方法标注何时何地运行(通知注解);
- 5、将切面类和业务逻辑类(目标方法所在类)都加入到容器中;
- 6、必须告诉 Spring 哪个类是切面类 (给切面类上加一个注解:@Aspect)
- 7、给配置类中加 @EnableAspectJAutoProxy 【开启基于注解的 aop 模式】
在Spring中很多的 @EnableXXX;
4. 设计模式
4.1. 代理模式
4.2. 适配器模式
Spring AOP 的增强或通知 (Advice) 使用到了适配器模式,与之相关的接口是 AdvisorAdapter
Advice 常用的类型有:BeforeAdvice(目标方法调用前,前置通知)、AfterAdvice(目标方法调用后,后置通知)、AfterReturningAdvice(目标方法执行结束后,return 之前) 等等。
每个类型 Advice(通知)都有对应的拦截器:MethodBeforeAdviceInterceptor、AfterReturningAdviceAdapter、AfterReturningAdviceInterceptor。
Spring 预定义的通知要通过对应的适配器,适配成 MethodInterceptor 接口 (方法拦截器) 类型的对象(如:MethodBeforeAdviceInterceptor 负责适配 MethodBeforeAdvice)。
5. 实战经验
5.1. 通知的版本变化
📙❕ ^nj3sol
确切的说是5.2.7 版本就发生了变化,将 After 放在 AfterThrowing 或者 AfterReturning 后面,更符合人们的正常思维习惯。
[[001-Spring面试题笔记#8 2 Spring通知有哪些类型?]]
顺序的源头:Spring-3、AOP实现原理-@EnableAspectJAutoProxy
5.2. 默认动态代理变化⭐️🔴
❕❕ ^5vgexk
https://cloud.tencent.com/developer/article/1532547
- Spring 5.x 中 AOP 默认依旧使用 JDK 动态代理。
- SpringBoot 2.x 开始,为了解决使用 JDK 动态代理可能导致的类型转化异常而默认使用 CGLIB。
- 在 SpringBoot 2.x 中,如果需要默认使用 JDK 动态代理可以通过配置项
spring.aop.proxy-target-class=false
来进行修改,proxyTargetClass
配置已无效。
1 |
|
自定义注解
http://www.imooc.com/article/22556
6. 参考与感谢
6.1. 尚硅谷 - 雷丰阳 -Spring 注解驱动开发
6.1.1. 视频
6.1.2. 配套代码
[[MainConfigOfAOP.java]]
6.1.3. 配套笔记
https://liayun.blog.csdn.net/article/details/111413608
6.2. 尚硅谷 - 周阳 - 大厂面试第三季
[[pages/002-schdule/001-Arch/001-Subject/013-DemoCode/brain-mapping/docs/大厂面试题第3季.mmap]]
6.3. 图灵徐庶
https://www.bilibili.com/video/BV1mf4y1c7cV?p=88&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204
[[Spring全家桶面试题—图灵徐庶.pdf]]
https://www.processon.com/view/link/5f5075c763768959e2d109df#map
6.4. 尚硅谷大数据技术之 Spring
6.4.1. 视频
https://www.bilibili.com/video/av58225341?p=270
6.4.2. 配套代码
[[02_尚硅谷大数据技术之Spring(老师原版).docx]]
[[pages/002-schdule/001-Arch/001-Subject/013-DemoCode/Spring03/src/com/atguigu/spring/aspectJ/xml/LoggingAspect.java]]
[[pages/002-schdule/001-Arch/001-Subject/013-DemoCode/Spring03/src/com/atguigu/spring/aspectJ/annotation/LoggingAspect.java]]
6.5. 网络笔记
https://blog.csdn.net/andy_zhang2007/article/details/86479685 »20230220-0911
https://blog.csdn.net/qq_42419406/article/details/116640641 »20230220-0928
https://blog.csdn.net/yuan882696yan/article/details/115359291
https://blog.csdn.net/yuan882696yan/article/details/116137802
https://juejin.cn/post/7037839773267918879#heading-3
https://juejin.cn/post/6978742789165187079#heading-6
https://segmentfault.com/a/1190000041228253
https://www.jianshu.com/p/b45bf3befa25
https://www.luckfirefly.com/archives/spring%E6%8F%AD%E7%A7%98-%E6%B7%B1%E5%85%A5advisedsupport
https://blog.csdn.net/lom9357bye/article/details/124659927 »20230220-0841