文档章节

Spring3.1.0实现原理分析(九).AOP之创建代理对象的过程

 叶琎宇
发布于 2017/05/19 16:53
字数 2580
阅读 116
收藏 0

        大家好,今天我会用一个例子来讲解Spring创建bean代理对象的过程,为大家揭开Spring AOP的神秘面纱。在看这篇博客前我强烈建议读者先看下这两篇博客《Spring3.1.0实现原理分析(六).实例化》《Spring3.1.0实现原理分析(七).填充Bean属性,初始化Bean,登记善后处理,注册单例Bean》, 这两篇博客分析了Spring创建对象的完整过程,有助于你能更好地理解本文的内容。

       下面是例子的bean定义配置,

<bean id="accountDao" class="testaop.com.qiujy.dao.AccountDaoMySQLImpl"/>
	
	<bean id="accountService" class="testaop.com.qiujy.service.AccountServiceImpl">
		<property name="accountDao" ref="accountDao"/>
	</bean>

	<!-- 日志切面类 -->
	<bean id="logAspectBean" class="testaop.com.qiujy.aspect.LogAspect" />
	
	<!-- AOP配置 -->
	<aop:config>
		
		<!-- 配置一个切面 -->
		<aop:aspect id="logAspect" ref="logAspectBean">
			
			<!-- 定义切入点,指定切入点表达式 (spring目前仅支持方法的切入,不支持属性的切入) -->
			<aop:pointcut id="allMethod" expression="execution(* testaop.com.qiujy.service.*.*(..))"/>
			
			<!-- 前置通知 -->
			<aop:before method="before" pointcut-ref="allMethod" />
		</aop:aspect>
	
	</aop:config>

在这个例子中一共有6个bean对象需要创建,它们的ID如下(为了便于描述我对ID进行了简化处理),
    1. accountDao, 
    2. accountService, 
    3. logAspectBean, 
    4. internalAutoProxyCreator,        (用于实现bean代理的bean后处理器,这个对象是Spring检测到存在aop配置自动添加的)
    5. AspectJPointcutAdvisor#0,      (通知器)
    6. allMethod                                        (切入点)

  下面我按照这6个对象的创建顺序进行讲解。

  一. 创建internalAutoProxyCreator

     何时被创建: Spring启动过程的注册bean后处理器阶段。
     如果光看applicationContext.xml配置文件你是找不到这个bean的,因为这个bean是Spring自动添加的。当Spring检测到用户配置中存在AOP相关配置时会自动添加一个实例化敏感bean后处理器(InstantiationAwareBeanPostProcessor)的定义,它的ID是"internalAutoProxyCreator",类型是"AspectJAwareAdvisorAutoProxyCreator"。然后在Spring启动过程中的注册bean后处理器阶段去创建这个bean后处理器,具体是AbstractApplicationContext#registerBeanPostProcessors(ConfigurableListableBeanFactory)方法中,创建完成的bean后处理器会被注册到AbstractBeanFactory的beanPostProcessors(list),同时这个单例bean也会被注册到DefaultSingletonBeanRegistry的singletonObjects(map)。

    二. 创建accountDao 

          何时被创建: Spring启动过程中实例化单例bean对象阶段,在创建accountDao的过程中,还会引发创建AspectJPointcutAdvisor#0(通知器)和allMethod(切入点)。

          由于此时bean工厂中已存在实例化敏感bean后处理器,即ID是“internalAutoProxyCreator”的对象。所以实例化任何bean之前会先调用实例化敏感bean后处理器的postProcessBeforeInstantiation(Class<?> beanClass, String beanName)方法,尝试创建bean的代理对象,下面我们分析下AspectJAwareAdvisorAutoProxyCreator的postProcessBeforeInstantiation(...)方法的处理流程。

          1.创建所有通知器
             当第一次调用postProcessBeforeInstantiation(...)方法时,会根据用户配置创建所有通知器,在本例中只存在一个通知器对象,它的ID是“AspectJPointcutAdvisor#0”,类型是AspectJPointcutAdvisor。创建AspectJPointcutAdvisor对象调用是它的有参构造函数,声名如下:public AspectJPointcutAdvisor(AbstractAspectJAdvice advice),
从方法声名中可以发现,创建AspectJPointcutAdvisor(通知器对象)的前提是要先创建AbstractAspectJAdvice(通知对象),方法声明中引用的是通知抽象类,在本例中具体是它的派生类AspectJMethodBeforeAdvice(前置通知对象)。

             然后创建AspectJMethodBeforeAdvice对象调用也是有参构造函数,声名如下:public AbstractAspectJAdvice(Method aspectJAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aspectInstanceFactory) 。从方法声名中可以发现,需要先创建“Method”,“AspectJExpressionPointcut”,“AspectInstanceFactory”三个对象,还好创建这三个对象调用的都是默认构造函数,下面分析下这三个参数。

              1). Method : 这个参数代表切入方法对象,本例中就是LogAspect类的befor方法,该参数的值是MethodLocatingFactoryBean对象,它实现了FactoryBean接口,通过调用getObject()方法获取切入方法对象,什么是FactoryBean接口可以回看这个篇博客《Spring3.1.0实现原理分析(六).实例化》实例化MethodLocatingFactoryBean对象成功后,在填充bean属性值这个步骤中Spring会为它的两个成员变量“targetBeanName”(切面bean's Id),“methodName”(切面bean中方法名称)赋值,值分别是“logAspectBean”,“before”。

             2).AspectJExpressionPointcut : 这个参数代表切入点对象,在本例中该参数的值是AspectJExpressionPointcut对象,一个基于AspectJ表达式的切入点对象,切入点对象的作用是根绝切入点表达式判断是否有必须要为某bean创建代理对象。实例化AspectJExpressionPointcut对象成功后,在填充bean属性值这个步骤中会为它的成员变量“expression”(切入点表达式)赋值,值是“execution(* testaop.com.qiujy.service.*.*(..))”。这个对象的ID就是allMethod,也就是说ID是allMethod的对象创建成功了。               

            3).AspectInstanceFactory : 这个参数代表切面实例工厂,在本例中该参数的值是SimpleBeanFactoryAwareAspectInstanceFactory对象,这个类通过bean工厂获取切面对象。实例化SimpleBeanFactoryAwareAspectInstanceFactory对象成功后,在填充bean属性值这个步骤中会为它的成员变量“aspectBeanName”(切面bean's Id)赋值,值是“logAspectBean”。

           实例化AspectJMethodBeforeAdvice对象成功后,在填充AspectJMethodBeforeAdvice对象属性值阶段会为它的两个成员变量“aspectName”(切面bean's id)和“declarationOrder”(节点序号)赋值,值分别是“logAspectBean”,“7”(这个7的意思是aop:aspect节点在aop:config内出现的序号),至此AspectJMethodBeforeAdvice对象算是创建完成了,然后把它传入AspectJPointcutAdvisor的构造函数,于是AspectJPointcutAdvisor也创建成功了,这个通知器对象的ID是AspectJPointcutAdvisor#0。

          PS:在AspectJPointcutAdvisor类的构造函数中,会调用传入通知对象的buildSafePointcut()方法,这个方法定义在AbstractAspectJAdvice类中,这个方法会分析切入方法对象的参数,从中获取相关信息,这些信息大多被保存在AbstractAspectJAdvice类中,该类是所有通知对象的超类。

  • 判断是否存在联合点参数(切入方法的第一个参数类型可以是"JoinPoint.class","ProceedingJoinPoint.class","JoinPoint.StaticPart.class"之一)。
  • 把切入方法参数名称和索引对应关系置入map(包含返回值参数和异常对象参数,但是不包含联合点参数)。
  • 获取被拦截方法返回值类型。
  • 获取被拦截方法抛出的异常对象类型。
  • 获取切入方法除"联合点","返回值","抛出异常"之外的其它参数的名称和类型。

 至此已有三个bean对象创建成功了,ID分别是“internalAutoProxyCreator”,“allMethod”,“AspectJPointcutAdvisor#0”,下面我用两张图对上述内容做下总结,

更形象的展示几个对象相互间的关系。

    

2. 判断是否有必要创建accountDao的代理对象,如果以下任意条件成立,则不会继续尝试创建bean的代理对象。

    1).  如果bean实现了Advisor接口,不能创建通知器的代理对象。

    2).  如果bean实现了Advice接口,不能创建通知的代理对象。

    3).  如果bean实现了AopInfrastructureBean接口,实现了该接口的类是Spring's AOP系统类,不能被代理。

    4).  如果当前bean是切面bean,不能创建切面bean的代理对象。

    5).  如果当前bean的RootBeanDefinition成员变量synthetic为true,不成创建合并bean的代理对象。

             以上条件,accountDao一个都不满足,所以继续向下执行。

 

         3. 判断AspectJAwareAdvisorAutoProxyCreator的成员变量customTargetSourceCreators(目标源创建器)是否不为null,

            默认情况下customTargetSourceCreators的值是为null,所以判断不成立返回null,说明Spring默认情况下不会在postProcessBeforeInstantiation方法中创建bean的代理对象。

           从AspectJAwareAdvisorAutoProxyCreator的postProcessBeforeInstantiation(...)方法返回后,Spring继续对accountDao执行实例化,填充bean属性值,然后初始化。我之前的博客中有写到,初始化过程的最后一个步骤是调用bean工厂中所有bean后处理器的postProcessAfterInitialization(Object bean, String beanName)方法对bean执行后处理。其中的bean后处理器就包含上述ID是internalAutoProxyCreator的对象。在internalAutoProxyCreator的postProcessAfterInitialization(Object bean, String beanName)方法中会判断是否有必须要创建accountDao的代理对象。判断的方法是获取同accountDao对象匹配的通知器列表,如果所有的通知器都不匹配accountDao则无需创建accountDao的代理对象,在本例中只有一个通知器,判断通知器是否匹配bean对象这项工作是由AspectJExpressionPointcut对象完成的,具体就是使用AspectJ表达式判断bean是否处于被拦截的范围内,bean中是否存在需要被拦截的方法对象,如果这两个条件满足就认为通知器匹配bean对象。很明显在本例中,accountDao跟表达式

execution(* testaop.com.qiujy.service.*.*(..))

是不匹配的,所以不会为accountDao创建代理对象,Spring会把这种无需创建代理对象的bean的ID缓存起来,避免重复判断,至此accountDao对象创建完成。

三. 创建accountService

      创建accountService的步骤跟accountDao是一样的,区别在于accountService跟AspectJ表达式execution(* testaop.com.qiujy.service.*.*(..))是匹配的,应该为accountService创建代理对象,创建代理对象最重要的前提就是存在同对象匹配的通知器,虽然本例只有一个通知器,假设“before”,“after-returning”,“after”,“after-throwing”四种通知器都存在的情况,spring在获取同对象匹配的通知器这个步骤中还会对通知器进行排序,也就是说通知器的调用顺序并不一定是用户的定义顺序,通知器调用顺序如下。PS:可能不太好理解,下篇博客会分析通知器的工作流程。

  • before  (前置通知)
  • after-throwing  (异常后通知)
  • after  (最终通知)
  • after-returning (后置通知)

    于是Spring就在AspectJAwareAdvisorAutoProxyCreator#postProcessAfterInitialization(Object bean, String beanName)方法中为accountService对象创建了代理对象,被代理bean的Id也会被置入缓存,代理对象可以拦截对accountServic对象的所有方法调用,从而切入处理逻辑,至此accountService对象创建完成。

四. 创建logAspectBean

      就是创建一个普通的单例bean对象,至此logAspectBean对象创建完成。

总结下:这篇博客主要分析了Spring在什么时候,在哪里创建bean代理对象,至于代理对象具体是如何工作的,我打算在下篇博客分析,敬请期待。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

© 著作权归作者所有

粉丝 4
博文 25
码字总数 36889
作品 0
宁波
高级程序员
私信 提问
Spring AOP源码解析——AOP动态代理原理和实现方式

Spring介绍 Spring(http://spring.io/)是一个轻量级的Java 开发框架,同时也是轻量级的IoC和AOP的容器框架,主要是针对JavaBean的生命周期进行管理的轻量级容器,可以单独使用,也可以和Str...

Jia
2016/10/31
1K
0
Spring 源码分析(三) —— AOP(二)Spring AOP 整体架构

Spring AOP 架构 先是生成代理对象,然后是拦截器的作用,最后是编织的具体实现。这是AOP实现的三个步骤,当然Spring AOP也是一样。 而从Spring AOP整体架构上看,其核心都是建立在代理上的。...

水门-kay
2016/03/08
1K
2
透过现象看原理:详解 Spring 中 Bean 的 this 调用导致 AOP 失效的原因

原文出处:光闪 前言 在我们使用Spring时,可能有前辈教导过我们,在bean中不要使用this来调用被@Async、@Transactional、@Cacheable等注解标注的方法,this下注解是不生效的。 那么大家可曾...

光闪
2018/05/16
0
0
Spring源码分析之AOP解析

作者: 一字马胡 转载标志 【2018-02-02】 更新日志 日期 更新内容 备注 2018-02-02 创建分析文档 Spring源码分析系列文章(三) 前言 本文是Spring源码分析系列的第三篇文章,前两篇文章分别...

一字马胡
2018/01/02
0
0
Spring AOP是什么?你都拿它做什么?

对于最近博主最近写博客的兴致大发,我也在思考:为什么而写博客?在互联网时代,无论你是牛人大咖,还是小白菜鸟,都有发表自己看法的权利。无论你是对的还是错的,都会在这个平台上找到答案...

我叫刘半仙
2017/07/18
0
33

没有更多内容

加载失败,请刷新页面

加载更多

任务调度-单体应用定时任务解决方案

1. 应用场景: 单体应用(并发少、就公司内部使用)、业务比较简单、单一、稳定,传统行业首选,项目初期。 2. 主要方式: Spring XML配置方式,timer。 <bean id="cycleBonusTimer" class="...

秋日芒草
26分钟前
3
0
EditText中singleLine过期替代方法

android:lines="1" android:inputType="text"

球球
40分钟前
1
0
删除 Tomcat-webapps 目录自带项目

本文将 %CATALINA_HOME% 目录称为“tomcat”目录。 1.webapps目录中的项目 在 Tomcat 8.0 的 tomcat/webapps 目录中,含有 5 个 Tomcat 自带的 Web 项目,如下所示: docs 有关于 Tomcat 的介...

Airship
44分钟前
3
0
好文:华杉:我等用功,不求日增,但求日减。减一分人欲,则增一分天理,这是何等简易!何等洒脱!

#写在前面1.怎么理解“减一分人欲,则增一分天理,这是何等简易!”?1)华杉提倡 “一劳永逸” 排除浪费,少干活,多赚钱,一战而定,降低作业成本。2)华杉提倡学海无涯,回头是岸...

阿锋zxf
54分钟前
3
0
vue 的bus总线

bus声明 global.bus = new Vue() 事件发送 controlTabbar () {global.bus.$emit('pickUp', 'ddd')}, 事件接收 global.bus.$on('pickUp', (res) => {this.isFocus = true})......

Js_Mei
59分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部