写点什么

Spring 系列之 AOP 工作过程详解一

作者:王威07325
  • 2023-05-29
    上海
  • 本文字数:5560 字

    阅读完需:约 18 分钟

引言

spring 的 IOC 系列已经正式完结了,这一个星期一直在想着接下来更新什么系列,想了想,面试里面不就是 IOC 和 AOP 考的最多吗?那么就索性接下来开始更新 AOP 系列的文章,就非常的河狸


aop:config

aop 部分的解析器由 AopNamespaceHandler 注册,其 init 方法:


@Overridepublic void init() {    registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());    registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());    registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());}
复制代码


而 init 方法的调用则是在 Spring 解析 XMl 文件过程中的 DefaultNamespaceHandlerResolver#resolve 方法中。


其中 ConfigBeanDefinitionParser 用于解析 aop:config,的此标签用以配置 pointcut, advisor, aspect,实例:


<aop:config expose-proxy="true" proxy-target-class="true">    <aop:pointcut expression="execution(* exam.service..*.*(..))" id="transaction"/>    <aop:advisor advice-ref="txAdvice" pointcut-ref="transaction"/>    <aop:aspect ref="" /></aop:config>
复制代码


ConfigBeanDefinitionParser.parse:


@Override  public BeanDefinition parse(Element element, ParserContext parserContext) {    CompositeComponentDefinition compositeDef =        new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));    parserContext.pushContainingComponent(compositeDef);
configureAutoProxyCreator(parserContext, element);
List<Element> childElts = DomUtils.getChildElements(element); for (Element elt: childElts) { String localName = parserContext.getDelegate().getLocalName(elt); if (POINTCUT.equals(localName)) { parsePointcut(elt, parserContext); } else if (ADVISOR.equals(localName)) { parseAdvisor(elt, parserContext); } else if (ASPECT.equals(localName)) { parseAspect(elt, parserContext); } }
parserContext.popAndRegisterContainingComponent(); return null; }
复制代码


还是分部分讲解:


  1. 解析

  2. 代理子类的生成


这篇文章只讲解第一部分“解析过程”

解析

解析的过程主要分为以下几个部分。


proxy-target-class & expose-proxy


对应着 aop:config 的两个属性,前者代表是否为被代理这生成 CGLIB 子类,默认 false,只为接口生成代理子类(话说如果不生成子类那么怎么拦截?)。后者代表是否将代理 bean 暴露给用户,如果暴露,可以通过 Spring AopContext 类获得,默认不暴露。


解析的过程无非就是属性的读取,不再详细说明。


对应代码:


AopNamespaceUtils:


private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, Element sourceElement) {    if (sourceElement != null) {      boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));      if (proxyTargetClass) {        AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);      }      boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));      if (exposeProxy) {        AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);      }    }  }
复制代码


aop:pointcut


pointcut 的解析是生成一个 BeanDefinition 并将其 id, expression 等属性保存在 BeanDefinition 中。注意以下几点:


  • BeanDefinition 的 ID 来自于 id 属性,如果没有,那么自动生成。

  • BeanDefinition 的 class 是 AspectJExpressionPointcut

  • BeanDefinition 的 scope 为 prototype。


AspectJExpressionPointcut 类图:



对应代码:ConfigBeanDefinitionParser:


private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {    String id = pointcutElement.getAttribute(ID);    String expression = pointcutElement.getAttribute(EXPRESSION);
AbstractBeanDefinition pointcutDefinition = null;
try { this.parseState.push(new PointcutEntry(id)); pointcutDefinition = createPointcutDefinition(expression); pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));
String pointcutBeanName = id; if (StringUtils.hasText(pointcutBeanName)) { parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition); } else { pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition); }
parserContext.registerComponent( new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression)); } finally { this.parseState.pop(); }
return pointcutDefinition; }
复制代码


aop:advisor


首先是其所有属性的示例:


<aop:advisor id="" order="" advice-ref="aopAdvice" pointcut="" pointcut-ref="" />
复制代码


advisor 概念是 Spring 独有的,来自于上古时代,应该是较早时候的 aop 概念的实现: AOP Alliance (Java/J2EE AOP standards)。Spring 官方的说法: aop-schema-advisors。


advice-ref 是必须的属性,并且这里的 advice 必须实现 org.aopalliance.aop.Advice 的子接口。这些子接口指的什么呢?比如 org.aopalliance.intercept.MethodInterceptor


最常见的用途就是结合事务使用:


<tx:advice id="txAdvice" transaction-manager="transactionManager">    <tx:attributes>        <tx:method name="get*" read-only="true" propagation="NOT_SUPPORTED"/>        <tx:method name="find*" read-only="true" propagation="NOT_SUPPORTED"/>        <tx:method name="*" propagation="REQUIRED"/>    </tx:attributes></tx:advice>
<aop:config> <aop:pointcut expression="execution(* exam.service..*.*(..))" id="transaction"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="transaction"/></aop:config>
复制代码


解析的套路和上面类似,只不过此处的 beanClass 是 DefaultBeanFactoryPointcutAdvisor,其类图:



另外注意对于 pointcut 和 pointcut-ref 两者处理的区别,对于 pointcut 属性,Spring 会同样创建一个 AspectJExpressionPointcut 类型的 BeanDefinition,对于 pointcut-ref 会生成一个 RuntimeBeanReference 对象指向原 pointcut 的引用。此类的类图:



可以看出,这种 aop 的实现需要实现各种接口,所以不应该再使用此种方式进行 aop,除了 Spring 内部的实现。


对应代码:


/**   * Parses the supplied {@code <advisor>} element and registers the resulting   * {@link org.springframework.aop.Advisor} and any resulting {@link org.springframework.aop.Pointcut}   * with the supplied {@link BeanDefinitionRegistry}.   */  private void parseAdvisor(Element advisorElement, ParserContext parserContext) {    AbstractBeanDefinition advisorDef = createAdvisorBeanDefinition(advisorElement, parserContext);    String id = advisorElement.getAttribute(ID);
try { this.parseState.push(new AdvisorEntry(id)); String advisorBeanName = id; if (StringUtils.hasText(advisorBeanName)) { parserContext.getRegistry().registerBeanDefinition(advisorBeanName, advisorDef); } else { advisorBeanName = parserContext.getReaderContext().registerWithGeneratedName(advisorDef); }
Object pointcut = parsePointcutProperty(advisorElement, parserContext); if (pointcut instanceof BeanDefinition) { advisorDef.getPropertyValues().add(POINTCUT, pointcut); parserContext.registerComponent( new AdvisorComponentDefinition(advisorBeanName, advisorDef, (BeanDefinition) pointcut)); } else if (pointcut instanceof String) { advisorDef.getPropertyValues().add(POINTCUT, new RuntimeBeanReference((String) pointcut)); parserContext.registerComponent( new AdvisorComponentDefinition(advisorBeanName, advisorDef)); } } finally { this.parseState.pop(); } }
复制代码


aop:aspect


配置举例:


<bean id="aopAdvice" class="base.aop.AopDemoAdvice" /><!-- 必须配置,因为被代理的对象必须在Spring容器中 --><bean id="aopDemo" class="base.aop.AopDemo" /><aop:config>    <aop:pointcut id="pointcut" expression="execution(* base.aop.AopDemo.send())" />    <aop:aspect ref="aopAdvice">        <aop:before method="beforeSend" pointcut-ref="pointcut" />        <aop:after method="afterSend" pointcut-ref="pointcut" />    </aop:aspect></aop:config>
复制代码


解析形成的 BeanDefinition 结构如下:


AspectComponentDefinition    beanRefArray        RuntimeBeanReference(aop:aspect的ref属性)    beanDefArray        // 被注册        RootBeanDefinition(aop:declare-parents)            beanClass: DeclareParentsAdvisor            ConstructorArg                implement-interface                types-matching                default-impl                delegate-ref        // 被注册        RootBeanDefinition(aop:before,aop:after...)            beanClass: AspectJPointcutAdvisor            ConstructorArg                RootBeanDefinition                    beanClass: 由子标签决定                    ConstructorArg                        RootBeanDefinition                            beanClass: MethodLocatingFactoryBean                            properties                                targetBeanName: aspectName                                methodName: method属性                        RootBeanDefinition                            beanClass: SimpleBeanFactoryAwareAspectInstanceFactory                            properties                                aspectBeanName: aspectName                        //还有pointcut定义和引用...
复制代码


结构图里面的 aspectName 来自于 aop:aspect 的 ref 属性,此属性是必须配置的,因为 Spring 要知道 aop:before 等标签指定的方法是哪个 bean/类/对象的方法。


aop:declare-parents 对于 aop:declare-parents 子标签,其决定的是代理子类应该实现哪些接口:


<aop:declare-parents types-matching="" implement-interface="" />
复制代码


此标签最终被解析成为 beanClass 为 DeclareParentsAdvisor 的 BeanDefinition,并注册到容器中。其类图:



其它此处的其它指的是 aop:before, aop:after 等最核心的标签。其最终被解析为 beanClass 为 AspectJPointcutAdvisor 的 BeanDefinition,类图:



正如上面结构图里所描述的,其构造参数为一个 BeanDefintion,此对象的 beanClass 是不确定的,由 aop:before/after 中的 before 和 after 决定,代码:


private Class<?> getAdviceClass(Element adviceElement, ParserContext parserContext) {    String elementName = parserContext.getDelegate().getLocalName(adviceElement);    if (BEFORE.equals(elementName)) {        return AspectJMethodBeforeAdvice.class;    } else if (AFTER.equals(elementName)) {        return AspectJAfterAdvice.class;    } else if (AFTER_RETURNING_ELEMENT.equals(elementName)) {        return AspectJAfterReturningAdvice.class;    } else if (AFTER_THROWING_ELEMENT.equals(elementName)) {        return AspectJAfterThrowingAdvice.class;    } else if (AROUND.equals(elementName)) {        return AspectJAroundAdvice.class;    }}
复制代码


而此 BeanDefintion 的构造参数又由以下几个部分组成:


  1. MethodLocatingFactoryBean

  2. SimpleBeanFactoryAwareAspectInstanceFactory


MethodLocatingFactoryBean 第一个便是 beanClass 为此类型的 BeanDefinition。其内部有一个 methodName 属性,存储的便是标签的 method 属性的值。其类图:



这个东西是干什么用的呢?其实是用于在指定的 advice(aop:aspect 的 ref 属性)中得到 Method 对象。入口在 setBeanFactory 方法:


@Overridepublic void setBeanFactory(BeanFactory beanFactory) {    Class<?> beanClass = beanFactory.getType(this.targetBeanName);    this.method = BeanUtils.resolveSignature(this.methodName, beanClass);}
复制代码


SimpleBeanFactoryAwareAspectInstanceFactory 其类图:



此类用于在 BeanFactory 中定位 aspect bean,这个 bean 指的是谁?


<bean id="aopAdvice" class="base.aop.AopDemoAdvice" />
复制代码


就是它!查找很简单:


@Overridepublic Object getAspectInstance() {    return this.beanFactory.getBean(this.aspectBeanName);}
复制代码


从整个 aop:aspect 标签最终被解析为一个 AspectJPointcutAdvisor 来看,Spring 在实现上仍将其作为 Advisor 的概念。


稍微总结一下标签与 BeanDefinition 中的 beanClass 之间的关系:



用户头像

王威07325

关注

还未添加个人签名 2021-12-29 加入

还未添加个人简介

评论

发布
暂无评论
Spring系列之AOP工作过程详解一_spring_王威07325_InfoQ写作社区