Spring AOP 源码系列(一)解析 AOP 配置信息

本文深入分析 Spring AOP 的源码实现,详细解读 AOP 配置的加载过程,包括 XML 配置的解析、AspectJAwareAdvisorAutoProxyCreator 的注册以及切面、通知和切点的定义。

在进行源码阅读之前建议先看一下这篇文章:Spring AOP 源码分析系列文章导读 by 田小波,写的非常好,推荐阅读。

关于 AOP 中常用的一些术语这里就不解释了,如果不清楚的建议先看一遍上面推荐的文章。

一、AOP 配置入口

在分析源码之前,我们先来看一下 XML 中关于 AOP 的配置:

    <bean id="userServiceImpl" class="com.jas.mess.aop.UserServiceImpl"/>
    <bean id="ownerAspect" class="com.jas.mess.aop.spring.OwnerAspect"/>

    <aop:config>
        <aop:aspect ref="ownerAspect">
            <aop:pointcut id="ownerPointcut" expression="execution(* com.jas.mess.aop.*.*(..))"/>
            <aop:before method="recordLog" pointcut-ref="ownerPointcut"/>
        </aop:aspect>
    </aop:config>

在以前的文章里有总结过,XML 文件的配置信息会被转化成 BeanDefinition,其中 AOP 相关的配置也不例外,从上面的例子中我们能看出一个明显的区别:普通的 bean 标签与 AOP 配置标签不一样,一个是 <bean /> 一个是 <aop />,除此之外你要想配置 AOP,还需要在 XMl 中引入 xmlns:aop="http://www.springframework.org/schema/aop" 命名空间。

普通的 bean 标签与 AOP 标签处理逻辑通过命名空间区分,下面我们来看源码。

    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        // 命名空间检查,默认的命名空间是 "http://www.springframework.org/schema/beans"
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++) {
                Node node = nl.item(i);
                if (node instanceof Element) {
                    Element ele = (Element) node;
                    // <bean /> 标签处理入口
                    if (delegate.isDefaultNamespace(ele)) {
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        // <aop /> 标签处理入口
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }

上面我们说过,AOP 的命名空间是 http://www.springframework.org/schema/aop,因此会执行到 delegate.parseCustomElement(ele)

    public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
        String namespaceUri = getNamespaceURI(ele);
        if (namespaceUri == null) {
            return null;
        }
        // 根据命名空间获取 handler
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        }
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }


    public BeanDefinition parse(Element element, ParserContext parserContext) {
        BeanDefinitionParser parser = findParserForElement(element, parserContext);
        return (parser != null ? parser.parse(element, parserContext) : null);
    }

上面的流程比较简单,根据命名空间 URI 获取命名空间 handler,这里对应的是 AopNamespaceHandler,通过 handler 去解析配置。handler 内部会根据标签配置信息得到 ConfigBeanDefinitionParser,最终调用 parse 方法。

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        CompositeComponentDefinition compositeDef =
                new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
        parserContext.pushContainingComponent(compositeDef);

        // 注册 AspectJAwareAdvisorAutoProxyCreator 类型的beanDefinition,继承自 BeanPostProcessor
        configureAutoProxyCreator(parserContext, element);

        // 获取所有子标签
        List<Element> childElts = DomUtils.getChildElements(element);
        for (Element elt: childElts) {
            String localName = parserContext.getDelegate().getLocalName(elt);
            // 解析 <aop:poincut />
            if (POINTCUT.equals(localName)) {
                parsePointcut(elt, parserContext);
            }
            // 解析 <aop:>advisor />
            else if (ADVISOR.equals(localName)) {
                parseAdvisor(elt, parserContext);
            }
            // 解析 <aop:aspect />
            else if (ASPECT.equals(localName)) {
                parseAspect(elt, parserContext);
            }
        }

        parserContext.popAndRegisterContainingComponent();
        return null;
    }

这里涉及到一个 <aop:advisor /> 标签,大家可能不太清楚它的作用,advisor 与 advice(<aop:before /><aop:after />…)功能类似,都可以用来增强目标方法,具体用法可以参考这篇文章:Spring Aop(八)——advisor标签 by 234390216

parse 方法会处理 <aop:config /> 标签下的子标签,具体的处理逻辑后面会分析到。在解析标签之前先调用了 configureAutoProxyCreator 方法,这个方法比较重要。

二、注册 AspectJAwareAdvisorAutoProxyCreator beanDefinition

    private void configureAutoProxyCreator(ParserContext parserContext, Element element) {
        AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element);
    }

    public static void registerAspectJAutoProxyCreatorIfNecessary(
            ParserContext parserContext, Element sourceElement) {
        BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(
                parserContext.getRegistry(), parserContext.extractSource(sourceElement));

        useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);

        registerComponentIfNecessary(beanDefinition, parserContext);
    }

registerAspectJAutoProxyCreatorIfNecessary方法中有三个处理逻辑,下面一一来看下。

    @Nullable
    public static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(
            BeanDefinitionRegistry registry, @Nullable Object source) {
        // 注意这里传的是 AspectJAwareAdvisorAutoProxyCreator 类型
        return registerOrEscalateApcAsRequired(AspectJAwareAdvisorAutoProxyCreator.class, registry, source);
    }

    private static BeanDefinition registerOrEscalateApcAsRequired(
            Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {

        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");

        // AUTO_PROXY_CREATOR_BEAN_NAME = "org.springframework.aop.config.internalAutoProxyCreator"
        if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
            BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
            if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
                int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
                int requiredPriority = findPriorityForClass(cls);
                if (currentPriority < requiredPriority) {
                    apcDefinition.setBeanClassName(cls.getName());
                }
            }
            return null;
        }

        RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
        beanDefinition.setSource(source);
        beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        // 注册一个类型为 AspectJAwareAdvisorAutoProxyCreator 的 beanDefinition
        registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
        return beanDefinition;
    }

注册一个类型为 AspectJAwareAdvisorAutoProxyCreator 的 beanDefinition,注意此时还没有创建 bean,AspectJAwareAdvisorAutoProxyCreator 其实是一个 BeanPostProcessor,后面在对方法增强的时候会用到,具体细节会在后面的文章里总结。

    private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable 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:config />是否配置了 proxy-target-classexpose-proxy,如果有配置,则为 beanDefinition 添加 exposeProxyproxyTargetClass 属性,这两个配置标签后面还会遇到,这里先简单介绍下作用。

proxy-target-class:默认为 false,如果设置为 true,会使用 CGLIB 创建代理对象
expose-proxy:默认为 false,作用:假设 A,B 方法都被增强,A 方法调用 B 方法,A 方法会被切面拦截,A 里的 B 方法是否会被切面拦截增强,默认不会

private static void registerComponentIfNecessary(@Nullable BeanDefinition beanDefinition, ParserContext parserContext) {
        if (beanDefinition != null) {
            parserContext.registerComponent(
                    new BeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME));
        }
    }

注册组件。

三、解析 <aop:aspect ref="" id="" />

<aop:aspect /> 标签下可以配置多种类型的子标签,比如 <aop:before /><aop:after />、<aop:pointcut /><aop:declare-parents /> 等,不同类型的标签处理逻辑也是不一样的。

    private void parseAspect(Element aspectElement, ParserContext parserContext) {
        String aspectId = aspectElement.getAttribute(ID);
        // 切面 beanName
        String aspectName = aspectElement.getAttribute(REF);

        try {
            // 添加进 LinkedList
            this.parseState.push(new AspectEntry(aspectId, aspectName));
            List<BeanDefinition> beanDefinitions = new ArrayList<>();
            List<BeanReference> beanReferences = new ArrayList<>();

            // 获取  <aop:declare-parents /> 子标签
            List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);
            for (Element declareParentsElement : declareParents) {
                beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));
            }

            NodeList nodeList = aspectElement.getChildNodes();
            boolean adviceFoundAlready = false;
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                // 处理通知节点 <aop:before,after, around ... />
                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(new RuntimeBeanReference(aspectName));
                    }
                    // 处理通知
                    AbstractBeanDefinition advisorDefinition = parseAdvice(
                            aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
                    beanDefinitions.add(advisorDefinition);
                }
            }

            // 创建 AspectComponentDefinition
            AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(
                    aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
            parserContext.pushContainingComponent(aspectComponentDefinition);

            // 获取所有切点 element
            List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
            for (Element pointcutElement : pointcuts) {
                parsePointcut(pointcutElement, parserContext);
            }

            parserContext.popAndRegisterContainingComponent();
        }
        finally {
            this.parseState.pop();
        }
    }

主要流程如下:

  1. 获取 <aop:aspect > 的 id 与 ref 属性,封装成 AspectEntry 添加到 parseState 集合中,流程处理完后,出栈
  2. 获取 <aop:declare-parents /> 子标签,如果有配置则封装成 beanDefinition
  3. 解析 <aop:aspect > 下所有子标签,循环处理通知配置
  4. 解析切点配置
   private AbstractBeanDefinition parseAdvice(
           String aspectName, int order, Element aspectElement, Element adviceElement, ParserContext parserContext,
           List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {

       try {
           this.parseState.push(new AdviceEntry(parserContext.getDelegate().getLocalName(adviceElement)));

           RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);
           // 切面 bean(类)
           methodDefinition.getPropertyValues().add("targetBeanName", aspectName);
           // 设置切面方法
           methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));
           methodDefinition.setSynthetic(true);

           // 创建切面 BeanDefinition
           RootBeanDefinition aspectFactoryDef =
                   new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
           aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);
           aspectFactoryDef.setSynthetic(true);

           // 创建通知 BeanDefinition,此时已明确通知类型
           AbstractBeanDefinition adviceDef = createAdviceDefinition(
                   adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,
                   beanDefinitions, beanReferences);

           RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);
           advisorDefinition.setSource(parserContext.extractSource(adviceElement));
           // 将上面创建的 adviceDef 注入到 advisorDefinition 中
           advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);
           // <aop:aspect ref="" order="" />,控制切面方法优先级
           if (aspectElement.hasAttribute(ORDER_PROPERTY)) {
               advisorDefinition.getPropertyValues().add(
                       ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY));
           }

           // 注册通知 BeanDefinition
           parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);

           return advisorDefinition;
       }
       finally {
           this.parseState.pop();
       }
   }

上面逻辑主要是创建出 advisorDefinition,并注册到 beanFactory 中,由此可见,我们配置的每一个通知,最终都会被转化成 beanDefinition 并注册到 IoC 容器中。

上面这个方法深追下去知识点还挺多的,比如 createAdviceDefinition 方法,注册 beanDefinition 时 beanName 的生成规则等,有兴趣的自己可以研究下,这里就不介绍了,看不懂的地方可以通过 DEBUG 的方式帮助理解。

    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));
            // 创建切点 beanDefinition,类型为 ConfigurableBeanFactory.SCOPE_PROTOTYPE
            pointcutDefinition = createPointcutDefinition(expression);
            pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));

            String pointcutBeanName = id;
            // 注册切点 BeanDefinition 到 beanFactory 中
            if (StringUtils.hasText(pointcutBeanName)) {
                parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);
            }
            else {
                // <aop:pointcut id="" expression=""/> 没有配置 id 先生成 beanName 在注册到 beanFactory 中
                pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition);
            }

            parserContext.registerComponent(
                    new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression));
        }
        finally {
            this.parseState.pop();
        }

        return pointcutDefinition;
    }

    protected AbstractBeanDefinition createPointcutDefinition(String expression) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition(AspectJExpressionPointcut.class);
        // 切点作用域原型
        beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
        beanDefinition.setSynthetic(true);
        beanDefinition.getPropertyValues().add(EXPRESSION, expression);
        return beanDefinition;
    }

处理切点的逻辑与通知类似,注意切点 beanDefinition 的作用域并非单例而是原型,初始化 beanDefinition 后注册到 IoC 容器中。

参考

【Spring源码分析】AOP源码解析(上篇) by 五月的仓颉

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值