spring源码阅读(三) Bean加载之自定义标签加载

 

紧接着上一篇关于spring默认标签加载,这一篇来看下自定义标签的加载

继续从 DefaultBeanDefinitionDocumentReader来看

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        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)) {
                        this.parseDefaultElement(ele, delegate); // 默认标签解析
                    } else {
                        delegate.parseCustomElement(ele); // 自定义标签解析
                    }
                }
            }
        } else {
            delegate.parseCustomElement(root);  // 自定义标签解析
        }

    }

写在前边的东西,最近结合着《架构整洁之道》和《spring源码深度解析》这两本书一块儿看着,架构整洁之道里描述的一些面向对象的开发原则,接口隔离/单一职责/开闭幕式/依赖反转/里式替换

这些原则,在spring的源码里可谓是用的淋漓尽致,尤其单一职责/接口隔离,这两个翻看源码的时候尤其有体会,之前自己在项目开发中,其实根本没有在一这些事情,只是按照业务划分进行接口的拆分,并不在意是否是单一职责/是否接口隔离这些事情,其实单一职责能让我们更好的去拓展和维护我们的代码。包括接口隔离其实都是这样的目的。能够符合这些规则,我们的代码就能更大限度的符合高可维护性/低耦合性这些要求,也就能实现最大限度的优化开发效率这件事情。

好了,扯了些题外话,我们继续自定义标签的解析工作:

public BeanDefinition parseCustomElement(Element ele) {
        return this.parseCustomElement(ele, (BeanDefinition)null);
    }
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        String namespaceUri = this.getNamespaceURI(ele);
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        } else {
            return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
        }
    }

 看到这里有点儿蒙圈,如果没有用过自定义标签的话,会有些蒙,那我们不妨在第一篇的例子上我们搞一个自定义标签来试试。

 2019.07.06 14:29分 于图书馆继续(在家带了三天孩子,今天继续,上午继续看《架构整洁之道》不过有点儿瞌睡,中间看了一个小时的曾国藩家书,曾国藩说敬字/勤字,个个方面来说都要早起,而且早起更是曾氏一门,延续了好几代人的好习惯,并要求子女家人也要这样,是啊,想想,业精于勤荒于嬉,不说的都是这个意思吗?)早上起的太晚,习惯了现在连孩子都是这样了,这样下去下一代也还将延续我的毛病,如果现在不痛下决心就很难改变了。

好了,穿插下想说的话,现在继续。刚才翻了之前的两篇博客,写的还是不够好,自己看起来都有点儿晕,不过很快串联起来就好了,这两篇默认标签和自定义标签包括第一篇的xml文件到Doc的解析,这些相关的内容无非就是Bean的解析过程,认准了这个核心,带着这个核心去看就不难明白这其中的流程了。

前文提到,我们自己构建一个例子来看下自定义标签到底是如何使用的。这里就来展示下之前构建的例子。

创建一个自定义标签的步骤如下:

1:创建一个需要扩展的组件

2:定义一个XSD文件描述组件内容

3:创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件重的定义和组建定义

4:创建一个Handle文件,扩展自NamespaceHandlerSupport目的是将组件注册到Spring容器

5:编写spring.handlers和spring.schemas文件

OK,根据这个步骤我们来操作:

1:创建组件

public class User {

    private String userName;
    private String email;
    // ... 省略get set方法
}

2:定义XSD文件

创建了spring-test.xsd文件在META-INF下:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<xsd:schema xmlns="http://www.zaojiaoshu.com/schema/user"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://www.zaojiaoshu.com/schema/user"
            elementFormDefault="qualified"
                >

    <xsd:element name="user">
        <xsd:complexType>
            <xsd:attribute name="id" type="xsd:string"/>
            <xsd:attribute name="userName" type="xsd:string"/>
            <xsd:attribute name="email" type="xsd:string"/>
        </xsd:complexType>
    </xsd:element>

</xsd:schema>

目录

3:创建BeanDefinition接口实例,来解析XSD文件

package com.zaojiaoshu.learn.selfTag;

import com.zaojiaoshu.learn.entity.User;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

    protected Class<?> getBeanClass(Element element) {
        return User.class;
    }

    protected void doParse(Element element, BeanDefinitionBuilder builder) {


        String userName = element.getAttribute("userName");
        String email = element.getAttribute("email");

        if(StringUtils.hasText(userName)){
            builder.addPropertyValue("userName", userName);
        }


        if(StringUtils.hasText(email)){
            builder.addPropertyValue("email", email);
        }


    }

}

4:创建Handle文件,扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器

package com.zaojiaoshu.learn.selfTag;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class MyNameSpaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
    }
}

5:创建spring.handlers和spring.schemas文件

spring.handlers

http\://www.zaojiaoshu.com/schema/user=com.zaojiaoshu.learn.selfTag.MyNameSpaceHandler

 spring.schemas

http\://www.zaojiaoshu.com/schema/user.xsd=META-INF/spring-test.xsd

至此,一个自定义标签需要的工作,就都完成了。我们来做个测试在我们项目的ioc.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:myname="http://www.zaojiaoshu.com/schema/user"
       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/context/spring-context-3.0.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.zaojiaoshu.com/schema/user http://www.zaojiaoshu.com/schema/user.xsd">

    <myname:user id="testSelfBean" userName="aaa" email="bbb"/>

</beans>

测试代码:

public class ServerMain {

    public static void main(String args[]){
        BeanFactory context = new XmlBeanFactory(new ClassPathResource("ioc.xml"));

        User user = (User) context.getBean("testSelfBean");
        System.out.println(user.getUserName() + "" + user.getEmail() + "米");
    }

执行结果:

aaabbb米

好了,这样我们的一个自定义标签的例子就完成了,那么我们就带着问题来翻看自定义标签的解析源码吧。首先第一个问题,关于为什么META-INF/spring.handlers和META-INF/spring.schemas这两个文件,为什么会被加载到?

对,这是我本能的第一个好奇,why?你告诉我在这里写的,但是为什么?

上文的自定义解析的代码如下:

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        String namespaceUri = this.getNamespaceURI(ele);
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        } else {
            return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
        }
    }

我们来看这一句,看上去就是根据Namespace找到对应的Resolver来进行resolve。其实就是这一句起作用。

rederContext.getNamespaceHandlerResolver()方法,返回的实例对象是:DefaultNamespaceHandlerResolver,这个在构建BeanDefinitionDocuemntReader的时候可以看到。

public XmlReaderContext createReaderContext(Resource resource) {
        return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor, this, this.getNamespaceHandlerResolver());
    }

    public NamespaceHandlerResolver getNamespaceHandlerResolver() {
        if (this.namespaceHandlerResolver == null) {
            this.namespaceHandlerResolver = this.createDefaultNamespaceHandlerResolver();
        }

        return this.namespaceHandlerResolver;
    }

    protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
        return new DefaultNamespaceHandlerResolver(this.getResourceLoader().getClassLoader());
    }

知道了是DefaultNamespaceHandlerResolver对象,那么我们来看它的resolve方法

public NamespaceHandler resolve(String namespaceUri) {
        Map<String, Object> handlerMappings = this.getHandlerMappings();
        Object handlerOrClassName = handlerMappings.get(namespaceUri);
        if (handlerOrClassName == null) {
            return null;
        } else if (handlerOrClassName instanceof NamespaceHandler) {
            return (NamespaceHandler)handlerOrClassName;
        } else {
            String className = (String)handlerOrClassName;

            try {
                Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
                if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                    throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
                } else {
                    NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass);
                    namespaceHandler.init();
                    handlerMappings.put(namespaceUri, namespaceHandler);
                    return namespaceHandler;
                }
            } catch (ClassNotFoundException var7) {
                throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "] not found", var7);
            } catch (LinkageError var8) {
                throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]: problem with handler class file or dependent class", var8);
            }
        }
    }

头两句,一句获取handlerMappings映射关系,第二句根据映射关系拿到对应的处理类。

第一句:

private Map<String, Object> getHandlerMappings() {
        if (this.handlerMappings == null) {
            synchronized(this) {
                if (this.handlerMappings == null) {
                    try {
                        Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
                        if (this.logger.isDebugEnabled()) {
                            this.logger.debug("Loaded NamespaceHandler mappings: " + mappings);
                        }

                        Map<String, Object> handlerMappings = new ConcurrentHashMap(mappings.size());
                        CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
                        this.handlerMappings = handlerMappings;
                    } catch (IOException var5) {
                        throw new IllegalStateException("Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", var5);
                    }
                }
            }
        }

        return this.handlerMappings;
    }

看到,是个加载属性文件的方法,里边的this.handlerMappingLocation我们留意下。这个location其实在上边构造函数里其实已经指定了。我们来看下DefaultNamespasceHandlerResolver的构造函数。

public DefaultNamespaceHandlerResolver() {
        this((ClassLoader)null, "META-INF/spring.handlers");
    }

    public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {
        this(classLoader, "META-INF/spring.handlers");
    }

    public DefaultNamespaceHandlerResolver(ClassLoader classLoader, String handlerMappingsLocation) {
        this.logger = LogFactory.getLog(this.getClass());
        Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null");
        this.classLoader = classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader();
        this.handlerMappingsLocation = handlerMappingsLocation;
    }

两个构造函数都是默认的文件位置,所以这个spring.handlers文件我们一定是已经加载了的。

所以这里的mapping里一定有,我们spring.handlers文件里的定义

http\://www.zaojiaoshu.com/schema/user=com.zaojiaoshu.learn.selfTag.MyNameSpaceHandler

那么,上边的第二句代码user这个element的namespace就是 http://www.zaojiaoshu.com/schema/user ,那么获取的class就是:com.zaojiaoshu.learn.selfTag.MyNameSpaceHandler类。

接下来做的事情就是:加载这个类,实例化这个类,调用这个类的init()方法。我们在上边的代码上能表清晰的看到这些步骤。而init方法是我们在实例代码里MyNameSpaceHanlder类里的唯一一个方法,目的就是注册解析类。

同时,resolve方法返回的是一个NamespaceHandler对象。

我们继续上文parseCustomeElement的代码,调用handler的parse方法:

  public BeanDefinition parse(Element element, ParserContext parserContext) {
        return this.findParserForElement(element, parserContext).parse(element, parserContext);
    }

    private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        String localName = parserContext.getDelegate().getLocalName(element);
        BeanDefinitionParser parser = (BeanDefinitionParser)this.parsers.get(localName);
        if (parser == null) {
            parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
        }

        return parser;
    }

第一句两件事情,1:根据element找到对应的parser,然后调用parser的parse方法

查看findParserForElement 的代码,可以看到,parsers里根据localName获取的。这里这个localName对于我们的标签来说肯定就是:user了。

我们还记得MyNamespaceHandler的init代码:

registerBeanDefinitionParser("user", new UserBeanDefinitionParser());

调用的无非就是:NamespaceHandlerSupport 里的:

  protected final void registerBeanDefinitionDecorator(String elementName, BeanDefinitionDecorator dec) {
        this.decorators.put(elementName, dec);
    }

那就可以看到了,我们自定义的Handler里做的就是注册user这个key到NamespaceHandlerSupport 的parsers列表里,然后在解析的时候,这里获取的就是这个parser了,相应的调用的就是我们自定义的那个parser的parse方法了,返回了BeanDefinition对象。

继续解析的parse方法:

public final BeanDefinition parse(Element element, ParserContext parserContext) {
        AbstractBeanDefinition definition = this.parseInternal(element, parserContext);
        if (definition != null && !parserContext.isNested()) {
            try {
                String id = this.resolveId(element, definition, parserContext);
                if (!StringUtils.hasText(id)) {
                    parserContext.getReaderContext().error("Id is required for element '" + parserContext.getDelegate().getLocalName(element) + "' when used as a top-level tag", element);
                }

                String[] aliases = null;
                if (this.shouldParseNameAsAliases()) {
                    String name = element.getAttribute("name");
                    if (StringUtils.hasLength(name)) {
                        aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
                    }
                }

                BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
                this.registerBeanDefinition(holder, parserContext.getRegistry());
                if (this.shouldFireEvents()) {
                    BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
                    this.postProcessComponentDefinition(componentDefinition);
                    parserContext.registerComponent(componentDefinition);
                }
            } catch (BeanDefinitionStoreException var8) {
                parserContext.getReaderContext().error(var8.getMessage(), element);
                return null;
            }
        }

        return definition;
    }

这个parser当然就是我们之前创建的自定义的parser对象,我们继承了

AbstractSimpleBeanDefinitionParser

这个类。我们来看下类图:

所以第一句partnerInternal调用的是:AbstractSingleBeanDefinitionParser也即子类的实现

  protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
        String parentName = this.getParentName(element);
        if (parentName != null) {
            builder.getRawBeanDefinition().setParentName(parentName);
        }

        Class<?> beanClass = this.getBeanClass(element);
        if (beanClass != null) {
            builder.getRawBeanDefinition().setBeanClass(beanClass);
        } else {
            String beanClassName = this.getBeanClassName(element);
            if (beanClassName != null) {
                builder.getRawBeanDefinition().setBeanClassName(beanClassName);
            }
        }

        builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
        if (parserContext.isNested()) {
            builder.setScope(parserContext.getContainingBeanDefinition().getScope());
        }

        if (parserContext.isDefaultLazyInit()) {
            builder.setLazyInit(true);
        }

        this.doParse(element, parserContext, builder);
        return builder.getBeanDefinition();
    }

    protected String getParentName(Element element) {
        return null;
    }

    protected Class<?> getBeanClass(Element element) {
        return null;
    }

    protected String getBeanClassName(Element element) {
        return null;
    }

    protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
        this.doParse(element, builder);
    }

    protected void doParse(Element element, BeanDefinitionBuilder builder) {
    }

这里一看就是明显的模版方法,惯用的套路,我们实现的就是最下边那个方法doParse(Element element, BeanDefinitionBuilder builder) ,层层往上,我们只是实现了一个自定义的parser。

还记得我们做了什么事情吗?来看下我们的UserBeanDefinition:

protected void doParse(Element element, BeanDefinitionBuilder builder) {


        String userName = element.getAttribute("userName");
        String email = element.getAttribute("email");

        if(StringUtils.hasText(userName)){
            builder.addPropertyValue("userName", userName);
        }


        if(StringUtils.hasText(email)){
            builder.addPropertyValue("email", email);
        }


    }

做的事情无非是把解析到的value值添加到builder的propertyValue属性里。然后返回了BeanDefinition,回到最初的调用链 AbstractBeanDefinitionParser的parse方法里。

接下去做的事情就比较熟悉了,尤其是那句

this.registerBeanDefinition(holder, parserContext.getRegistry());

无非就是跟之前的默认标签的最终操作一样,把我们的解析到的BeanDefinition封装成BeanDefinitionHolder然后,注册到我们的注册器中,最终添加到DefaultListableBeanFactory的那个concurrentHashMap里。

至此,我们的自定义标签的解析就结束了,相对默认标签的解析,这里的工作因为是使用自定义的Handler和parser,复杂程度,就主要在customer的自定义复杂程度上了,本身的解析复杂度,因为有了之前默认标签的解析对比,这里就轻松多了。

经过了这三篇的学习,我们把spring的xml经过

xml --> Doc --> BeanDefinition --> 注册到BeanFactory里,这几个步骤就完成了,我们接下来的工作就进入到最最核心的加载步骤了。期待。。。

2019-07-06 17:19于图书馆

posted on 2019-07-02 17:55  aquariusm  阅读(274)  评论(0编辑  收藏  举报

导航