美文网首页spring
Spring源码学习(6) —— 基于Schema的aop配置加

Spring源码学习(6) —— 基于Schema的aop配置加

作者: shysheng | 来源:发表于2018-06-18 17:45 被阅读0次

    1.引子

    通过前面几篇的学习,我们已经知道了aop实现的基本原理,但是定义一个切面的过程是比较繁琐的,我们需要自己是实现特定的接口。事实上,Spring提供了更加简单的方式来定义切面,比如说使用@AspectJ注解或者基于Schema配置的方式。今天我们就基于Schema的方式,看一下这种方式下aop的配置时如何被加载的。话不多说,先上一个例子看看如何基于Schema配置一个切面。

    <?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: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
           http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <bean id="adviceMethods" class="com.youzan.shys.advisor.AdviceMethods" />
        <bean id="waiterTarget" class="com.youzan.shys.advisor.NaiveWaiter" />
    
        <aop:config proxy-target-class="true">
            <aop:aspect ref="adviceMethods">
                <aop:before method="preGreeting" pointcut="target(com.youzan.shys.advisor.NaiveWaiter) and execution(* greetTo(..))" />
            </aop:aspect>
        </aop:config>
    </beans>
    

    在这个例子中,我们通过切点表达式,对NaiveWaiter类的greetTo()方法添加了一个前置增强,具体增强为AdviceMethods类的preGreeting()方法。接下来我们主要就来看看这些配置是如何被加载的。

    2.配置文件读取流程

    Location ——> ResourceLoader.getResource
            Resource ——> DocumentLoader.loadDocument
                Document/Element ——> BeanDefinitonParser
                        BeanDefinition
    
    2.1获取命名空间

    熟悉Spring IOC的朋友都知道,Spring从配置文件中读取bean配置并将其转换成BeanDefinition大致上需要经过以上几个步骤,本文重点关注最后一步,即Spring是如何将Element转化为BeanDefinition的,代码直接定位到DefaultBeanDefinitionDocumentReader#parseBeanDefinitions:

    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        // 是否为默认的命名空间,即判断root的命名空间uri是否为空或者等于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;
                    if (delegate.isDefaultNamespace(ele)) {
                        // 默认命名空间,比如<bean id="xxx" />
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        // 非默认命名空间,比如<aop:config />
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }
    

    在解析bean定义的时候,根据命名空间分成了两个分支。显然,由于在我们的例子中不是默认命名空间,因此会进入第二个分支,进去看一看:

    public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
        // 获取命名空间uri,即在xml头部中配置的这一串内容xmlns:aop="http://www.springframework.org/schema/aop"
        String namespaceUri = getNamespaceURI(ele);
        if (namespaceUri == null) {
            return null;
        }
    
        // 这里比较简单,就是根据namespaceUri实例化对应的namespaceHandler,在这里就是AopNamespaceHandler
        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));
    }
    

    在解析自定义元素的时候,首先获取命名空间uri,根据uri获取对应的hander,然后再进行解析。AopNamespaceHandler中针对每一个标签,都有一个具体的解析器来负责解析。

    public void init() {
        // In 2.0 XSD as well as in 2.1 XSD.
        registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
        registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
        registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
    
        // Only in 2.0 XSD: moved to context namespace as of 2.1
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    }
    
    2.2 解析具体标签

    可以看到,AopNamespace中一共只有4个标签,在我们的例子中,首先就是config标签,于是定位到ConfigBeanDefinitionParser#parse:

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        CompositeComponentDefinition compositeDef =
                new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
        parserContext.pushContainingComponent(compositeDef);
    
        // 注册一个名为org.springframework.aop.config.internalAutoProxyCreator的bean,默认实现为AspectJAwareAdvisorAutoProxyCreator
        // 同时根据配置设置一些代理属性,比如proxy-target-class和expose-proxy
        configureAutoProxyCreator(parserContext, element);
    
        // 获取config的子标签,并分别进行解析
        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;
    }
    

    config一共只有3种类型的子标签,分别为advisor、pointcut和aspect。在我们的例子中,首先解析的应该就是aspect,于是进入parseAspect方法,该方法主要做了两件事:

    1. 加载增强节点bean定义(6种增强,包括引介增强)
    2. 获取aspect的所有子标签并进行解析
    private void parseAspect(Element aspectElement, ParserContext parserContext) {
        String aspectId = aspectElement.getAttribute(ID);
        String aspectName = aspectElement.getAttribute(REF);
    
        try {
            // ParseState是一个基于LinkedList的结构,每个LinkedList里存放的是Entry类型的对象
            // 在解析bean的过程中,每次操作开始时将一个Entry入栈,每次操作结束将Entry出栈
            this.parseState.push(new AspectEntry(aspectId, aspectName));
            List<BeanDefinition> beanDefinitions = new ArrayList<>();
            List<BeanReference> beanReferences = new ArrayList<>();
    
            // 如果配置了引介增强,获取其bean定义
            List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);
            for (int i = METHOD_INDEX; i < declareParents.size(); i++) {
                Element declareParentsElement = declareParents.get(i);
                beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));
            }
    
            // 获取aspect的所有子标签并进行解析
            NodeList nodeList = aspectElement.getChildNodes();
            boolean adviceFoundAlready = false;
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                // 如果是增强节点,将aspect节点对应的bean引用加入beanReferences列表
                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 = createAspectComponentDefinition(
                    aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
            parserContext.pushContainingComponent(aspectComponentDefinition);
    
            // aspect节点下可以配置pointcut属性,这里获取所有的切点并进行解析
            List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
            for (Element pointcutElement : pointcuts) {
                parsePointcut(pointcutElement, parserContext);
            }
    
            parserContext.popAndRegisterContainingComponent();
        }
        finally {
            this.parseState.pop();
        }
    }
    
    2.3 advice标签解析

    下面分别看下这里涉及到的几个主要方法,也就是isAdviceNode、parseAdvice和parsePointcut。
    isAdviceNode方法很简单,就是用来判断该节点是否是5中增强节点之一:

    private boolean isAdviceNode(Node aNode, ParserContext parserContext) {
        if (!(aNode instanceof Element)) {
            return false;
        }
        else {
            String name = parserContext.getDelegate().getLocalName(aNode);
            return (BEFORE.equals(name) || AFTER.equals(name) || AFTER_RETURNING_ELEMENT.equals(name) ||
                    AFTER_THROWING_ELEMENT.equals(name) || AROUND.equals(name));
        }
    }
    

    parseAdvice方法逻辑其实也很简单:先根据增强类型生成bean定义,然后将增强封装成切面,最后将切面注册到beanFactory中。

    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);
            methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));
            methodDefinition.setSynthetic(true);
    
            // create instance factory definition
            RootBeanDefinition aspectFactoryDef =
                    new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
            aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);
            aspectFactoryDef.setSynthetic(true);
    
            // 根据增强类型生成bean定义
            AbstractBeanDefinition adviceDef = createAdviceDefinition(
                    adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,
                    beanDefinitions, beanReferences);
    
            // 将advice封装成advisor,同时设置order属性
            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));
            }
    
            // 将advisor注册到DefaultListableBeanFactory中
            parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);
    
            return advisorDefinition;
        }
        finally {
            this.parseState.pop();
        }
    }
    
    2.3.1 生成增强的bean定义

    根据增强类型生成bean定义主要包含两部分内容:
    1.根据增强类型如<aop:before />选择对应的增强类对象,这部分在getAdviceClass中完成;
    2.解析<aop:before />标签中的pointcut属性,这部分在parsePointcutProperty中完成。

    private AbstractBeanDefinition createAdviceDefinition(
            Element adviceElement, ParserContext parserContext, String aspectName, int order,
            RootBeanDefinition methodDef, RootBeanDefinition aspectFactoryDef,
            List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {
    
        // 根据增强类型生成bean定义
        RootBeanDefinition adviceDefinition = new RootBeanDefinition(getAdviceClass(adviceElement, parserContext));
        adviceDefinition.setSource(parserContext.extractSource(adviceElement));
    
        ......中间是一堆属性设置,代码省略......
    
        // 解析pointcut属性
        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;
    }
    

    getAdviceClass代码如下,一共5种增强类型,与前面isAdviceNode方法一致

    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;
        }
        else {
            throw new IllegalArgumentException("Unknown advice kind [" + elementName + "].");
        }
    }
    

    parsePointcutProperty方法如下,从这里可以看出,在aspect标签中,必须且只能设置pointcut、pointcut-ref中的一个属性。

    private Object parsePointcutProperty(Element element, ParserContext parserContext) {
        // 不能同时设置pointcut属性和pointcut-ref属性
        if (element.hasAttribute(POINTCUT) && element.hasAttribute(POINTCUT_REF)) {
            parserContext.getReaderContext().error(
                    "Cannot define both 'pointcut' and 'pointcut-ref' on <advisor> tag.",
                    element, this.parseState.snapshot());
            return null;
        }
        else if (element.hasAttribute(POINTCUT)) {
            // Create a pointcut for the anonymous pc and register it.
            String expression = element.getAttribute(POINTCUT);
            AbstractBeanDefinition pointcutDefinition = createPointcutDefinition(expression);
            pointcutDefinition.setSource(parserContext.extractSource(element));
            return pointcutDefinition;
        }
        else if (element.hasAttribute(POINTCUT_REF)) {
            String pointcutRef = element.getAttribute(POINTCUT_REF);
            if (!StringUtils.hasText(pointcutRef)) {
                parserContext.getReaderContext().error(
                        "'pointcut-ref' attribute contains empty value.", element, this.parseState.snapshot());
                return null;
            }
            return pointcutRef;
        }
        // pointcut属性和pointcut-ref属性必须设置其中一个
        else {
            parserContext.getReaderContext().error(
                    "Must define one of 'pointcut' or 'pointcut-ref' on <advisor> tag.",
                    element, this.parseState.snapshot());
            return null;
        }
    }
    
    2.3.2 将增强bean定义封装成切面bean定义

    这一步比较简单,只是把adviceDef用AspectJPointcutAdvisor类型重新包装了一下,同时设置了order属性

    2.3.3 将切面bean注册到BeanFactory中

    这就是parseAdvice()最后一步的工作

    public String registerWithGeneratedName(BeanDefinition beanDefinition) {
        // 获取bean注册的名字
        String generatedName = generateBeanName(beanDefinition);
        // 注册bean定义
        getRegistry().registerBeanDefinition(generatedName, beanDefinition);
        return generatedName;
    }
    

    获取bean名称时,如果有重复的bean名称存在,默认会在原始bean名称后面通过增加“#+数字”的方式来生成新的bean名称以示区分。具体可参考我的另外一篇文章Spring配置文件id/name重复定义问题

        public static String generateBeanName(
                BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean)
                throws BeanDefinitionStoreException {
    
            ......
    
            String id = generatedBeanName;
            if (isInnerBean) {
                // Inner bean: generate identity hashcode suffix.
                id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
            }
            else {
                // Top-level bean: use plain class name.
                // Increase counter until the id is unique.
                int counter = -1;
                while (counter == -1 || registry.containsBeanDefinition(id)) {
                    counter++;
                    id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter;
                }
            }
            return id;
        }
    

    至此,parseAdvice分析完成,另外两个标签的解析parseAdvisor和parsePointCut思路类似,大家可以自行分析😆

    相关文章

      网友评论

        本文标题:Spring源码学习(6) —— 基于Schema的aop配置加

        本文链接:https://www.haomeiwen.com/subject/aneneftx.html