系列文章目录和关于我

零丶序言

《Spring源码学习笔记12——总结篇,IOC,Bean的生命周期,三大扩展点》中,我们总结了Spring IOC部分的知识,为了更好的给群里的伙伴们分享Spring AOP的知识,遂有了这篇文章,这篇文章将从IOC聊到AOP,其中IOC不会那么细致,重点还是在AOP。

一丶引入

1.AOP概述

  • AOPAspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。 是一种新的模块化机制,用来描述分散在对象,类,或函数中的横切关注点

  • AOP的优点:分离关注点使解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑代码中不在含义针对特定领域的代码调用,业务逻辑同特定领域问题的关系通过切面封装,维护,这样原本分散在整个应用程序中的变动可以很好地管理起来

2.何为横切关注点

如上图中我们有三个Service处理不同的业务逻辑,但是在三个服务中有一些和业务无关的逻辑横切在业务代码中,比如上面的权限校验,事务,耗时统计等。

为了解决横切关注点和业务逻辑的耦合,以及其分散不好管理的问题,AOP营运而生。AOP解决问题的方式有三种:

  1. 编译期AOP:在编译时,由编译器把切面调用编译进字节码,这种方式需要定义新的关键字并扩展编译器,AspectJ就扩展了Java编译器,使用关键字aspect定义切面 + aspecj特殊编译器来实现编译期AOP;
  2. 类加载器AOP:在目标类被装载到JVM时,通过一个特殊的类加载器,对目标类的字节码重新“增强”;
  3. 运行期AOP:目标对象和切面都是普通Java类,通过JVM的动态代理功能或者第三方库实现运行期动态织入。

其中Spring AOP就是基于运行期AOP。

3.Spring AOP

AOP 并不是Spring框架提出的概念,而是spring将AOP结合到自己的IOC框架中,在spring 的bean的声明周期中实现运行期AOP增强。代表的有Spring事务,Spring @Async,Spring @Cacheable等功能。

二丶Spring Bean的生命周期

上面我们说到,Spring AOP的优势在于其基于Spring IOC在spring bean声明周期中对bean进行增强,从而实现:程序员编写横切逻辑=>Spring 容器启动 => 动态代理AOP=> 丝滑使用。

这一章节我们先简单回顾下Spring bean的生命周期,了解Spring AOP 实现的"抓手doge"

1. BeanDefinition的生成

注解 or xml,这是spring中定义bean两种常用方式,两种方式都最终会将bean的定义转化为Bean的定义信息BeanDefinition。

2.bean的实例化

BeanDefinition 将指导bean的生命周期,首先是实例化,即将BeanDefinition 中记录的类实例化成对象,

  • 其中根据Bean 作用域不同的有所不同

    • 单例bean且非懒加载:spring 容器启动的时候即初始化
    • 非单例bean:由对应的Scope控制生命周期,但是实例化和单例一样
  • 另外spring中具备多种实例化bean的方式

  • FactoryBean,调用getObject生成,一般是懒加载的,但是SmartFactoryBean#isEagerInit = true 且如果是单例的话,那么在spring启动的时候就会调用getObject进行生成

  • 非FactoryBean:

    • 反射调用构造方法or工厂方法 生成
    • CGLIB 生成子类进行生成(当使用了spring的MethodOverride后)

3.bean属性填充

bean的属性填充笔者不那么专业的分为三种

  • 有参构造方法 or 工厂方法实例化bean的时候,参数会被spring 进行依赖注入,从而我们可以将注入的bean设置到属性上

  • xml定义的bean使用<property>指定属性填充,or BeanDefinition#setPropertyValues 指定属性填充,这种方式将在populateBean进行属性填充

    GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
    MutablePropertyValues propertyValues = new MutablePropertyValues();
    // 指定属性a 使用 名称为b 进行填充
    propertyValues.add("a",new RuntimeBeanReference("b"));
    genericBeanDefinition.setPropertyValues(propertyValues);
  • @Resource 或者@Autowired注解定义的属性注入

    二者会由不同的BeanPostProcessor(Bean的后置处理器)进行处理

    @Resource由 CommonAnnotationBeanPostProcessor进行处理

    @Autowired由 AutowiredAnnotationBeanPostProcessor进行处理

4.bean的初始化

  1. 如果实现了Aware那么进行回调

    • BeanNameAware,BeanClassLoaderAware,BeanFactoryAware由BeanFactory#invokeAwareMethods方法直接触发
    • EnvironmentAware,ApplicationContextAware等接口,由ApplicationContextAwareProcessor这个BeanPostProcessor触发
  2. 回调初始化方法
    • 如果实现了InitializingBean,那么回调afterPropertiesSet
    • @PostConstruct 注解标注的方法被反射回调
    • 如果BeanDefinition中定义了initMethodName(例如@Bean注解指定初始化方法,xml 声明init-method)

5.Bean的销毁

  1. @PreDestroy 注解修饰的方法被反射回调
  2. 实现了DisposableBean接口,那么回调destroy方法

三丶重要的扩展点BeanPostProcessor&AOP 和IOC的结合点

BeanPostProcessor 中文拙劣的翻译为Bean后置处理器,其中这个后置我没太多理解,此处理器贯穿Bean声明周期,每一个Bean都要经过它的处理

图中没有体现SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference这个在循环依赖中使用到的方法。

这里我们不细聊每一个BeanPostProcessor的方法,着重说一下postProcessAfterInitialization这个方法,此方法在Bean完成初始化后将被回调,其出参是一个Object,如果返回值不为null,那么将由返回的对象替换掉原bean对象。

由此我们可以在postProcessAfterInitialization对原有bean进行AOP增强,并最终被加入Spring BeanFactory中去!

这就是Spring AOP 和IOC的结合点(ps:postProcessBeforeInstantiation,getEarlyBeanReference也由结合的逻辑,postProcessBeforeInstantiation需要我们配置对应的TargetSourceCreator才会生效,getEarlyBeanReference在产生循环依赖的时候会被调用,实现半成品对象也进行AOP增强。循环依赖指路:Spring源码学习笔记8——Spring是如何解决循环依赖的 - Cuzzz - 博客园 (cnblogs.com)

四丶AOP中几个关键的概念

在学习Spring AOP实现原理前,我们先学习下AOP中的几个概念。

1. Advice 通知

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

2.Pointcut切入点

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

3.Advisor通知器

整合Advice 和 Pointcut,定义应该在哪个关注点使用什么通知进行增强。

4.Joint point连接点

表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point

五丶实现AOP功能的BeanPostProcessor

上面介绍了Spring IOC 和 AOP的结合点和AOP中几个关键的概念,但是具体是如何结合的?下面介绍和AOP功能密切的几个类

注意看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子类,它支持AspectJ表达式。

  • AnnotationAwareAspectJAutoProxyCreator

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

六丶Spring AOP代理源码

1.AbstractAutoProxyCreator#postProcessAfterInitialization 在bean初始化后进行代理

其中earlyProxyReferences记录了被代理的早期对象,在下面代码中实现了被代理对象不会再此被代理

2.AbstractAutoProxyCreator#wrapIfNecessary创建代理

其中shouldSkipAspectJAwareAdvisorAutoProxyCreator重写,如果AdvisorAspectJPointcutAdvisor并且切面名称和bean名称相同那么会跳过,这保证了@Aspect标注的类,生成的实例对象不会被代理

2.1.AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean找到所有合适的advice 和advisor

  • findCandidateAdvisors方法会找到容器中所以的Advisor类型的bean,AnnotationAwareAspectJAutoProxyCreator进行了重写,它还会把所以标注了@Aspect注解的bean中的增强逻辑封装成Advisor
  • findAdvisorsThatCanApply这个方法内部逻辑基本上就是调用PointcutAdvisor获取类过滤器,方法匹配器进行匹配。
  • sortAdvisors 这里默认是通过@Order注解,或者Ordered接口进行排序,但是AspectJAwareAdvisorAutoProxyCreator进行了重写,因为它需要对同一个标注@Aspect切面里面的前置后置等进行排序

2.2AbstractAutoProxyCreator#createProxy创建代理对象

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());
}

可以看到最终使用ProxyFactory.getProxy方法进行动态代理

2.2.1 ProxyFactory是如何创建代理对象的

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

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

其中JdkDynamicAopProxy,还实现了InvocationHandler

  • Jdk动态代理

  • 生成代理对象

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

  • CGLIB动态代理

    • 设置CallBack

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

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

    • 代理对象生成

      使用Enhancer进行增强

3.AOP代理逻辑的执行

以Jdk动态代理为例子,我们看下增强后对象方法的调用,是怎么进入到Advice中代码逻辑的

3.1 前置逻辑

这里解释了为什么AopContext可以拿到当前代理后的对象!

3.2 方法执行

3.2.1 创建拦截器链

首先有一层cache,避免每次都重复创建

这一步会将Advisor转换成MethodInterceptor,转换的过程使用了AdvisorAdapterRegistry

可以看到它可以将Advisor和MethodInterceptor进行互相转换,其中转换逻辑如下,使用了责任链+适配器模式

以下是三种类型的Adapter和对应转换后的MethodInterceptor

根据这些MethodInterceptor的名称可猜测出它们不同的执行时机!

3.2.1 ReflectiveMethodInvocation#proceed(拦截逻辑+被代理对象方法执行)

ReflectiveMethodInvocation的执行和J2EE中的FilterChain异曲同工之妙。

所谓的Joinpoint执行其实就是反射调用被代理对象的方法

4.AnnotationAwareAspectJAutoProxyCreator(Spring中@Aspect是如何生效)

上面我们讲了其父类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 AOP的原理,可以看到Spring AOP实现的原理没有那么神秘,其实就是BeanPostProcessor + 动态代理 + 反射,但是其和Spring IOC结合密切,这正是其”技术壁垒(doge)“,其中也有一些优秀的设计——工厂+代理+责任链+策略+门面设计模式,这些设计模式是真好用呀。

那么事务,@Retryable,@Cacheable,@Async是怎么实现的昵?----它们由各自的Advisor去实现。

这也是Spring AOP的优点之一,后续横切逻辑只需要开发者编写对应的Adivor即可,实现了横切逻辑和业务逻辑的解耦合

Spring源码学习笔记13——总结篇, 从IOC到AOP的更多相关文章

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

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

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

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

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

    Spring 源码学习笔记10--Spring AOP 参考书籍<Spring技术内幕>Spring AOP的实现章节 书有点老,但是里面一些概念还是总结比较到位 源码基于Spring-a ...

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

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

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

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

  6. BIND9源码学习笔记1---gdb调试篇

    学习bind9源码之前,首先要知道如何用gdb来调试bind.BIND9的源码我是先看代码弄懂它的架构,像什么event-drive,epoll等, 再去看它的业务流程.看业务流程的时候要追踪它的数据 ...

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

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

  8. Spring源码学习笔记之基于ClassPathXmlApplicationContext进行bean标签解析

    bean 标签在spring的配置文件中, 是非常重要的一个标签, 即便现在boot项目比较流行, 但是还是有必要理解bean标签的解析流程,有助于我们进行 基于注解配置, 也知道各个标签的作用,以及 ...

  9. Spring源码学习笔记1

    1.Spring中最核心的两个类 1)DefaultListableBeanFactory XmlBeanFactory继承自DefaultListableBeanFactory,DefaultLis ...

  10. Spring源码学习笔记2

    1.默认标签的解析 对四种不同标签的解析 private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate dele ...

随机推荐

  1. Selenium - 快速上手之启动浏览器

    Selenium - 浏览器启动/关闭 使用webdriver前,需要先导入包 from selenium import webdriver,每次打开浏览器时; 执行完毕要记得关闭浏览器,使用 dri ...

  2. 沁恒 CH32V208(五): CH32V208 运行FreeRTOS示例的说明

    目录 沁恒 CH32V208(一): CH32V208WBU6 评估板上手报告和Win10环境配置 沁恒 CH32V208(二): CH32V208的储存结构, 启动模式和时钟 沁恒 CH32V208 ...

  3. 计算机网络 vlan

    目录 一.vlan的概念 二.vlan的优势 三.vlan的种类 四.静态vlan的配置 五.trunk的概念和配值 六.实验 一.vlan的概念 在传统的以太网中,所有的用户都是同一个广播域,当数据 ...

  4. flutter 填坑之旅(dart学习笔记篇)

    俗话说 '工欲善其事必先利其器' 想要撸flutter app 而不懂 dart 那就像一个不会英语的人在和英国人交流,懵! 安装 dart 就不用说了,比较简单dart 官网 https://dar ...

  5. Vue3.3 的新功能的体验(下):泛型组件(Generic Component) 与 defineSlots

    上一篇说了 DefineOptions.defineModel.Props 的响应式解构和从外部导入类型 这几个新功能,但是没有说Generic.defineSlots等,这是因为还没有完全搞清楚可以 ...

  6. ggplot2 调整绘图区域大小

    熟悉 R 绘图的朋友肯定知道,在普通绘图中,图片的大小可以直接在 png() 和 pdf() 中指定,而绘图区大小则可以用 par() 中的 mar 或 mai 来指定.但是在 ggplot2 中,图 ...

  7. 解码器 | 基于 Transformers 的编码器-解码器模型

    基于 transformer 的编码器-解码器模型是 表征学习 和 模型架构 这两个领域多年研究成果的结晶.本文简要介绍了神经编码器-解码器模型的历史,更多背景知识,建议读者阅读由 Sebastion ...

  8. 深入探究for...range语句

    1. 引言 在Go语言中,我们经常需要对数据集合进行遍历操作.对于数组来说,使用for语句可以很方便地完成遍历.然而,当我们面对其他数据类型,如map.string 和 channel 时,使用普通的 ...

  9. 解决 Windows 环境下 conda 切换 Python 版本报错 NoWritablePkgsDirError: No writeable pkgs directories configured.

    1. 起因 今天运行一个 flask 项目,报错:AttributeError: module 'time' has no attribute 'clock' 一查才发现,Python3.8 不再支持 ...

  10. 处理.git文件夹过大出现臃肿问题-filter-branch和BFG工具

    Git开发手册 git一些不常用的命令记不住,可以查看git开发手册(https://m.php.cn/manual/view/34957.html) 1..git/objects/pack 文件过大 ...