Spring 源码学习笔记10——Spring AOP

参考书籍《Spring技术内幕》Spring AOP的实现章节
书有点老,但是里面一些概念还是总结比较到位
源码基于Spring-aop 5.3.22 可能和旧版本有所差异但是大体逻辑一致

一丶AOP概述

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。 是一种新的模块化机制,用来描述分散在对象,类,或函数中的横切关注点,分离关注点使解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑代码中不在含义针对特定领域的代码调用,业务逻辑同特定领域问题的关系通过切面封装,维护,这样原本分散在整个应用程序中的变动可以很好地管理起来。

用人话说就是,通过切面完成特定逻辑(事务,入参出参日志等)
可以和业务逻辑(crud)抽离开,便于维护

1. Advice 通知

定义在连接点做什么,为切面增强提供植入接口。描述Spring AOP围绕方法调而注入的切面行为

2.Pointcut切入点

切点决定Advice通知应该作用在哪个连接点,也就是通过Poincut来定义需要增强的方法集合,这些集合可以按照一定规则来完成,这种情况下,Pointcut意味着标识方法(比如事务切面定义了事务注解方法上生效)切入点是一些列织入逻辑代码的连接点集合

3.Advisor通知器

整合Advice 和 Pointcut,定义应该使用哪个通知器并在哪个关注点使用它。

二丶aop 重要接口和编程体验

我们先抛弃Spring框架,利用Spring aop中存在的工具实现aop增强。

1.基于Advice

Advice接口的实现有AfterAdvice后置通知Beforeadvice前置通知MethodInterceptor方法拦截器可以看做是环绕通知。

/**
* 服务类
*/
public static class Service{
public void doSomething(){
System.out.println("service doSomething");
}
} /***
* 自定义的advice 环绕通知
*/
public static class MyAdvice implements MethodInterceptor { @Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("my advice before");
//service 方法执行
Object res = invocation.proceed();
System.out.println("my advice after");
return res;
}
} public static void main(String[] args) {
//代理工程 用于生成代理对象
ProxyFactory proxyFactory = new ProxyFactory();
//目标对象
Service service = new Service(); //设置需要代理的对象
proxyFactory.setTarget(service);
proxyFactory.addAdvice(new MyAdvice()); //生成代理对象
Service proxyService = (Service) proxyFactory.getProxy();
//代理对象执行
proxyService.doSomething();
}

上述代码执行结果

基于Advice,ProxyFactory成功实现了Aop代理增强

2.基于Advisor

public static void advisorBased(){
//代理工程 用于生成代理对象
ProxyFactory proxyFactory = new ProxyFactory();
//目标对象
Service service = new Service(); //advisor
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
//根据名称匹配方法的pointcut
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
//指定只有doSomething 才增强
pointcut.setMappedName("doSomething");
advisor.setPointcut(pointcut);
advisor.setAdvice(new MyAdvice()); //设置需要代理的对象
proxyFactory.setTarget(service);
proxyFactory.addAdvisor(advisor);
//生成代理对象
Service proxyService = (Service) proxyFactory.getProxy();
//代理对象执行
proxyService.doSomething();
System.out.println();
proxyService.sayHello();
}

最后我们发现只有名称匹配的方法才生效。

Advisor接口具备方法Advice getAdvice()来获取通知。PointcutAdvisor实现了Advisor并且新增方法Pointcut getPointcut()来获取切入点的定义。Pointcut接口定义了两个方法ClassFilter getClassFilter(),MethodMatcher getMethodMatcher()分别是对类和方法的筛选,来决定Advise是不是应该作用于当前类。

三丶ProxyFactory 和 ProxyFactoryBean

1.ProxyFactory

1.1类图

TargetSource 用于获取 AOP 调用的当前“目标”

getTargetClass可以获取被代理对象的类型,getTarget可以获取被代理对象,HotSwappableTargetSource中的swap方法可以替换掉代理对象,Spring aop常用的是SingletonTargetSource它持有了原始的被代理对象。

1.2 proxyFactory是如何创建代理对象的。

public Object getProxy() {
return createAopProxy().getProxy();
}

1.2.1 DefaultAopProxyFactory # createAopProxy

这里生成的AopProxy 才是负责生成代理对象的,其中spring内置了两种策略——JDK动态代理和CGLIB动态代理。

只有设置了需要代理目标类,或者说没有指定代理的接口,且代理目标类不是接口,不是lambda,不是已经被JDK动态代理后的类,那么才会使用CGLIB进行动态代理。

1.2.2 AopPorxy

其中JdkDynamicAopProxy,还实现了InvocationHandler

  • Jdk动态代理

  • 生成代理对象

    Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this),这里的this便是自己。

  • invoke

首先是对`equals`,`hashCode`的处理,目标对象声明了让目标对象执行,反之调用`JdkDynamicAopProxy`对应的方法

其次是如果配置中设置了暴露代理对象,那么将其放入到`AopContext`中的`ThreadLocal`中

```java
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
``` 然后获取当前对象的拦截器链,如果拦截器链为空 那么直接反射调用目标对象的方法。如果存在拦截器链那么new 一个`ReflectiveMethodInvocation`利用反射依次执行。Spring只支持方法拦截器`MethodInterceptor`进行代理增强,对于Advise都会适配成`MethodInterceptor`,Spring采用适配器模式,具体的适配器如下 ![image-20220824172451573](https://img2022.cnblogs.com/blog/2605549/202208/2605549-20220824172515386-796996823.png) ```java
public interface AdvisorAdapter {
//支持什么Advise被适配
boolean supportsAdvice(Advice advice); //适配
MethodInterceptor getInterceptor(Advisor advisor);
}
``` 自然是Spring遍历每一个Advise责任链模式依次找到`AdvisorAdapter`然后调用适配方法得到一个`MethodInterceptor`,下面是适配成的`MethodInterceptor`。 ![image-20220824172904504](https://img2022.cnblogs.com/blog/2605549/202208/2605549-20220824172910944-1903834494.png)
  • CGLIB动态代理

    • 设置CallBack

      首先new 一个Enhancer设置父类为被代理对象的类型,这里会把讲Aop的逻辑转变为一个DynamicAdvisedInterceptor,equals和hashCode方法也有对应的callBack

      注意这里的MethodInterceptororg.springframework.cglib.proxy.MethodInterceptor,其中的intercept 方法的逻辑和JDK动态代理的invoke类似,都是链式调用。

2.ProxyFactoryBean

ProxyFactoryBean创建代理对象的逻辑和ProxyFactory类似,但是ProxyFactoryBean是一个FactoryBean,我们可以利用这一点在bean初始化的时候生成一个代理对象

创建原型对象类似,但是不会判断之前是否创建过,直接无脑创建即可

四丶Spring AOP 和IOC是如何结合起来的

通常我们在使用Spring Aop的时候会在启动类上加一个@EnableAspectJAutoProxy注解,这个注解@Import(AspectJAutoProxyRegistrar.class)导入了AspectJAutoProxyRegistrar,这个类实现了ImportBeanDefinitionRegistrar,Spring容器会调用其registerBeanDefinitions方法为我们注入BeanDefinition,后续会实例化一个AnnotationAwareAspectJAutoProxyCreator类型的bean,它是Spring IOC和AOP结合的关键

1.AnnotationAwareAspectJAutoProxyCreator类结构图

这其中最为关键的必然是AnnotationAwareAspectJAutoProxyCreator是一个BeanPostProcessor,从而在Spring 回调postProcessAfterInitialization对bean进行代理的增强,并且它实现了SmartInstantiationAwareBeanPostProcessor Spring容器创建bean的时候如果出现了循环依赖那么会调用到getEarlyBeanReference,在这个方法里面同样也会进行aop的增强

  • AbstractAutoProxyCreator 实现了SmartInstantiationAwareBeanPostProcessor是一个bean后置处理器,使用 AOP 代理包装每个符合条件的 bean,在调用 bean 本身之前委托给指定的拦截器,AOP代理发生的地方。

  • AbstractAdvisorAutoProxyCreator

    为了每一个Bean找到合适的Advisor并且进行,如果Advisor标注了@Order或者说实现了Ordered接口那么会进行排序。

  • AspectJAwareAdvisorAutoProxyCreator

    AbstractAdvisorAutoProxyCreator子类,对一个切面中的多个Advisor进行优先级排序

  • AnnotationAwareAspectJAutoProxyCreator

    AspectJAwareAdvisorAutoProxyCreator的子类,会将容器中标注了@AspectJ注解的类解析成Advisor(整合Advice 和 Pointcut,定义应该使用哪个通知器并在哪个关注点使用它)

2.AbstractAutoProxyCreator是如何进行Aop增强的

进行AOP增强的地方其实还有postProcessBeforeInstantiation如果我们自己配置了TargetSourceCreator并且可以为当前bean生成,那么才可能发生aop,这里一般不会进行任何操作。

2.1 wrapIfNecessary

其中shouldSkipAspectJAwareAdvisorAutoProxyCreator重写,如果AdvisorAspectJPointcutAdvisor并且切面名称和bean名称相同那么会跳过,这应该是我们标注@Aspect的时候需要保证这个类会被Spring加入到容器,所有需要加@Componet所以会过滤掉

2.1.1 如何找到所有合适的advice 和advisor

  • findCandidateAdvisors方法会找到容器中所以的Advisor类型的bean,AnnotationAwareAspectJAutoProxyCreator进行了重写,它还会把所以标注了@Aspect注解的bean中的增强逻辑封装成Advisor

  • findAdvisorsThatCanApply这个方法内部逻辑基本上就是调用PointcutAdvisor获取类过滤器,方法匹配器进行匹配。

  • sortAdvisors 这里默认是通过@Order注解,或者Ordered接口进行排序,但是AspectJAwareAdvisorAutoProxyCreator进行了重写,因为它需要对同一个标注@Aspect切面里面的前置后置等进行排序

2.1.2 创建代理对象

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) { if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
} ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
//这里的ProxyTargetClass 来自上面的copyFrom 取决于EnableAspectJAutoProxy注解的proxyTargetClass
//proxyTargetClass 表示是否使用基于CGLIB子类的代理
if (!proxyFactory.isProxyTargetClass()) {
//shouldProxyTargetClass 方法就是去BeanFactory中看当前bean的BeanDefinition中是否存在AutoProxy.PRESERVE_TARGET_CLASS_ATTRIBUTE=trued的attribute,当我们手动注入bean的时候可以使用这个强制让当前bean使用CGLIB增强
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
//获取当前类中非Spring回调(InitializingBean,DisposableBean,Aware)类型的接口,且如果接口的方法大于0,那么会把接口类型加入到proxyFactory中,否则设置ProxyTargetClass(没有接口那么没办法使用JDK动态代理)
evaluateProxyInterfaces(beanClass, proxyFactory);
}
} //主要是把上面找到的advise 适配成Advisor。调用的是advisorAdapterRegistry的wrap方法
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
//这里的targetSource是SingletonTargetSource
proxyFactory.setTargetSource(targetSource);
//留给子类扩展的方法
customizeProxyFactory(proxyFactory); proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
//生成代理对象
return proxyFactory.getProxy(getProxyClassLoader());
}

这里其实和我们上面的Aop编程体验中基于Advisor类似,最后都是AopProxy创建代理对象

2.2 AnnotationAwareAspectJAutoProxyCreator

上面我们讲了其父类AbstractAutoProxyCreator的大体逻辑,AnnotationAwareAspectJAutoProxyCreator会将@Aspect注解类解析成Advisor,下面我们重点看下AnnotationAwareAspectJAutoProxyCreator是怎么将@Aspect注解类解析成Advisor

这里依赖了BeanFactoryAspectJAdvisorsBuilder,它会遍历所有bean,并调用isAspect方法

然后调用ReflectiveAspectJAdvisorFactorygetAdvisors方法将其适配成多个Advisor,会遍历每一个没有标注@Pointcut的方法,然后获取@Around, @Before, @After, @AfterReturning, @AfterThrowing(如果没有那么直接返回)然后获取value中的内容包装成AspectJExpressionPointcut(AspectJ表达式pointcut),然后包装成InstantiationModelAwarePointcutAdvisorImpl在这个类中会把对应注解的方法封装成对应的AbstractAspectJAdvice的子类

调用对应方法依旧采用反射,其子类在合适的实际进行调用。

Spring 源码学习笔记10——Spring AOP的更多相关文章

  1. Spring 源码学习笔记11——Spring事务

    Spring 源码学习笔记11--Spring事务 Spring事务是基于Spring Aop的扩展 AOP的知识参见<Spring 源码学习笔记10--Spring AOP> 图片参考了 ...

  2. Spring源码学习笔记12——总结篇,IOC,Bean的生命周期,三大扩展点

    Spring源码学习笔记12--总结篇,IOC,Bean的生命周期,三大扩展点 参考了Spring 官网文档 https://docs.spring.io/spring-framework/docs/ ...

  3. Spring源码学习笔记9——构造器注入及其循环依赖

    Spring源码学习笔记9--构造器注入及其循环依赖 一丶前言 前面我们分析了spring基于字段的和基于set方法注入的原理,但是没有分析第二常用的注入方式(构造器注入)(第一常用字段注入),并且在 ...

  4. spring源码学习之路---深入AOP(终)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 上一章和各位一起看了一下sp ...

  5. spring源码学习(一)--AOP初探

    LZ以前一直觉得,学习spring源码,起码要把人家的代码整体上通读一遍,现在想想这是很愚蠢的,spring作为一个应用平台,不是那么好研究透彻的,而且也不太可能有人把spring的源码全部清楚的过上 ...

  6. Spring源码学习(二)AOP

    ----ProxyFactoryBean这个类,这是AOP使用的入口---- AOP有些特有的概念,如:advisor.advice和pointcut等等,使用或配置起来有点绕,让人感觉有些距离感,其 ...

  7. spring源码学习笔记之容器的基本实现(一)

    前言 最近学习了<<Spring源码深度解析>>受益匪浅,本博客是对学习内容的一个总结.分享,方便日后自己复习或与一同学习的小伙伴一起探讨之用. 建议与源码配合使用,效果更嘉, ...

  8. Spring源码学习(7)——AOP

    我们知道,使用面对对象编程的时候有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共行为时,例如日志.安全检测等,所以就有了一个对面对对象编程的补充,即面对切面编程(AOP),AOP所关注的方向 ...

  9. Spring源码学习笔记之bean标签属性介绍及作用

    传统的Spring项目, xml 配置bean在代码中是经常遇到, 那么在配置bean的时候,这些属性的作用是什么呢? 虽然说现在boot项目兴起,基于xml配置的少了很多, 但是如果能够了解这些标签 ...

随机推荐

  1. 前端2CSS2

    内容概要 伪元素选择器 选择器优先级 字体样式 文字属性 背景属性 display属性 边框属性 盒子模型 浮动(重要) 解决浮动造成的影响 内容详情 伪元素选择器 """ ...

  2. npm init cabloy背后的故事

    背景 我们知道许多框架会提供一个脚手架工具,我们先下载安装脚手架工具,然后再通过脚手架命令行来创建项目.在npm@6.1.0中引入了npm init <initializer>的语法.简单 ...

  3. Docker容器编译安装Redis

    Docker容器编译安装Redis 1.创建容器 -i 交互模式 -d 后端运行 -h 容器的hostname --name 容器名 --network 网卡 --ip IP地址 -p 端口映射 -- ...

  4. H2-Table CATALOGS not found

    在使用 IntelliJ IDEA 2021.1.3 版本,使用默认配置连接 H2 数据库的时候,出现下面错误,项目里 H2 使用的版本为 2.0.202 . [42S02][42102] org.h ...

  5. 论文解读(USIB)《Towards Explanation for Unsupervised Graph-Level Representation Learning》

    论文信息 论文标题:Towards Explanation for Unsupervised Graph-Level Representation Learning论文作者:Qinghua Zheng ...

  6. 重学ES系列之模版字符串

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. 自己封装的tools.js文件

    /* * 生成指定范围的随机整数 * @param lower 下限 * @param upper 上限 * @return 返回指定范围的随机整数,上/下限值均可取 */ function rand ...

  8. Linux IO重定向和管道

    计算机组成部分: 由io . 控制器.计算器.存储器组成 IO: input output 计算机里面通过终端窗口实现输入和输出,键盘鼠标屏幕这些只是手段,真正完成输入输出的是终端窗口 标准输入.出. ...

  9. Tomcat深入浅出——Servlet(二)

    一.Servlet简介 Servlet类最终开发步骤: 第一步:编写一个Servlet类,直接继承HttpServlet 第二步:重写doGet方法或者doPost方法,重写哪个我说的算! 第三步:将 ...

  10. java 九九乘法表(for循环)

    package study5ran2yl.study; public class ForDemo01 { public static void main(String[] args) { int h; ...