前言

AOP字面意思解释就是面向切面编程,面向切面编程是一种编程模型,我们知道JAVA是面向对象的也就是OOP,OOP这种面向对象的编程适用于定义纵向的关系,但是并不适用定义横向的关系。那么应对这种OOP的存在的这些不利,AOP面向切面的编程模型作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理。

AOP代理介绍

这里说的是AOP的实现,注意不是SpringAOP,首先AOP实现关键在于代理模式,AOP的代理又分为动态代理和静态代理,我们所知的Spring AOP是属于动态代理,而静态代理的的代表是AspectJ;

静态代理

AspectJ是静态代理的增强,所谓静态代理就是AOP框架会在编译阶段生成AOP代理类,因此这种也成为编译时增强,静态代理会在编译阶段将AspectJ(切面)织如到java字节码中,运行的时候就是增强后的AOP对象;

动态代理

SpringAOP是动态代理的,所谓动态代理就是说AOP框架不会去修改字节码文件,而是每次运行时在内存中零时为方法生成AOP对象,这个AOP对象包含目标对象的全部方法。并且在特定的切面做了增强处理,并回调原对象的方法;

区别

静态代理和动态代理的区别在于生成AOP代理对象的时机不同,相对AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而SpringAOP则就无需特定的编译器处理

SpringAOP动态代理

我们已经知道了SpringAOP是采用动态代理实现的,在动态代理中又有JDK动态代理和CGLIB动态代理两种实现方式!如果代理类没有实现JDK动态代理中的InvocationHandler接口,那么SpringAOP会选择使用CGLIB来动态代理目标类!默认情况下@EnableAspectJAutoProxy没有指定proxyTargetClass = true(默认不写为false),并且我们的业务类有实现InvocationHandler接口接口,那么AOP就使用JDK动态代理,没有实现InvocationHandler接口的话使用CGLIB代理,如果@EnableAspectJAutoProxy有指定proxyTargetClass = true,那么强制使用CGLIB,不管业务类是否有没有实现InvocationHandler接口,还有引用增强(A方法中调用B方法)如果@EnableAspectJAutoProxy注解中指定了exposeProxy=true的话,(默认为false),那么将暴露代理对象到线程变量中,那么调用的时候从线程变量中吧代理对象取出来然后通过代理对象进行调用,那么A方法增强,B方法也得到增强!类似于事务A方法调用事务方法B

  • JDK动态代理只提供接口代理,不支持类的代理,核心是和Proxy类,InvocationHandle通过invoke()方法反射来调用目标类中的代码,动态的将横切逻辑和业务编织在一起;接着Proxy利用InvocationHandle动态创建一个符合某一接口的实例生成目标类的代理对象
  • CGLIB(Code Generation Library)是一个代码生成的类库,可以在运行时动态生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过集成的方式做的动态代理,因此如果某个类被标记为final,那么是无法实现CGLIB做动态代理的。
  • Spring AOP实现流程、源码分析_静态代理

AOP术语

连接点(Joinpoint)

就是需要增强的某个方法

切点(PointCut)

一组连接点的集合,就称之为切点

切面(Aspect)

就是包含切点、连接点、通知组成的类,

通知(Advice)

  • 前置通知(before):在执行业务代码前做些操作,比如获取连接对象
  • 后置通知(after):在执行业务代码后做些操作,无论是否发生异常,它都会执行,比如关闭连接对象
  • 异常通知(afterThrowing):在执行业务代码后出现异常,需要做的操作,比如回滚事务
  • 返回通知(afterReturning),在执行业务代码后无异常,会执行的操作
  • 环绕通知(around),这个目前跟我们谈论的事务没有对应的操作,所以暂时不谈

目标对象(Target)
需要被加强的业务对象

织入(Weaving)

织入就是将增强添加到对目标类具体连接点上的过程。织入是一个形象的说法,具体来说,就是生成代理对象并将切面内容融入到业务流程的过程。

代理类(Proxy)

一个类被AOP织入增强后,就产生了一个代理类。

SpringAOP实例代码

目标对象

@Service
public class A {
public void f(){
System.out.println("我被执行了");
}
}

切面

@Component
@Aspect
public class MyAspect {

//切点
@Pointcut("execution(* com.xxx.xxx..*(..))")
private void anyOldTransfcr(){}
//com.xxx.xxx..*(..))这个中描述的每个方法为织入点

//通知
@Before("anyOldTransfcr()")
public void advice(JoinPoint joinPoint){
//String methodName=joinPoint.getSignature().getName();
//System.out.println("执行的方法--->"+methodName);
//System.out.println(Arrays.asList(joinPoint.getArgs()));
System.out.println("--------开始执行-------");
}

//通知
@After("anyOldTransfcr()")
public void advice2(){
System.out.println("--------结束执行-------");
}

}

主启动类

@EnableAspectJAutoProxy(proxyTargetClass = true)

​补充:这里在切面中有个@Aspect这个注解,和上面提到的AspectJ静态代理其实是有点关系的,因为Spring在刚开始实现AOP的时候写的语法是不太友好的,然后Spring后面在迭代的过程中实现AOP就参照了AspectJ的语法,注意是语法!​

启动后运行结果

Spring AOP实现流程、源码分析_动态代理_02

SpringAOP源码实现流程分析

这里需要走SpringIOC的整个流程,说白了,SpringAOP就是IOC某部分实现的功能!SpringIOC的流程请看​​Spring源码分析(流程梳理)​​此文中有如下这张图

Spring AOP实现流程、源码分析_spring_03


AOP创建代理对象的过程在上图中能看出是在Bean创建的过程中实现的,AOP过程分为如下;

  1. 切面查找
  2. 缓存切面通知
  3. 通过切点表达式判断当前bean是否属于织入点
  4. 是织入点创建代理对象
  5. 执行目标方法

1.切面查找

在每次创建Bean的时候都会进入到AbstractAutowireCapableBeanFactory类中的createBean方法,这个方法中有个关于AOP十分关键的代码;

Spring AOP实现流程、源码分析_动态代理_04

try {
// 给BeanPostProcessors一个返回代理而不是目标bean实例的机会
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}

进入这行代码后

Spring AOP实现流程、源码分析_静态代理_05


继续进入

Spring AOP实现流程、源码分析_spring_06


这里就尤为关键了,当我们开启AOP功能注解后,@EnableAspectJAutoProxy(proxyTargetClass = true)

这个注解中会帮我们Import一个AspectJAutoProxyRegistrar类

Spring AOP实现流程、源码分析_动态代理_07


进入AspectJAutoProxyRegistrar类中,这个类实现了ImportBeanDefinitionRegistrar接口,并且重写了registerBeanDefinitions,那么这里肯定是帮我们注册了Bean,进入这个方法


Spring AOP实现流程、源码分析_静态代理_08


Spring AOP实现流程、源码分析_静态代理_09


注册了一个AnnotationAwareAspectJAutoProxyCreator。对就是这个bean很重要!

回到我们的AOP流程代码中

Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);

这行代码当ibp为上面注入的AnnotationAwareAspectJAutoProxyCreator时进入AbstractAutoProxyCreator类中的postProcessBeforeInstantiation,因为AnnotationAwareAspectJAutoProxyCreator顶层继承了AbstractAutoProxyCreator,所以可以调用AbstractAutoProxyCreator类中的postProcessBeforeInstantiation方法。

Spring AOP实现流程、源码分析_spring_10


这个方法shouldSkip(beanClass, beanName),会调用AspectJAwareAdvisorAutoProxyCreator类中的shouldSkip方法,

Spring AOP实现流程、源码分析_静态代理_11


Spring AOP实现流程、源码分析_spring_12


这里的调用会进入到BeanFactoryAspectJAdvisorsBuilder类中的buildAspectJAdvisors,这个方法就是查找出切面类,然后返回切面类中声明的通知类型集合

2.缓存切面通知

List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);

这行代码中生成通知类型对象集合,并将切面类作为key,通知对象集合作为当前切面类为key的value存储起来!

Spring AOP实现流程、源码分析_静态代理_13


这步获取切面类并根据切面类中的通知生成通知对象的流程比较耗时,所以解析出来的通知对象就加入到缓存中去,后续创建Bean走到这里时会先判断aspectBeanNames是否为空,不为空就直接取缓存中的数据返回

根据切面类中的通知类型生成通知对象
​​​List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);​​这行代码作为入口,最终进入ReflectiveAspectJAdvisorFactory类中的getAdvice这个方法

Spring AOP实现流程、源码分析_动态代理_14

回到主流程,那么走到这里主流程已经把切面类找到,并且已经将通知对象缓存,那么开始原路返回到

Spring AOP实现流程、源码分析_动态代理_15


然后往下主流程开始进入创建Bean的流程;

Spring AOP实现流程、源码分析_静态代理_16


属性赋值的流程我们跳过不看,和AOP没多大关系,直接进入初始化Bean的流程

Spring AOP实现流程、源码分析_静态代理_17


初始化完成后会执行

Spring AOP实现流程、源码分析_静态代理_18


Spring AOP实现流程、源码分析_spring_19


这里就有为关键了,这里开启了AOP的@EnableAspectJAutoProxy注解后上面有提到BeanPostProcessor中会多出一个AnnotationAwareAspectJAutoProxyCreator

Spring AOP实现流程、源码分析_静态代理_20


当这里的processor为AnnotationAwareAspectJAutoProxyCreator对象时,进入AbstractAutoProxyCreator类中的postProcessAfterInitialization方法

Spring AOP实现流程、源码分析_动态代理_21


Spring AOP实现流程、源码分析_静态代理_22


这行代码又会去1.查找切面类、2.缓存切面类中的通知对象,但是只是要做这个操作但是在第一次调用shouldSkip方法是已经完成了1.查找切面类、2.缓存切面类中的通知对象这两步 操作,那么这里就不从新执行逻辑,而是进入到buildAspectJAdvisors()这个方法中后直接返回第一次执行buildAspectJAdvisors()方法缓存的通知对象结果集!

3.通过切点表达式判断当前bean是否属于织入点

Spring AOP实现流程、源码分析_动态代理_23


如果这里返回的是null,那么代表当前需要初始化的Bean不需要创建代理对象,如果如果返回不为null,那么开始创建代理对象

4.是织入点创建代理对象

Spring AOP实现流程、源码分析_spring_24


Spring AOP实现流程、源码分析_静态代理_25


Spring AOP实现流程、源码分析_静态代理_26


Spring AOP实现流程、源码分析_静态代理_27


采用CGLIB生成代理对象

Spring AOP实现流程、源码分析_spring_28


文章开头在Spring AOP介绍是有解释这里为什么使用CGLIB完成动态代理!

5.执行目标方法

Spring AOP实现流程、源码分析_spring_29


调用f方法时则是进入代理对象的f方法

Spring AOP实现流程、源码分析_动态代理_30


由于这里是采用的CGLIB完成动态代理,那么执行也是由配套的代理对象执行器完成,CGLIB由CglibAopProxy完成代理执行,JDK由JdkDynamicAopProxy完成代理执行!其实最终都是通过ReflectiveMethodInvocation类中的proceed()方法完成执行!

Spring AOP实现流程、源码分析_静态代理_31

//当前拦截器的的索引
this.currentInterceptorIndex

//拦截器集合
this.interceptorsAndDynamicMethodMatchers

//当拦截器索引等于拦截器集合数组长度-1时,执行目标方法
this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1

//执行目标方法
return invokeJoinpoint();

//完成当前拦截器获取和当前拦截器索引++操作
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);

//使用当前索引获取到的拦截器执行invoke方法逻辑
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);

这里在invoke方法中传入的是this就是为了递归循环调用proceed()方法自己

Spring AOP实现流程、源码分析_动态代理_32


每次递归其实就是往代码块内部增加逻辑代码块,然后执行。这里完整递归完的骨架代码如上。由上往下执行!

  1. AOP Aronud before…
  2. AOP Before Advice…
  3. 目标方法被执行了…
  4. AOP Aronud after…
  5. AOP After Advice…
  6. AOP AfterReturning Advice:(如果正常执行完成执行当前方法)
  7. AOP AfterThrowing Advice…(如果抛出异常,那么执行当前方法)

这种调用流程就是责任链+递归完成的