Spring源码-中级(三)-Spring AOP

一、BeanDefinition的准备工作

(一)AOP是什么?

AOP(Aspect Oriented Programming)是Spring的两大核心之一,它是一种面向切面编程的思想,简单来说就是通过代码织入的方式来对原来代码逻辑进行无侵入的增强。
Spring的另一大核心是IOC(Inversion Of Control,控制反转)容器,通过容器来管理Bean对象的创建、使用、销毁。其中,IOC是DI(Dependency Injection,依赖注入)的理论基础,而DI是IOC的具体实现方式。
另外,AOP的实现依托于IOC容器。

(二)AOP如何实现?

首先,在IOC容器里面添加额外的业务逻辑(切面:Aspect);
其次,选择位置来执行这个额外添加的业务逻辑(切入点:Pointcut);
最后,筛选执行额外业务逻辑的位置(连接点:JoinPoint)。

(三)AOP的使用方式:xml文件配置方式和注解方式

xml文件配置方式和注解方式的解析过程使用的是同一套逻辑,只是xml和注解这两种方式在具体解析时会调用不同的方法而已,但是大体逻辑是相同的。
无论是xml方式还是注解方式,其解析结果都会交给BeanDefinition接口来统一管理,先封装成BeanDefinition对象,然后进行实例化(创建对象、填充属性、初始化)。在创建第一个对象之前,必需要把AOP需要的相关对象准备好,因为无法预估哪些对象需要动态代理。

(四)AOP的BeanDefinition对象的准备工作

1.扩展接口梳理

(1)BeanDefinition对象封装阶段
①BFPP(BeanFactoryPostProcessor)接口

是用来对BeanFactory中的BeanDefinition进行增强的,而实例化是在BeanDefinition对象封装之后,所以该接口不会影响到后续的实例化过程。

②BeanDefinitionRegistry接口

提供的方法是用来修改BeanDefinitionNames和BeanDefinitionMap信息的,这个过程也发生在BeanDefinition对象封装阶段,因此也不会影响到实例化过程。

(2)创建Bean对象阶段
①BeanPostProcessor接口

该接口提供了postProcessBeforeInitialization方法和postProcessAfterInitialization方法来修改Bean对象的定义信息,这两个方法可以分别在执行init-method初始化方法之前和之后执行。也可以在调用doCreateBean方法或createBeanInstance方法创建实例对象之前先尝试通过BeanPostProcessor接口提供的方法创建一个AOP代理对象,如果成功,后续逻辑就不用再继续创建对象了,Spring源码中就是这样实现的,它是在调用容器方法doCreateBean(beanName, mbdToUse, args)或createBeanInstance(beanName, mbd, null)创建实例对象之前尝试调用resolveBeforeInstantiation方法返回一个AOP代理对象,如果成功就返回代理对象不用走后续逻辑创建对象,而resolveBeforeInstantiation方法的内部就是通过BeanPostProcessor接口提供的方法实现的。

(3)初始化阶段
①InitializingBean接口

该接口提供的afterPropertiesSet()方法是作用在填充属性后的初始化阶段,先执行该接口的方法然后执行bean标签中自定义的init-method初始化方法。

2.xml文件中对象解析:obtainFreshBeanFactory()创建容器阶段

xml配置文件中bean对象(包含AOP对象)的解析发生在调用obtainFreshBeanFactory()方法创建容器阶段,解析出来的bean标签对应的BeanDefinition对象信息会存入beanDefinitionMap和beanDefinitionNames这两个容器属性中,具体来说是通过BeanDefinitionRegistry接口提供的方法来操作容器的这两个属性。测试代码如下:
配置文件myAop.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
">
    <bean id="logUtil" class="com.mashibing.myDemo.aop.xml.util.LogUtil"></bean>
    <bean id="myCalculator" class="com.mashibing.myDemo.aop.xml.service.MyCalculator"></bean>
    <aop:config>
        <aop:aspect ref="logUtil">
            <aop:pointcut id="myPoint"
                          expression="execution( Integer com.mashibing.myDemo.aop.xml.service.MyCalculator.*  (..))"/>
            <aop:around method="around" pointcut-ref="myPoint"></aop:around>
            <aop:before method="start" pointcut-ref="myPoint"></aop:before>
            <aop:after method="logFinally" pointcut-ref="myPoint"></aop:after>
            <aop:after-returning method="stop" pointcut-ref="myPoint" returning="result"></aop:after-returning>
            <aop:after-throwing method="logException" pointcut-ref="myPoint" throwing="e"></aop:after-throwing>
        </aop:aspect>
    </aop:config>
</beans>

测试类:

public static void main(String[] args) throws Exception {
   
        ApplicationContext ac = new ClassPathXmlApplicationContext("myAop.xml");
        MyCalculator bean = ac.getBean(MyCalculator.class);
        System.out.println(bean.toString());
        bean.add(1,1);
        bean.sub(1,1);
    }
(1)入口:new ClassPathXmlApplicationContext(“myAop.xml”)
(2)创建容器:obtainFreshBeanFactory()
(3)容器刷新:refreshBeanFactory()

1.如果已经存在容器则销毁容器中的bean并关闭容器;如果不存在容器则直接创建容器。
2.调用createBeanFactory()方法创建一个DefaultListableBeanFactory容器,并给容器设置容器id。
3.设置容器相关属性,比如是否允许覆盖相同名称的不同方式定义的bean对象(allowBeanDefinitionOverriding属性)和是否允许bean对象之间存在循环依赖(allowCircularReferences属性)。
4.解析xml配置文件(loadBeanDefinitions(beanFactory)方法)。

(4)解析xml配置文件:

loadBeanDefinitions(beanFactory):初始化一个xml文件读取器并开始文件解析。
->loadBeanDefinitions(configLocations):根据xml配置文件路径解析文件。
->loadBeanDefinitions(location)
->loadBeanDefinitions(location, null)
->loadBeanDefinitions(resources):资源加载器加载xml配置文件路径获得Resource资源对象,然后根据资源对象解析xml文件。
->loadBeanDefinitions(resource)
->loadBeanDefinitions(new EncodedResource(resource)):利用转换后的资源对象解析xml配置文件。
->doLoadBeanDefinitions(inputSource, encodedResource.getResource()):根据资源对象的输入流解析文件。
->registerBeanDefinitions(doc, resource):将资源对象和资源对象对应的输入流解析成一个Document文档对象,然后再将文档对象和资源对象解析并封装成BeanDefinition对象。
->registerBeanDefinitions(doc, createReaderContext(resource)):完成具体解析过程。
->doRegisterBeanDefinitions(doc.getDocumentElement())
->parseBeanDefinitions(root, this.delegate):先获得文档对象的根标签beans,然后进一步解析。

(5)解析xml配置文件中的默认标签:parseDefaultElement(ele, delegate)

解析beans标签下的子标签bean。除此之外该方法还负责解析import和alias标签以及beans标签下的嵌套beans标签。除了这四种标签外其他所有标签都是由parseCustomElement(ele)方法完成解析。
->processBeanDefinition(ele, delegate)
->delegate.parseBeanDefinitionElement(ele)
->parseBeanDefinitionElement(ele, null):返回的BeanDefinitionHolder对象封装了BeanDefinition和beanName以及bean的别名。
->registerBeanDefinition(bdHolder, getReaderContext().getRegistry()):向容器中注册解析得到的BeanDefinition对象信息注册到容器。
->registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()):将解析出来的BeanDefinition对象信息存入容器属性beanDefinitionMap和beanDefinitionNames中。

(6)解析xml配置文件中的自定义标签:parseCustomElement(ele)

AOP标签属于beans根标签下下的自定义标签,因此AOP相关配置的解析在该方法中完成。
parseCustomElement(ele, null)
->handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)):根据aop标签获得对应的命名空间,然后通过命名空间处理器解析器获得命名空间处理器,最后调用处理对象的解析方法进一步解析。
->parser.parse(element, parserContext) : null):先获取bean标签元素的解析器,然后通过元素解析器进行解析。
->configureAutoProxyCreator(parserContext, element):注册自动代理模式创建器AspectjAwareAdvisorAutoProxyCreator。注册名为org.springframework.aop.config.internalAutoProxyCreator的beanDefinition,其中的class类为AspectJAwareAdvisorAutoProxyCreator,其也会被注册到bean工厂中。

registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition)

->DomUtils.getChildElements(element):获取aop根标签aop:config的子标签aop:aspect的名称。
->parserContext.getDelegate().getLocalName(elt):获取子标签aop:aspect的aspect名称。
->parseAspect(elt, parserContext):解析子标签aop:aspect。
①获取子标签的id和引用的切面名称,然后将其封装到实体类里面在存入双向队列里面,然后接着处理后面的逻辑。
②declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS):解析aop:aspect下的declare-parents节点。
③解析aop:aspect下的其他advice通知类型节点:先通过aspectElement.getChildNodes()获取aspect标签下的节点的集合。
④遍历节点集合然后调用isAdviceNode(node, parserContext)方法逐一判断节点的通知类型是否是advice:before/advice:after/advice:after-returning/advice:after-throwing/advice:around节点。
->parseAdvice(aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences):逐一解析每个通知类型。开始解析之前将通知类型的名称push到双向队列parseState中,这种类型的通知解析完成后通过pop方法将通知类型名称再弹出。
->createAdviceDefinition(adviceElement,parserContext,aspectName,order,methodDefinition,aspectFactoryDef,beanDefinitions,beanReferences):通知标签的解析。解析过程主要是将标签的切面名称和通知类型的名称分别封装到不同的实体类中,然后再将两个实体类进一步封装成AbstractBeanDefinition对象。如果通知的标签中包含returning/throwing/arg-names等属性,会将包含的属性也设置进AbstractBeanDefinition对象。
->parsePointcutProperty(adviceElement, parserContext):解析过程中会获取切入点的标签id并存入List集合中。
->上面步骤中的AbstractBeanDefinition实体类最终会被封装到AspectJPointcutAdvisor对象里面并最终返回。

// configure the advisor
			// 最终包装为AspectJPointcutAdvisor对象
			RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);
			advisorDefinition.setSource(parserContext.extractSource(adviceElement));
			advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);
			if (aspectElement.hasAttribute(ORDER_PROPERTY)) {
   
				advisorDefinition.getPropertyValues().add(
						ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY));
			}

->registerWithGeneratedName(advisorDefinition):将包含通知实体类的AspectJPointcutAdvisor存入Spring一级缓存中。aspect切面标签的解析过程中,会将每一个子标签的BeanDefinition存入beanDefinitions集合中。
->createAspectComponentDefinition(aspectElement, aspectId, beanDefinitions, beanReferences, parserContext):将切面标签元素对象aspectElement、切面aspectId、存放所有子标签的BeanDefinition集合进一步封装成AspectComponentDefinition对象。然后调用pushContainingComponent(aspectComponentDefinition)方法将封装后的对象放入双向队列Deque,等接下来的aop:point-cut标签节点解析完成后再将那个封装好的对象弹出队列。
->解析aop:point-cut标签节点:
1.List pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT):取出所有的point-cut标签;
2.parsePointcut(pointcutElement, parserContext):遍历集合逐个解析标签。解析时先获取切入点标签的唯一标识和切入点标签的切面表达式,然后将它们封装到PointcutEntry对象里面,再把这个对象放入ArrayDeque栈数据结构中,当切入点标签对应的BeanDefinition封装完成并存入Spring容器中后再从栈弹出。
3.至此,xml文件的解析完成。

二、核心对象的创建

首先,xml配置文件中配置的AOP对象在obtainFreshBeanFactory()创建容器阶段会完成解析并封装成BeanDefinition对象存入Spring的容器属性definitionMap和definitionNames中。
其次,在registerBeanPostProcessors(beanFactory)注册后置处理器阶段会完成AOP对象处理器的注册。
接下来,在finishBeanFactoryInitialization(beanFactory)初始化容器阶段进行对象的实例化、填充属性、初始化。

(一)核心对象的包含关系

核心对象的包含关系

AspectJPointcutAdvisor是AOP的核心对象,该类里面封装了五种不同的通知类型对象AspectJXXXAdvice,每种通知类型对象里面又封装了三个对象:MethodLocatingFactoryBean对象(构造器索引是0)、AspectJExpressionPointcut对象(构造器索引是1)、SimpleBeanFactoryAwareAspectInstanceFactory对象(构造器索引是2),而这三个对象刚好是AspectJXXXAdvice含参构造器的三个参数,而AspectJXXXAdvice又是AspectJPointcutAdvisor的构造器参数。
Spring AOP中一共五种通知类型(around、before、after、after-returning、after-throwing)都封装在AspectJPointcutAdvisor里面,每一种通知类型对应一个AspectJPointcutAdvisor。

(二)创建对象的先后逻辑:先内后外

由内向外先创建内层需要的参数对象,然后创建外层对象。内层对象准备好了才能创建外层对象。创建AspectJPointcutAdvisor对象时,先创建AspectJXXXAdvice对象,在创建AspectJXXXAdvice对象时需要先创建其构造器中的三个参数对象,内层三个参数对象创建完成后,逐一向外完成外层对象的创建。
创建完AspectJPointcutAdvisor#0后创建AspectJPointcutAdvisor#1,一直到创建AspectJPointcutAdvisor#4完成,至此,五种通知类型的核心对象才算创建完成。
对象创建出来后,需要先调用Ware接口的方法给对象设置beanName、classLoader、beanFactory等属性,然后从其BeanDefinition中取出属性值进行其他属性的填充,然后再执行InitializingBean接口的afterPropertiesSet()方法对bean的属性进行设置如果有对这个接口方法做扩展的话,最后再执行bean标签中自定义的init-method方法
AOP核心对象创建顺序

(三)创建对象核心步骤

1.实例化(doCreateBean)

getBean->doGetBean->createBean->doCreateBean->createBeanInstance->instantiateBean(autowireConstructor)->instantiate->clazz.getDeclaredConstructor()获得构造器->BeanUtils.instantiateClass(构造器)反射方式创建对象。

2.填充属性(populateBean)

(1)属性封装

从创建容器阶段封装的BeanDefinition取出属性值封装到PropertyValues对象中。

(2)设置属性值:applyPropertyValues(beanName, mbd, bw, pvs)

属性值设置给实例化对象并返回对象。

3.调用Aware接口设置属性:invokeAwareMethods(beanName, bean)

Aware接口处理器,调用BeanNameAware、BeanClassLoaderAware、beanFactoryAware接口方法分别给实例对象设置BeanName、BeanClassLoader、BeanFactory等属性。

4.初始化的前置处理:applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName)

执行BeanPostProcessor接口的postProcessBeforeInitialization方法对Bean对象进行增强处理。如果没有对该方法进行增强,那么该方法返回的Bean实例是原始Bean包装器。

5.执行初始化方法:initializeBean(beanName, exposedObject, mbd)

(1)调用扩展方法:afterPropertiesSet()

如果实现了InitializingBean接口,则先执行该接口的扩展方法afterPropertiesSet(),然后再执行初始化方法。

(2)调用初始化方法:invokeCustomInitMethod(beanName, bean, mbd)

如果bean标签配置了init-method属性方法,则先在调用该方法的逻辑。

6.初始化的后置处理

执行BeanPostProcessor接口的postProcessAfterInitialization方法对Bean对象进行增强处理。如果没有对该方法进行增强,那么该方法返回的Bean实例是原始Bean包装器。

7.完整对象

(四)核心对象的创建流程

核心对象的创建流程

(五)测试代码

1.xml配置方式创建AOP对象

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
">
    <bean id="logUtil" class="com.mashibing.myDemo.aop.xml.util.LogUtil"></bean>
    <bean id
  • 12
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值