我是靠谱客的博主 畅快高跟鞋,最近开发中收集的这篇文章主要介绍Spring源码解析之二 ------ 自定义标签的解析和注册(IOC的第一步),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

在《spring源码解析之一 -----  默认标签的解析注册》中,已经介绍了Spring对于默认标签(beans、bean、alias、import)的解析注册。看到这个时候,你也会明白,我们常用的aop标签,并不是spring的默认标签,而是自定义标签。spring对自定义标签,提供了良好的扩展和支持。在上篇文章中,按照源码看bean的解析过程中,在下方的代码里,是自定义标签和Default标签的分开点。

再谈具体实现自定义标签之前,通过下面的代码,我们可以简单推测出一些东西。Node是存放标签的节点元素,在if语句中,通过node  instanceof Element,来判断node节点是不是Element元素,如果是Element类型,name就会解析。所以Element类型,对应着一个标签,比如bean标签就是Element类型。

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


一、自定义标签的实现过程

这里先宏观介绍一下自定义标签的实现过程

1.实现一个POJO类,定义标签对应的相关参数

2.实现一个XSD文件,用来描述POJO类

3.实现一个解析类,用来解析XSD和POJO。Spring为我们提供了抽象类 AbstractSingleBeanDefinitionParser

4.实现一个Handler类,该类被Spring使用,用来注册bean到容器中。Spring为我们提供了抽象类  NamespaceHandlerSupport

5.添加我们自定义的handler,schema和location到spring.handlers  和 spring.schemas

二、User类型的POJO

package com.heitian.ssm.model;

/**
 * Created by weili on 17/6/27.
 */
public class UserInfo {
    String name;
    String id;
    String tel;
    String address;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTel() {
        return tel;
    }

    public void setTel(String tel) {
        this.tel = tel;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

}
三、定义XSD描述POJO

XSD的文件格式规范和入门学习,可以去这个网址 http://www.phpstudy.net/e/schema/ 。

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.weili.com/schema/userinfo"
        xmlns:tns="http://www.weili.com/schema/userinfo"
        elementFormDefault="qualified">


    <element name="userinfo">
        <complexType>
            <attribute name="id" type="string" />
            <attribute name="name" type="string" />
            <attribute name="address" type="string" />
            <attribute name="tel" type="string" />
        </complexType>
    </element>

</schema>


四、继承Bean解析的接口

package com.heitian.ssm.model;

import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

/**
 * Created by weili on 17/6/27.
 */
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser{
    @Override
    protected Class<?> getBeanClass(Element element) {
        return UserInfo.class;
    }

    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {
        String id=element.getAttribute("id");
        String name=element.getAttribute("name");
        String tel=element.getAttribute("tel");
        String address=element.getAttribute("address");
        //将提取到的数据放入beanDefinitionBuilder 中,待完成所有的bean解析后统一放到beanfactory
        if(StringUtils.hasText(id)){
            builder.addPropertyValue("id", id);
        }
        if(StringUtils.hasText(name)){
            builder.addPropertyValue("name", name);
        }
        if(StringUtils.hasText(tel)){
            builder.addPropertyValue("tel", tel);
        }
        if(StringUtils.hasText(address)){
            builder.addPropertyValue("address", address);
        }
    }


}


五、实现Handler类

该类的作用是将解析器和相应的标签名字,关联起来,告诉Spring

package com.heitian.ssm.model;

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

/**
 * Created by weili on 17/6/27.
 */
public class userInfoNamespaceHandler extends NamespaceHandlerSupport {
    public void init() {
        registerBeanDefinitionParser("userinfo", new UserBeanDefinitionParser());
    }
}


六、修改spring的schema和handler

我用的Intellij建立的maven工程,所以工程下没有spring.handlers和spring.shcemas这两个文件。为了让maven能够读取这俩文件,需要在你工程的Resource目录下新建一个META-INF目录,然后再这个目录下新建spring.handlers和spring.schemas。同时需要把你定义的xsd文件也移动到META-INF文件下。

spring.handlers:

http://www.weili.com/schema/userinfo = com.heitian.ssm.model.userInfoNamespaceHandler

spring.schemas:

http://www.weili.com/schema/userinfo/userinfo.xsd = META-INF/userinfo.xsd

spring.handlers文件中增加(告诉spring,这样的URI应该去用哪个handler去调用解析)

spring.schemas文件中增加(告诉spring,这样的URI应该去什么地方找到xsd文件描述)

这个时候你的resource目录下应该和图上的差不多:因为我是在一个SSM Demo上直接写的,所以无关的配置打了马赛克。



七、配置spring-bean.xml

注意第四行的xmlns:myname  需要和定义的标签<myname:userinfo>相一致。

通过下面的xml,我们就配置了一个userinfo的标签,也可以说是我们自己实现的bean。最后就可以在测试类里面读取这个配置文件了。

<?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.weili.com/schema/userinfo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                        http://www.weili.com/schema/userinfo
                        http://www.weili.com/schema/userinfo/userinfo.xsd">
<myname:userinfo id="userinfoId" name="userinfoName" tel="18801289635" address="bupt"></myname:userinfo>
</beans>

八、测试类

最后就是写个测试类,从classpath中加载xml,然后获取我们自己定义的bean。可以看到,我们在xml文件中自己定义了一个<userinfo>标签或者bean,然后通过这个标签的id属性的名字,获取相应的标签元素。

package com.heitian.ssm;

import com.heitian.ssm.model.UserInfo;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by weili on 17/6/25.
 */
public class SpringBean {
    public static void main (String args[]){
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-bean.xml");
        UserInfo usrInfo = (UserInfo) ctx.getBean("userinfoId");
        System.out.print(usrInfo.getName());


    }
}


以上就是自己实现自定义标签的过程,我们通过spring提供的相应接口和类,实现了一个userinfo的标签,这个标签类似于bean标签,可以配置id、name等属性。

这个方法虽然简单,但是确实自定义标签的基本步骤。在阿里巴巴的开源分布式服务框架Dubbo中,dubbo-config-spring中就是dubbo和spring结合的配置文件。里面充分用了这个方法,可能你很清楚的直到一个文件dubbo.xsd。

二、自定义bean的源码解析

自定义bean和spring的默认bean的流程基本是一致的。除了之前提到的,spring的default bean和自定义bean进入的parse方法不同外,基本没啥大区别。本文的源码解析,着重看自定义的标签的shecma和handler是怎么联系和加载的,然后又是怎么通过handler调用了我们自己的解析方法。

还是和之前spring默认bean的加载过程一样,我们一路debug直到下面这个地方:

XmlBeanDefinitionReader 下的doLoadBeanDefinitions

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
        try {
            Document doc = this.doLoadDocument(inputSource, resource);
            return this.registerBeanDefinitions(doc, resource);
        } catch (BeanDefinitionStoreException var4) {
            throw var4;
        } catch (SAXParseException var5) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var5.getLineNumber() + " in XML document from " + resource + " is invalid", var5);
        } catch (SAXException var6) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var6);
        } catch (ParserConfigurationException var7) {
            throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var7);
        } catch (IOException var8) {
            throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var8);
        } catch (Throwable var9) {
            throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var9);
        }
    }
在上一篇,spring默认bean的加载中我们并没有进去看文档DOM树是怎么加载的。在这篇文章里,我们将简单看doc的生成过程,

进入这个方法,一直到如下这个方法。

    public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
        DocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware);
        if(logger.isDebugEnabled()) {
            logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
        }

        DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler);
        return builder.parse(inputSource);
    }
在上面的方法里,获取了一个文档的构建对象builder,这个builder并没有做什么特别的事情,只是构建一个对象。然后对inputSource进行解析,inputSource里面存放着配置文件spring.xml文件的存放位置。j进入parse方法。进去后,我们可以看到这里对xml文档进行了DOM树的解析,然后将xml文件中的标签转成DOM树的Node。如果你想看看,DOM树的解析,那么进入domParser.parse(is)。这里仅仅简单了解一下过程就行。如果对DOM树不了解,可以去随意找一些资料,了解xml和DOM的转换。

    public Document parse(InputSource is) throws SAXException, IOException {
        if (is == null) {
            throw new IllegalArgumentException(
                DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN,
                "jaxp-null-input-source", null));
        }
        if (fSchemaValidator != null) {
            if (fSchemaValidationManager != null) {
                fSchemaValidationManager.reset();
                fUnparsedEntityHandler.reset();
            }
            resetSchemaValidator();
        }
        domParser.parse(is); //在这里对inputSource进行解析
        Document doc = domParser.getDocument(); //将解析的结果传给doc
        domParser.dropDocumentReferences();
        return doc;     //结果返回
    }
经过上面的DOM树的解析,xml中的标签就被转成了doc对象中的节点,我们可以看一下doc中的数据





我们看到,在NodeName里面已经有我们自己定义的标签的名字和相应的属性。

那么doc对象生成了,接下来就该解析bean(myname:userinfo),然后注册。那么接下来就进入解析的方法。

进入之后,获取doc对象的根节点。xml文档的根节点是beans,然后进入真正的解析方法。

    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        this.logger.debug("Loading bean definitions");
        Element root = doc.getDocumentElement();
        this.doRegisterBeanDefinitions(root);
    }


到下面的方法后,就是默认bean和自定义bean的分界点了。在这个方法里,不同的bean,进入不同的解析方法。现在我们都是自定义bean,所以走下面的parseCustomElement(ele)。这个ele是我们自己定义的标签:myname:userinfo

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

    }
进入自定义bean的解析方法后,代码如下图所示。

这个方法首先从ele中获取namespaceUri,这个namespaceUri = “www.weili.com/schema/userinfo”。

然后获取handler。

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

进去获取handler的方法,你可以发现这个handler的获取非常简单。就是从map里面取出来的,key就是namespaceUri,取出来的value就是我们在spring.handlers中说明的 自定义的handler类

com.heitian.ssm.model.userInfoNamespaceHandler
既然获取到了类的权限定名,那么接下来自然想到的就是反射生成对应的类,也就是反射生成namespaceHandler。
这个namespaceHandler就是我们自己定义的userInfoNamespaceHandler。
有的人可能好奇,我们自己定义handler的时候,继承的是NamespaceHandlerSupport,这里的类型是NamespaceHandler。
原因是这样的:NamespaceHandler是一个接口,而

NamespaceHandlerSupport实现了NamespaceHandler接口,而我们自定义的类userInfoNamespaceHandler又继承了NamespaceHandlerSupport.

理清关系后,我们继续看。

接下里namespaceHandler调用了初始化方法init(),也就是我们写的userInfoNamespaceHandler中的init()方法。在初始化方法里,我们做了如下的事情

registerBeanDefinitionParser("userinfo", new UserBeanDefinitionParser());
这个注册告诉spring,userinfo这个bean,应该用什么样的解析器。

到了这里,所有的一切就都串联起来了。

    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();//这个初始化方法,向spring注册我们的bean名和handler的对应关系。
                    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);
            }
        }
    }

接下里,方法返回我们自己定义的handler,然后开始进入下面的方法

return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
 
进入上面的方法,到了下面这个方法里。我们可以看到,程序开始查找 bean标签element 对应的解析器。因为在上面已经向spring注册过,bean名字和解析器的对应关系,
所以在这里找解析器骗人色弱,就是一个从map中取出value的过程。
    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;
    }
找到我们自己定义的解析器后,就开始调用parse方法。进入相应的方法,直到下面。在这个方法里,我们看到了BeanDefinitionBuilder,
如果你还记得,在我们自己的实现的解析器里,有一个builder,我们将解析的属性名字和其对应的value,全部都放到了这个builder里。
如果你忘了,也没关系,进入这个方法的倒数第二行,doparse方法。
    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();
    }

进入之后是如图的调用,

再进去这个方法,然后你就会惊喜的发现,进入我们自己实现的解析器的parse方法了。
解析完毕后,开始一步步返回。
接下里的过程就是将,刚刚解析的beanDefinition注册到容器map的过程。这个和spring默认bean的注册时一致的。
相信如果你看懂了上篇文章,这篇文章也不会很难。
 
到这里,自定义标签的实现和解析原理,就讲完了。
有任何问题,欢迎留言交流!
 
 






























最后

以上就是畅快高跟鞋为你收集整理的Spring源码解析之二 ------ 自定义标签的解析和注册(IOC的第一步)的全部内容,希望文章能够帮你解决Spring源码解析之二 ------ 自定义标签的解析和注册(IOC的第一步)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(37)

评论列表共有 0 条评论

立即
投稿
返回
顶部