spring

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

2018-06-18  本文已影响0人  shysheng

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思路类似,大家可以自行分析😆

上一篇下一篇

猜你喜欢

热点阅读