基于 Spring Framework v5.2.6.RELEASE
概述
上一篇分析了wrapIfNecessary
方法创建 AOP 代理的大致过程,其中最先执行的步骤,就是获取到 Spring 中所有已配置的增强,而要获取到这些增强的信息,就需要事先将这些配置加载到 Spring 中。本文我们就来分析通过 XML 配置的切面信息是如何被加载的。
本文的分析内容,以下面这段配置为例:
<beansxmlns="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 https://www.springframework.org/schema/aop/spring-aop.xsd"><beanid="userService"class="pseudocode.UserService"/><beanid="userAspect"class="pseudocode.UserAspect"/><aop:config><aop:pointcutid="pointcut"expression="execution(* pseudocode.UserService.add(..))"/><aop:aspectref="userAspect"><aop:beforemethod="before"pointcut-ref="pointcut"/><aop:aroundmethod="around"pointcut-ref="pointcut"/></aop:aspect></aop:config></beans>
XML 切面配置解析
在【Spring 源码阅读 47:在 XML 配置中开启 AOP 特性的原理分析 】一文中曾经分析过,Spring 会通过对应的 BeanDefinitionParser 来解析自定义标签,其中,标签对应的是 ConfigBeanDefinitionParser,因此,我们从 ConfigBeanDefinitionParser 的parse
方法开始入手,分析 Spring 如何解析 XML 文件中的切面配置。
找到方法的源码。
publicBeanDefinitionparse(Elementelement, ParserContextparserContext) { CompositeComponentDefinitioncompositeDef=newCompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element)); parserContext.pushContainingComponent(compositeDef); configureAutoProxyCreator(parserContext, element); List<Element>childElts=DomUtils.getChildElements(element); for (Elementelt: childElts) { StringlocalName=parserContext.getDelegate().getLocalName(elt); if (POINTCUT.equals(localName)) { parsePointcut(elt, parserContext); } elseif (ADVISOR.equals(localName)) { parseAdvisor(elt, parserContext); } elseif (ASPECT.equals(localName)) { parseAspect(elt, parserContext); } } parserContext.popAndRegisterContainingComponent(); returnnull; }
其中,configureAutoProxyCreator
方法用于配置创建 AOP 代理的后处理器,在前面的文章,我们已经分析过了,这里我们重点关注for
循环的部分,也就是的子标签的解析,针对不同的子标签,有不同的方法用于解析。
在上面的配置文件实例中,有和两个子标签,我们分别来看它们被解析的过程。
<aop:pointcut>
标签的解析
进入parsePointcut
方法。
// org.springframework.aop.config.ConfigBeanDefinitionParser#parsePointcutprivateAbstractBeanDefinitionparsePointcut(ElementpointcutElement, ParserContextparserContext) { Stringid=pointcutElement.getAttribute(ID); Stringexpression=pointcutElement.getAttribute(EXPRESSION); AbstractBeanDefinitionpointcutDefinition=null; try { this.parseState.push(newPointcutEntry(id)); pointcutDefinition=createPointcutDefinition(expression); pointcutDefinition.setSource(parserContext.extractSource(pointcutElement)); StringpointcutBeanName=id; if (StringUtils.hasText(pointcutBeanName)) { parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition); } else { pointcutBeanName=parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition); } parserContext.registerComponent( newPointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression)); } finally { this.parseState.pop(); } returnpointcutDefinition; }
首先,获取了标签的id
和expression
属性,其中expression
属性的值就是切入点表达式。
然后,通过createPointcutDefinition
创建一个 AbstractBeanDefinition 的值,也就是说,这个标签会被解析成一个 BeanDefinition,我们进入方法查看创建 BeanDefinition 的过程。
// org.springframework.aop.config.ConfigBeanDefinitionParser#createPointcutDefinitionprotectedAbstractBeanDefinitioncreatePointcutDefinition(Stringexpression) { RootBeanDefinitionbeanDefinition=newRootBeanDefinition(AspectJExpressionPointcut.class); beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE); beanDefinition.setSynthetic(true); beanDefinition.getPropertyValues().add(EXPRESSION, expression); returnbeanDefinition; }
创建 BeanDefinition 的代码比较简单,不过有几点我们需要留意。
- 这个 BeanDefinition 对应的类型是 AspectJExpressionPointcut。
- 通过
setSynthetic(true)
,它会被标记为一个合成的 Bean,也就是不是通过代码定义的类型。 - 会给它配置一个属性
expression
,值是其对应的切入点表达式。
回到parsePointcut
方法中,后面的流程就比较简单易懂,最终,它会被作为一个 BeanDefinition 注册到 Spring 容器中。
<aop:aspect>
标签的解析
下面再看解析标签的parseAspect
方法。
// org.springframework.aop.config.ConfigBeanDefinitionParser#parseAspectprivatevoidparseAspect(ElementaspectElement, ParserContextparserContext) { StringaspectId=aspectElement.getAttribute(ID); StringaspectName=aspectElement.getAttribute(REF); try { this.parseState.push(newAspectEntry(aspectId, aspectName)); List<BeanDefinition>beanDefinitions=newArrayList<>(); List<BeanReference>beanReferences=newArrayList<>(); List<Element>declareParents=DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS); for (inti=METHOD_INDEX; i<declareParents.size(); i++) { ElementdeclareParentsElement=declareParents.get(i); beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext)); } // We have to parse "advice" and all the advice kinds in one loop, to get the// ordering semantics right.NodeListnodeList=aspectElement.getChildNodes(); booleanadviceFoundAlready=false; for (inti=0; i<nodeList.getLength(); i++) { Nodenode=nodeList.item(i); if (isAdviceNode(node, parserContext)) { if (!adviceFoundAlready) { adviceFoundAlready=true; if (!StringUtils.hasText(aspectName)) { parserContext.getReaderContext().error( "<aspect> tag needs aspect bean reference via 'ref' attribute when declaring advices.", aspectElement, this.parseState.snapshot()); return; } beanReferences.add(newRuntimeBeanReference(aspectName)); } AbstractBeanDefinitionadvisorDefinition=parseAdvice( aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences); beanDefinitions.add(advisorDefinition); } } AspectComponentDefinitionaspectComponentDefinition=createAspectComponentDefinition( aspectElement, aspectId, beanDefinitions, beanReferences, parserContext); parserContext.pushContainingComponent(aspectComponentDefinition); List<Element>pointcuts=DomUtils.getChildElementsByTagName(aspectElement, POINTCUT); for (ElementpointcutElement : pointcuts) { parsePointcut(pointcutElement, parserContext); } parserContext.popAndRegisterContainingComponent(); } finally { this.parseState.pop(); } }
这个方法的代码量相对较多,但其实也并不复杂。首先还是会获取它的两个属性,id
和ref
,ref
就是增强逻辑所在的 Bean 的名称。
接下来,就需要解析它的子元素,也就是我们配置的每一个增强逻辑。对于每一个经过isAdviceNode
方法验证的符合条件的字元素,会通过parseAdvice
方法,创建其对应的 BeanDefinition,并添加到事先创建好的beanDefinitions
集合中。
先看isAdviceNode
方法。
// org.springframework.aop.config.ConfigBeanDefinitionParser#isAdviceNodeprivatebooleanisAdviceNode(NodeaNode, ParserContextparserContext) { if (!(aNodeinstanceofElement)) { returnfalse; } else { Stringname=parserContext.getDelegate().getLocalName(aNode); return (BEFORE.equals(name) ||AFTER.equals(name) ||AFTER_RETURNING_ELEMENT.equals(name) ||AFTER_THROWING_ELEMENT.equals(name) ||AROUND.equals(name)); } }
其实就是根据标签名称,判断是不是用来配置增强逻辑的标签。我们再看创建 BeanDefinition 的parseAdvice
方法。
privateAbstractBeanDefinitionparseAdvice( StringaspectName, intorder, ElementaspectElement, ElementadviceElement, ParserContextparserContext, List<BeanDefinition>beanDefinitions, List<BeanReference>beanReferences) { try { this.parseState.push(newAdviceEntry(parserContext.getDelegate().getLocalName(adviceElement))); // create the method factory beanRootBeanDefinitionmethodDefinition=newRootBeanDefinition(MethodLocatingFactoryBean.class); methodDefinition.getPropertyValues().add("targetBeanName", aspectName); methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method")); methodDefinition.setSynthetic(true); // create instance factory definitionRootBeanDefinitionaspectFactoryDef=newRootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class); aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName); aspectFactoryDef.setSynthetic(true); // register the pointcutAbstractBeanDefinitionadviceDef=createAdviceDefinition( adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef, beanDefinitions, beanReferences); // configure the advisorRootBeanDefinitionadvisorDefinition=newRootBeanDefinition(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)); } // register the final advisorparserContext.getReaderContext().registerWithGeneratedName(advisorDefinition); returnadvisorDefinition; } finally { this.parseState.pop(); } }
这个方法中,先创建了一个对应 MethodLocatingFactoryBean 类型的 BeanDefinition,其中包含了我们配置的增强逻辑的 Bean 名称和方法名称。然后又创建了一个对应 SimpleBeanFactoryAwareAspectInstanceFactory 类型的 BeanDefinition,包含增强逻辑所在 Bean 的名称。
之后,使用上述的两个 BeanDefinition 和标签上的配置信息,通过createAdviceDefinition
方法,创建了增强逻辑对应的 BeanDefinition。我们进入这个方法查看一下源码。
// org.springframework.aop.config.ConfigBeanDefinitionParser#createAdviceDefinitionprivateAbstractBeanDefinitioncreateAdviceDefinition( ElementadviceElement, ParserContextparserContext, StringaspectName, intorder, RootBeanDefinitionmethodDef, RootBeanDefinitionaspectFactoryDef, List<BeanDefinition>beanDefinitions, List<BeanReference>beanReferences) { RootBeanDefinitionadviceDefinition=newRootBeanDefinition(getAdviceClass(adviceElement, parserContext)); adviceDefinition.setSource(parserContext.extractSource(adviceElement)); adviceDefinition.getPropertyValues().add(ASPECT_NAME_PROPERTY, aspectName); adviceDefinition.getPropertyValues().add(DECLARATION_ORDER_PROPERTY, order); if (adviceElement.hasAttribute(RETURNING)) { adviceDefinition.getPropertyValues().add( RETURNING_PROPERTY, adviceElement.getAttribute(RETURNING)); } if (adviceElement.hasAttribute(THROWING)) { adviceDefinition.getPropertyValues().add( THROWING_PROPERTY, adviceElement.getAttribute(THROWING)); } if (adviceElement.hasAttribute(ARG_NAMES)) { adviceDefinition.getPropertyValues().add( ARG_NAMES_PROPERTY, adviceElement.getAttribute(ARG_NAMES)); } ConstructorArgumentValuescav=adviceDefinition.getConstructorArgumentValues(); cav.addIndexedArgumentValue(METHOD_INDEX, methodDef); Objectpointcut=parsePointcutProperty(adviceElement, parserContext); if (pointcutinstanceofBeanDefinition) { cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcut); beanDefinitions.add((BeanDefinition) pointcut); } elseif (pointcutinstanceofString) { RuntimeBeanReferencepointcutRef=newRuntimeBeanReference((String) pointcut); cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcutRef); beanReferences.add(pointcutRef); } cav.addIndexedArgumentValue(ASPECT_INSTANCE_FACTORY_INDEX, aspectFactoryDef); returnadviceDefinition; }
这个方法虽然很长,但都是增强逻辑对应的 BeanDefinition 的各种属性的配置,其中有一个需要留意的地方,就是在创建 BeanDefinition 时,需要通过getAdviceClass方法来获取要创建的 BeanDefinition 对应的 Bean 的类型,这个方法中的源码逻辑也比较简单,就是通过标签的名称,来找到对应的增强类型对应的 Java 类。
它们都是同一个类的子类。
再回到parseAdvice
方法中,接下来会创建一个对应 AspectJPointcutAdvisor 增强器类型的 BeanDefinition,它里面包含了刚刚创建的包含增强逻辑的信息的adviceDef
对象。
最后,会通过以下代码,将增强器的 BeanDefinition 注册到 Spring 容器中。
parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);
获取 XML 配置的增强
通过以上的分析,我们知道了,XML 中配置的切面信息,会把每一个增强逻辑都封装成一个 AspectJPointcutAdvisor 类型的 Bean 注册到 Spring 容器中。
AspectJPointcutAdvisor 类型的继承结构也很简单,我们看一下。
在上一篇【Spring 源码阅读 49:AOP 代理创建的过程 】中,我们分析了用于创建 AOP 代理的后处理器,会通过findCandidateAdvisors
方法来查找所有的增强逻辑。对于 XML 配置的切面信息,这个逻辑是在 AbstractAdvisorAutoProxyCreator 中实现的,我们看代码。
// org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findCandidateAdvisorsprotectedList<Advisor>findCandidateAdvisors() { Assert.state(this.advisorRetrievalHelper!=null, "No BeanFactoryAdvisorRetrievalHelper available"); returnthis.advisorRetrievalHelper.findAdvisorBeans(); }
这个逻辑是通过advisorRetrievalHelper
的findAdvisorBeans
方法来完成的,我们继续深入这个方法。
// org.springframework.aop.framework.autoproxy.BeanFactoryAdvisorRetrievalHelper#findAdvisorBeanspublicList<Advisor>findAdvisorBeans() { // Determine list of advisor bean names, if not cached already.String[] advisorNames=this.cachedAdvisorBeanNames; if (advisorNames==null) { // Do not initialize FactoryBeans here: We need to leave all regular beans// uninitialized to let the auto-proxy creator apply to them!advisorNames=BeanFactoryUtils.beanNamesForTypeIncludingAncestors( this.beanFactory, Advisor.class, true, false); this.cachedAdvisorBeanNames=advisorNames; } if (advisorNames.length==0) { returnnewArrayList<>(); } List<Advisor>advisors=newArrayList<>(); for (Stringname : advisorNames) { if (isEligibleBean(name)) { if (this.beanFactory.isCurrentlyInCreation(name)) { // 省略日志逻辑。。。 } else { try { advisors.add(this.beanFactory.getBean(name, Advisor.class)); } catch (BeanCreationExceptionex) { // 省略异常处理的逻辑。。。 } } } } returnadvisors; }
代码很多,但是逻辑很简单,从当前的 Spring 容器中获取到所有的 Advisor 类型的 Bean 的名称,然后遍历这些名称,将每一个名称对应的 Bean 对象从容器中加载出来,添加到事先创建好的advisors
集合,并作为结果返回。
这里有两个值得单独提一下。第一,在加载 XML 配置时为每一个增强逻辑创建的 BeanDefinition 对应的 Bean 类型是 AspectJPointcutAdvisor,它是 Advisor 的实现类,因此,会被这个方法的逻辑筛选到。第二,遍历每一个 Bean 名称的时候,经过了isEligibleBean
方法的筛选,不过这个方法的实现中直接返回了true
。
总结
本文分析了 XML 配置的切面信息是如何被 Spring 加载到,以及 Spring 的后处理器是如何在创建 AOP 代理之前将所有增强器的信息 Advisor 找到的。将这些 Advisor 找到之后,不管是 XML 还是注解配置的 AOP 切面处理流程就都是一样的了,因此,再分析后续流程之前,下一篇将会分析注解配置的切面信息是如何被后处理器找到的。