Spring源码分析——AOP

我默认看这篇博客的朋友们大多都已经基本熟练掌握Spring框架,并对Spring的一把利器——AOP(面向切面编程)有一定的了解。所以一些基础的点就一带而过了。

 

 

1.AOP是什么?

AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,通过预编译方式和
运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续,是软件开发中
的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP
可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程
序的可重用性,同时提高了开发的效率。
                                                                                                                     ---- 百度百科《AOP》

 

用我自己的说法来总结就是,AOP就是一个切面,切面中包含有我们想要做的处理,我们可以在程序执行的任何阶段,将切面切入到程序中。如下图。

 

面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到
主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、
事务、日志等

 

2.搭建测试环境

注意:本文只是为了让大家对Spring对AOP底层实现的核心注意点有所掌握,而不是带大家通读源码,所以只会展示重要部分的源码,其余不做展示。

 

首先。我们需要搭建测试环境:

 测试环境其实相当简单,下面我将测试环境的代码贴出来。

 

切面代码如下:
 

@Aspect//标识当前类为切面类,不然Spring容器无法辨认那个类是切面
public class ApectForYAAG {

    //抽取出来一个切入点
    @Pointcut(value = "execution(* *com.SpringAop.Entity.YAAG.*(..))")
    public void aspectStr(){}

    @Before(value = "execution(public String com.SpringAop.Entity.YAAG.Desc(..))")
    public void isBefore(JoinPoint joinPoint){
        System.out.println(joinPoint.getSignature().getName()+":执行isBefore前置方法");
    }

    @After("aspectStr()")
    public void isAfter(JoinPoint joinPoint){
        System.out.println(joinPoint.getSignature().getName()+"执行After后置方法");
    }

    @AfterReturning(value = "aspectStr()",returning = "result")
    public void isReturn(JoinPoint joinPoint,Object result){
        System.out.println(joinPoint.getSignature().getName()+"执行正常返回方法AfterReturning,返回值为:"+result);
    }

    @Around(value = "aspectStr()")
    public void isAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println(proceedingJoinPoint.getSignature().getName()+"的参数是"+ Arrays.asList(proceedingJoinPoint.getArgs()) +"执行环绕方法isAround前置");
        System.out.println(proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs()));
        System.out.println("执行isAround环绕方法后置");
    }

    @AfterThrowing(value = "aspectStr()",throwing = "exception")
    public void isThrowing(Exception exception){
        System.out.println("执行isThrowing异常方法,异常为:"+exception);
    }


}

 

配置文件代码如下:
 

@Configuration
@EnableAspectJAutoProxy//这个注解类似于我们之前在些xml是添加的<aop:aspectj-autoproxy/>开启以注解方式织入切面
public class AspectForYAAGConfig {
    @Bean
    public YAAG getYAAG(){

        YAAG yaag = new YAAG();

        return yaag;
    }


    @Bean
    public ApectForYAAG getAspect(){
        ApectForYAAG yaag = new ApectForYAAG();

        return yaag;
    }
}

 

实例对象代码如下:

public class YAAG {
    public String Desc(String name){
        return "姓名:"+name+ "描述为:"+"元气少女一枚!";
    }
}


测试类代码如下:

    @Test
    public void test01(){
        AnnotationConfigApplicationContext annotationConfigApplicationContext =
                new AnnotationConfigApplicationContext(AspectForYAAGConfig.class);

        String[] beanDefinitionNames = annotationConfigApplicationContext.getBeanDefinitionNames();

        System.out.println("容器中已经注册的Bean如下:");
        for(String str : beanDefinitionNames){
            System.out.println(str);
        }

        YAAG getYAAG = (YAAG) annotationConfigApplicationContext.getBean("getYAAG");

        String msg = getYAAG.Desc("元气少女");

        System.out.println(msg);

    }

}

 

3.进入源码分析,Spring的AOP是如何实现的?

 

第一阶段,定义名称为internalAutoProxyCreator的Bean后处理器,它将是Spring实现AOP机制的重点步骤.


 我们会发现,在我们的配置类上出现了@EnableAspectJAutoProxy注解,该注解,就是我们探究Spring容器如何实现Aop机制的入口点。

 

1.点开@EnableAspectJAutoProxy注解发现其使用了@Import注解向容器中注册了一个名为AspectJAutoProxyRegistrar。

2.点击进入AspectJAutoProxyRegister,发现其实现了ImportBeanDefinitionRegistrar,实现该接口的类可以直接向Ioc容器中手动定义Bean。

3.在下图中对应位置打上断点,以Debug模式查看

4 .在AspectJAutoProxyRegistrar中唯一的接口方法RegisterBeanDefiniations上打断点,使用Debug查看其运行流程,发现了一个调用AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);可以清晰的看出,意思是注册一个AspectJAnnotationAutoProxyCreator如果需要的话。

5.继续跟进方法,发现了以下代码 RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);其中需要知道的是如果想要向容器中注册一个Bean那么这个Bean对象必须是经过BeanDefinition接口实现类包装的,而我们可以清楚的发现,RootBeanDefinition就是我们最常用的一种。还有在这里cls就是AnnotationAwareAspectJAutoProxyCreator,通过下面这段代码:registry.registerBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator", beanDefinition);我们可以看出AnnotationAwareAspectJAutoProxyCreator在容器中的名称为internalAutoProxyCreator但是需要注意的是,当前只是定义了一个名为intenalAutoProxyCreator的AnnotationAwareAspectJAutoProxyCreator【但是并并未创建Bean】

 

 

第二阶段,创建容器中所有的已经定义的Bean后处理器。

 

1.接下来会进我们自己的代码区,首先一步是创建容器,

AnnotationConfigApplicationContext annotationConfigApplicationContext =
                new AnnotationConfigApplicationContext(AspectForYAAGConfig.class);点进去发现,我们虽然调用的是有参构造器,但是底层调用的是无参构造器【创建容器】+registor(配置文件)【注册配置文件】+refresh()【刷新容器】。

2 .refresh()中有一个执行了一个名为registerBeanPostProcessors(beanFactory)【注册Bean的后置处理器,拦截Bean的创建】。

  1. 获取当前容器中所有的已经定义的需要创建的实现了BeanPostProcessor,将它们全部保存到beanFactory之中【例如处理Autowired注解的后置处理器,处理require注解的后置处理器,还有就是我们前面的@EnableAspectJAutoProxy注解的后置处理器AnnotationAspectJAwareAutoProxyCreator等等】
  2. 给容器中添加一些别的Bean后处理器。
  3. 给我们第一步得到得所用的Bean后处理器排序,priority order>order>rest【order是一个接口,而priority order继承自该order接口,rest代表无优先级】,将排序后得Bean后处理器放入各自得集合中保存起来,然后按优先级从高到底的顺序依次注册(注册而非定义)到容器中。

3.从上一步的创建Bean后处理器实例并注册到容器中进行分析,我们着重分析AnnotationAwareAspectjAutoProxyCreator的创建过程。

创建AnnotationAwareAspectjAutoProxyCreator后置处理器的步骤如下:
1.创建实例对象
2.调用PopulateBean()方法给Bean的个种属性赋值。
3.调用initializeBean初始化Bean对象

  • 进入initializeBean后发现会首先调用invoke,而invoke方法就是用来处理实现了Aware接口的类的回调方法,我们的       AnnotationAwareAspectjAutoProxyCreator实现了BeanFactoryAware接口。
  • applyBeanPostProcessorsBeforeInitialization()【遍历当前容其中存在的所有有Bean后处理器,并遍历执行它们的Bean后处理器前置方法】
  • 调用invokeInitMethods()【执行自定义的初始化方法】你可以简单的理解为就是处理@Bean标签中只当的init-method和Despose-method
  • 调用applyBeanPostProcessorsAfterInitialization()【就是遍历执行容器中注册的所有Bean后处理器】

4.调用beanFactory.addBeanPostProcessor(beanPostProcessors)【把创建完成的Bean后处理器们注册到BeanFactory之中】

至此,我们已经将Bean后处理器们注册到了容器之中。  

 

 

 第三阶段,创建容器中剩余的以定义但未创建的Bean对象。

1.上面我们已经将所有的后置处理器都向容器中注册过了,用于拦截接下来需要注册的普通Bean实例,依然是从refresh方开始,点进去后发现调用了this.finishBeanFactoryInitialization(beanFactory);【创建剩余的Bean对象】

2.点开该方法,会发现其就是通过循获取当前容器中定义的所有的普通Bean的名称,然后迭代的创建每一个Bean对象实例。

getBean->doGetBean->getSingleton() 然后,会先从缓存中获取singleton【只要创建好的Bean都会被缓存起来】实例,如过获取不到【从缓存获取这一步保证了Bean实例在容器中的唯一性】,则会触发创建Bean实例。

3.继续跟进,我们会发现在创建该Bean的实例对象前,出现了resolveBeforeInstantiation这个方法,点进去后发现,这个放法是用来得到Bean后处理器中的InstantiationAwareBeanPostProcessors类型的Bean后处理器,并且依次执行它们的applyBeanPostProcessorsBeforeInstantiation()方法,点击跟进去后发现

1.首先调用this.getCacheKey(beanClass, beanName)方法获取当前所想要创建的对象在容器中定义的名称【也就是获取当前想要创建的Bean的名称】。


2.根据Bean名称判断当前Bean是否在AdvisedBean中【保存了所有需要增强的Bean,但是我们的Bean对象是第一次处理,还未对其进行包装,所以还未加入到其中】。


3.根据Bean名称判断当前Bean是否是切切面。【Advice || Pointcut || Advisor || AopInfrastructureBean或者是使用了@Aspect的类】。
4.判断是否跳过(通过类型判断),判断每一个增强器是否为AspectJPointcutAdvisor【而我们的每一个封装通知的方法的增强器类型为InstantiationModelAwarePointcutAdvisor】,所以对我们来说永远返回false。

5.至此,InstanitionAwareBeanPostProcessor的前置处理器执行完毕,然后创建实例化对象

 

6.创建对象后就要执行PostProcessAfterInitalization方法,该方法的执行内容如下。

该方法中调用了本类的wrapIfNecessary()方法下面三步是wrapIfNecessary方法的主要执行步骤:

  • 1>它会调用如下这个方法getAdvicesAndAdvisorsForBean()遍历获取当前所有Bean的增强器(通知方法)。
  • 2>遍历这些增强器,找到可以应用到我们当前实例的增强器【是根据matchs()方法根据当前的切入点表达式判断当前增强器是否可以应用到我们当前的实例中】。
  • 3.不要以为在找到可用的增强器后就完成了,我们还需要根据增强器的特性对增强器进行排序。【以便后面执行代理对象方法时有序的调用各个不同的通知】。

 

 

7.现在,我们就获取到了当前实例所有的增强器数组。若果当前实例的增强器数组长度不为0,调用this.advisedBeans.put(cacheKey, Boolean.TRUE);将当前Bean增加到advisedBeans集合之中【在我看来就是吧当前Bean的实例和它的所有增强器封装以后存入到advisedBeans集合当中】。

1.如果当前实例的增强器数组不为空的话,那就是需要增强的实例,我们将为它生成代理对象。

  •  获取所有的增强器对象。
  •  获取所有的增强器,放入代理工厂中。
  • 创建代理对象。一种是cglib,一种是jdk,关于选用那种,是Spring自动决定的【当然,我们也可以通过设置参数来左右代理方式】。
  • 生成代理对象 ,放入Spring容器之中。   

8.上一步的wrapIfNecessary()方法,最终返回了一个代理对象。现在,其实我们的ioc容器中存在的就是我们的代理对象,所以,使用原实例名称在容器中获取Bean实例对象是,最终获取到的是原对象的代理对象,所以调用的方法自然也是代理对象增强后的方法。至此,我们的ioc容器的创建也就完毕了。下面我们一起来看看代理的方法的执行流程。

 

第四阶段,容器初始化完毕,执行目标方法。

 

1.首先,当代理方法执行的时候会被xxxAopProxy.intercept();方法拦截。

2.接下来根据ProxyFactory对象获得拦截器链【拦截器链就是将当前实例的所有增强器包装成MethodInterceptor类型的拦截器,通知方法与目标方法的顺序执行主要就依靠的是拦截器的拦截器链机制。】

this.advised激素是ProxyFactory。

3.首先创建了一个名称为inteceptorList的集合,用来保存拦截器们【注意这里会有一个默认的ExposeInvocationInterceptor】增强器。

4.遍历所有增强器,将其转换为MethodInteceptor类型的拦截器。

  1.   这里涉及两个判断,如果当前增强器就是MethodInterceptor,那就直接加入到拦截器结合中。
  2. 如果当前增强器不是MethodInterceptor,那么就需要使用适配器模式将其转化为MethodInterceptor
  3. 转换完成后返回Interceptor数组!

5.如果返回的拦截器链为空或者长度位0,那就是没有拦截器,直接调用调用目标方法。

6.如果有拦截器链,把需要执行的目标对象,拦截器链,目标方法等信息,传入创建一个Cglib/jdk MethodInvocation代理对象,并调用其的proceed()方法。我们下来将关注点转移到proceed()方法的执行上。、

7.proceed()方法会判断我们当前的实例是否拥有自定义的拦截器链,如果没有,则会直接执行目标方法。结束!

注意,拦截器链中会默认添加一个名为ExposeInvocationInterceptor的拦截器。所以是size-1.

8.实例拥有自己的拦截器,则需要通过拦截器链的方式来完成顾问和目标方法的有序执行。

 

这里需要说明的是,在前几步找到当前Bean所用的可执行的增强器后,将它们全部包装成MethodInterceptor,然后对其进行排序,目的就是可以在这里进行有序的调用,这里的调用顺序就是从上到下,然后还有一点递归的味道

 

 

1.Spring默认添加在拦截器链中的ExposeInvocationInterceptor拦截器感觉该拦截器就起一个入口和出口作用。

  • 调用阶段:感觉该拦截器就起一个入口和出口作用,ReflectMethodInvocation.proceed()方法【也可以说就是调用当前实例的ThrowingAdvice】。
  • 返回阶段:至此,目标方法的执行完毕。

 

2. AspectJAfterThrowingAdvice【也就是异常通知包装后的拦截器】

  • 虽然第一个执行的就是异常拦截器的方法,但是不会实际调用它的方法,只是需要在它的内部调用ReflectMethodInvocation.proceed()方法。其实就是调用下一个通知AfterReturingAdvice。
  • 返回阶段:如果下面的方法抛出了异常,则会执行该通知的通知方法,否则,不执行,是通过catch{}语句块来保证这一点的。

 

3.AfterReturingAdviceInterceptor【也就是AfterReturing通知包装后的拦截器】

  • 该拦截器只有在目标方法正常返回的时候才会执行其对应的通知方法,它也会调用ReflectMethodInvocation.proceed()方法,也就是调用下一个通知AfterAdvice。
  • 返回阶段:如果目标方法执行出错,无法正常返回,则该方法会通过抛出异常的方式阻止该拦截器所携带的通知的执行,同时,也激活了异常通知的执行。如果方法正常返回了,那就顺序执行到了该通知所携带的方法。

 

4.AspectJAfterAdvice【也就是after通知的后置处理器包装后的拦截器】

  • 该拦截器无论目标方法是否正常返回都会被执行,它也会调用ReflectMethodInvocation.proceed()方法。也就是调用AroundAdvice。
  • 返回阶段:通过一个finally{}语句块保证了无论目标方法是否正常返回,都会执行该通知方法。

 

5.AspectJAroundAdvice【也就是around通知包装后的拦截器】

  • 该拦截器会在目标方法执行的前后执行其前置通知方法,它也会调用ReflectMethodInvocation.proceed()方法。也就是调用BeforeAdvice。
  • 回溯阶段:在目标方法执行完毕之后立即执行环绕通知的后置方法。

 

6.MethodBeforeAdviceInterceptor【也就是前置通知Before包装后的拦截器】

该拦截器会在执行了自身携带的前置通知方法后调用ReflectMethodInvocation.proceed()方法,但是此时递归条件已经被破环,所以直接执行目标方法。所以不会再向下递归而是调用目标方法,开始一步步返回

 

 

 至此Spring对AOP的底层实现也就分析完毕了,大家可能感觉看起来有点乱,多看几遍,画一画,跟一跟源码,作者在一步步分析和整理的时候也很乱,需要一步步的跟源码,所以也希望大家可以跟一下源码,会有很多的收获,如果上面的只是你都已经掌握了,我相信,你一定会让面试官眼前一亮的。源码虽然枯燥无味,但是你要想高人一等,还是必须要啃的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值