Spring的AOP原理实现分析(上)

Spring中AOP的实现主要分为两个部分,一是将在xml中定义的切面信息解析并且注册到BeanFactory中,二是根据切面的相关的信息生成动态代理,实现在运行时对相关对象行为的修改。本篇博客主要介绍Spring是如何将在xml中定义的切面信息解析出来并注册到BeanFactory中的。
本次测试的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:context="http://www.springframework.org/schema/context" 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/context http://www.springframework.org/schema/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
    <bean id="timeHandler1" class="com.aop.TimeHandler"/>
    <bean id="helloWorld" class="com.pointcut.HelloWorld"/>
    <bean id="before_advisor" class="com.aop.BeforeAdvisor"/>
    <aop:config>
        <aop:aspect id="time" ref="timeHandler1">
            <aop:pointcut id="addAllMethod" expression="execution(* com.pointcut.HelloWorld.printHelloWorld(..))"/>
            <aop:before  method="printTimeStamp" pointcut-ref="addAllMethod"/>
            <aop:after method="printTimeStamp" pointcut-ref="addAllMethod"/>
        </aop:aspect>
    </aop:config>
</beans>

直接从开始解析的代码出进行分析:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        RuntimeException runtimeException = new RuntimeException("test");
        logger.info("",runtimeException);
        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;
                    if (delegate.isDefaultNamespace(ele)) {
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }

由于在解析aop的时候,这种Element不是namespace中的,因此会执行parseCustomElement(ele);方法。

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
        String namespaceUri = getNamespaceURI(ele);
        if (namespaceUri == null) {
            return null;
        }
        logger.info("namespaceUri = "+namespaceUri);
        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));
    }

这个会根据aop的NamespaceUri来获取对应的NamespaceHandler,其中部分NamespaceHandler与NamespaceUri对应关系如下:

http://www.springframework.org/schema/aop-----org.springframework.aop.config.AopNamespaceHandler
http://www.springframework.org/schema/task-----org.springframework.scheduling.config.TaskNamespaceHandler
http://www.springframework.org/schema/lang-----org.springframework.scripting.config.LangNamespaceHandler
http://www.springframework.org/schema/c-----org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http://www.springframework.org/schema/jee-----org.springframework.ejb.config.JeeNamespaceHandler
http://www.springframework.org/schema/cache-----org.springframework.cache.config.CacheNamespaceHandler
http://www.springframework.org/schema/jdbc-----org.springframework.jdbc.config.JdbcNamespaceHandler
http://www.springframework.org/schema/p-----org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http://www.springframework.org/schema/util-----org.springframework.beans.factory.xml.UtilNamespaceHandler
http://www.springframework.org/schema/tx-----org.springframework.transaction.config.TxNamespaceHandler
http://www.springframework.org/schema/context-----org.springframework.context.config.ContextNamespaceHandler

接下来将会执行Handler的parser方法,在AopNamespaceHandler中parser方法没有实现,程序调用的是其父类(NamespaceHandlerSupport)的方法

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        BeanDefinitionParser parser = findParserForElement(element, parserContext);
        //获取的parser为ConfigBeanDefinitionParser对象
        return (parser != null ? parser.parse(element, parserContext) : null);
    }

根据不同的Element查询不同的Parser,在解析的时候首先解析的为config,根据AopNamespaceHandler初始化的结果,根据config获取的Parser为ConfigBeanDefinitionParser,使用ConfigBeanDefinitionParser解析config元素。
ConfigBeanDefinitionParser中的parser方法:

    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;
    }

这里有一个入栈和出栈的操作,之后在解析<aop:config>中每一个子元素是都会出现这种操作,这种操作的目的一方面是因为元素的解析具有一定的顺序要求,另一方是为了绑定元素之间的关系,具体的来说就是切面与切点之间的关系。
首先解析<aop:aspect>标签:
最终是实现向BeanFactory中注册一个BeanDefinition的操作

    private void parseAspect(Element aspectElement, ParserContext parserContext) {
        String aspectId = aspectElement.getAttribute(ID);//根据配置文件id="time"
        String aspectName = aspectElement.getAttribute(REF);//根据配置文件ref="timeHandler1"

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

            List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);//根据declare-parent解析出来的bean信息
            for (int i = METHOD_INDEX; i < declareParents.size(); i++) {
                Element declareParentsElement = 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.
            NodeList nodeList = aspectElement.getChildNodes();
            boolean adviceFoundAlready = false;
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                if (isAdviceNode(node, parserContext)) {
                    if (!adviceFoundAlready) {
                        adviceFoundAlready = true;
                        beanReferences.add(new RuntimeBeanReference(aspectName));
                    }
                    AbstractBeanDefinition advisorDefinition = parseAdvice(
                            aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
                    beanDefinitions.add(advisorDefinition);//解析<aop:after>等指明切点和切入时机的标签
                }
            }
            //创建一个切面组件的定义,主要是根据前面解析封装好的通知器信息创建一个切面的信息
            AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(
                    aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
            parserContext.pushContainingComponent(aspectComponentDefinition);

            //解析出这个切面的左右的切点
            List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
            for (Element pointcutElement : pointcuts) {
                parsePointcut(pointcutElement, parserContext);
            }
            //绑定切入点与切入面直接的关系
            parserContext.popAndRegisterContainingComponent();
        }
        finally {
            this.parseState.pop();
        }
    }

首先在测试的配置文件中并没有使用declare-parent标签,关于解析declare-parent标签部分就不分析了。
先看一下如何解析出一个切面的所有通知器,代码如下:

    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)));

            // create the method factory bean
            RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);
            methodDefinition.getPropertyValues().add("targetBeanName", aspectName);//设置切面bean的名称
            methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));//获取切面方法
            methodDefinition.setSynthetic(true);

            // create instance factory definition
            //这个工厂主要用来辅助生成通知
            //工厂类为:SimpleBeanFactoryAwareAspectInstanceFactory
            RootBeanDefinition aspectFactoryDef =
                    new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
            aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);
            aspectFactoryDef.setSynthetic(true);

            // register the pointcut
            //创建一个通知
            AbstractBeanDefinition adviceDef = createAdviceDefinition(
                    adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,
                    beanDefinitions, beanReferences);

            // configure the advisor
            //根据创建的通知,创建一个通知器
            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));
            }

            // register the final advisor
            parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);

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

最终会将每个切入点通知器封装成一个RootBeanDefinition,其内部包含AspectJPointcutAdvisor类相关属性信息。生成advisorDefinition 的时候需要知道这个切入点通知器所关注的切入点信息,这个信息有adviceDef 提供,在createAdviceDefinition方法将会对pointcut和pointcut-ref属性进行解析,以获取有效的切入点信息。
方法如下:

开始创建一个通知:
    private AbstractBeanDefinition createAdviceDefinition(
            Element adviceElement, ParserContext parserContext, String aspectName, int order,
            RootBeanDefinition methodDef, RootBeanDefinition aspectFactoryDef,
            List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {

        //根据Element获取具体的通知实现类
        RootBeanDefinition adviceDefinition = new RootBeanDefinition(getAdviceClass(adviceElement, parserContext));
        adviceDefinition.setSource(parserContext.extractSource(adviceElement));
        //将标签中解析出来的内容填充到新建的adviceDefinition对象中
        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));
        }

        ConstructorArgumentValues cav = adviceDefinition.getConstructorArgumentValues();
        cav.addIndexedArgumentValue(METHOD_INDEX, methodDef);

        //解析出当前通知器关注的点,关注点分为:pointcut和pointcut-ref.
        Object pointcut = parsePointcutProperty(adviceElement, parserContext);
        if (pointcut instanceof BeanDefinition) {
            cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcut);
            beanDefinitions.add((BeanDefinition) pointcut);
        }
        else if (pointcut instanceof String) {
            RuntimeBeanReference pointcutRef = new RuntimeBeanReference((String) pointcut);
            cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcutRef);
            beanReferences.add(pointcutRef);
        }

        cav.addIndexedArgumentValue(ASPECT_INSTANCE_FACTORY_INDEX, aspectFactoryDef);

        return adviceDefinition;
    }

在将所有的通知器解析出来之后,开始生成切面对象。关于切面的对象,只是简单的使用相关的信息实例化一个对象:

    private AspectComponentDefinition createAspectComponentDefinition(
            Element aspectElement, String aspectId, List<BeanDefinition> beanDefs,
            List<BeanReference> beanRefs, ParserContext parserContext) {

        BeanDefinition[] beanDefArray = beanDefs.toArray(new BeanDefinition[0]);
        //这里的beanDefArray每个元素都是一个切入点通知器,可以通过打印元素数量判断分析的是否正确,以本次测试的xml为例,beanDefArray的长度为2
        BeanReference[] beanRefArray = beanRefs.toArray(new BeanReference[0]);
        Object source = parserContext.extractSource(aspectElement);
        return new AspectComponentDefinition(aspectId, beanDefArray, beanRefArray, source);
    }

下面开始解析切入点标签<aop:pointcut>,代码如下:

    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;
    }

最终构建一个pointcutDefinition,并将pointcutDefinition 添加到parserContext的componentContian中,实际上就是绑定切入点与切面之间的关系。

在解析aspect或者config元素后,执行了parserContext.popAndRegisterContainingComponent方法,这个方法主要是触发一个事件监听,告诉监听器,那个切面已经解析完成了。但是在代码中并没有使用代码设置那个事件监听器,所以并没有什么问,具体的不是怎么清楚,可能是分析的还不够仔细吧
总结一下:
1、通过AOP的namespaceUri获取对应的NameSpaceHandler。
2、通过在Handler初始化时生成的Parser去解析对应的标签。
3、使用ConfigBeanDefinitionParser解析<aop:config>标签
4、将aspect标签的内容解析封装成AspectComponentDefinition,对应的class为:
5、将<aop:after>等标签封装成RootBeanDefinition,对应的class为:AspectJAfterAdvice,RootBeanDefinition也内部也包含了这个方法执行的切入点。
6、将<aop:pointcut>解析为一个以以id为name的AspectJExpressionPointcut对象属性信息并将相关切面的信息都注册到BeanFactory中。以便在后期生成动态代理时使用。

以上都是自己分析源码后所得,博客中难免会存在错误的地方,如有发现还请告诉我一下,谢谢。

    原文作者:AOP
    原文地址: https://blog.csdn.net/u011043551/article/details/80462663
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞