Spring 源码阅读 50:解析 XML 中的切面配置

简介: 本文分析了 XML 配置的切面信息是如何被 Spring 加载到,以及 Spring 的后处理器是如何在创建 AOP 代理之前将所有增强器的信息 Advisor 找到的。

基于 Spring Framework v5.2.6.RELEASE

接上篇:Spring 源码阅读 49:AOP 代理创建的过程

相关内容:Spring 源码阅读 47:在 XML 配置中开启 AOP 特性的原理分析

概述

上一篇分析了wrapIfNecessary方法创建 AOP 代理的大致过程,其中最先执行的步骤,就是获取到 Spring 中所有已配置的增强,而要获取到这些增强的信息,就需要事先将这些配置加载到 Spring 中。本文我们就来分析通过 XML 配置的切面信息是如何被加载的。

本文的分析内容,以下面这段配置为例:

<?xmlversion="1.0" encoding="UTF-8"?><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 文件中的切面配置。

找到方法的源码。

@Override@NullablepublicBeanDefinitionparse(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;
}

首先,获取了标签的idexpression属性,其中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();
   }
}

这个方法的代码量相对较多,但其实也并不复杂。首先还是会获取它的两个属性,idrefref就是增强逻辑所在的 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 类。

image.png

它们都是同一个类的子类。

再回到parseAdvice方法中,接下来会创建一个对应 AspectJPointcutAdvisor 增强器类型的 BeanDefinition,它里面包含了刚刚创建的包含增强逻辑的信息的adviceDef对象。

最后,会通过以下代码,将增强器的 BeanDefinition 注册到 Spring 容器中。

parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);

获取 XML 配置的增强

通过以上的分析,我们知道了,XML 中配置的切面信息,会把每一个增强逻辑都封装成一个 AspectJPointcutAdvisor 类型的 Bean 注册到 Spring 容器中。

AspectJPointcutAdvisor 类型的继承结构也很简单,我们看一下。

image.png

在上一篇【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();
}

这个逻辑是通过advisorRetrievalHelperfindAdvisorBeans方法来完成的,我们继续深入这个方法。

// 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 切面处理流程就都是一样的了,因此,再分析后续流程之前,下一篇将会分析注解配置的切面信息是如何被后处理器找到的。

目录
相关文章
|
13小时前
|
XML Java 数据格式
Spring高手之路18——从XML配置角度理解Spring AOP
本文是全面解析面向切面编程的实践指南。通过深入讲解切面、连接点、通知等关键概念,以及通过XML配置实现Spring AOP的步骤。
20 6
Spring高手之路18——从XML配置角度理解Spring AOP
|
1天前
PandasTA 源码解析(二十三)
PandasTA 源码解析(二十三)
7 0
|
1天前
PandasTA 源码解析(二十二)(3)
PandasTA 源码解析(二十二)
5 0
|
1天前
PandasTA 源码解析(二十二)(2)
PandasTA 源码解析(二十二)
9 2
|
1天前
PandasTA 源码解析(二十二)(1)
PandasTA 源码解析(二十二)
6 0
|
1天前
PandasTA 源码解析(二十一)(4)
PandasTA 源码解析(二十一)
7 1
|
1天前
PandasTA 源码解析(二十一)(3)
PandasTA 源码解析(二十一)
6 0
|
1天前
PandasTA 源码解析(二十一)(2)
PandasTA 源码解析(二十一)
6 1
|
1天前
PandasTA 源码解析(二十一)(1)
PandasTA 源码解析(二十一)
8 2
|
1天前
PandasTA 源码解析(二十)(1)
PandasTA 源码解析(二十)
7 0

推荐镜像

更多