Spring(7)-Spring解析xml文件,我们从中获得了什么

xml中,各种元素,按照namespace分得清清白白的,下图表格,表示namespace和element的关系

 

 下班了,回去再看,giao,结果下班回去没看,回去都11点了,大丈夫当一诺千金

现在模仿Spring解析xml的方式来解析xml,解析下面的test-xml-read.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<f:table xmlns:f="http://www.w3school.com.cn/furniture"
         xmlns:t="http://www.w3school.com.cn/t">
    <f:name>African Coffee Table</f:name>
    <f:width>80</f:width>
    <f:length>120</f:length>

    <t:abc></t:abc>
</f:table>

把它们里面元素标签,遍历一遍

public class RunXmlFile {

    public static void main(String[] args) throws IOException, ParserConfigurationException, SAXException {
        URL url = Thread.currentThread().getContextClassLoader()
                .getResource("test-xml-read.xml");
        InputStream inputStream = url.openStream();
        //将流转变为InputSource,在后续xml解析使用
        InputSource inputSource = new InputSource(inputStream);
        DocumentBuilderFactory factory = createDocumentBuilderFactory();
        DocumentBuilder builder = factory.newDocumentBuilder();
        //可选,设置实体解析器,其实就是:你可以自定义去哪里加载xsd/dtd文件
        //这里我是没理解的
        builder.setEntityResolver(new EntityResolver() {
            @Override
            public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
                return null;
            }
        });
        //设置回调处理器,当解析出现错误时,(比如xsd里指定了出现a元素,但是出现了a元素)
        builder.setErrorHandler(null);
        //解析xml文件,获取到Document,代表了整个文件
        Document document = builder.parse(inputSource);
        //获取根元素
        Element root = document.getDocumentElement();

        NodeList nodeList = root.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node instanceof Element){
                Element element = (Element) node;
                System.out.println(element);
            }
        }
    }

    private static DocumentBuilderFactory createDocumentBuilderFactory() {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setValidating(true);
        factory.setNamespaceAware(true);
        return factory;
    }
}

重头戏:自定义EntityResolver

builder.setEntityResolver,上面是默认实现的
果然,跟我在第6篇的Spring学习一样,上面的就是namespaceUri
<f:table xmlns:f="http://www.w3school.com.cn/furniture"
         xmlns:t="http://www.w3school.com.cn/t">
xmlns:namespace-prefix="namespaceURI"

所以,我们这边的namespace前缀是f、t,内容分别是:

http://www.w3school.com.cn/furniture、http://www.w3school.com.cn/t

但是,我们这个xml格式是有要求的,一般能定义什么,都是写死的

那这个约束是在哪里的呢?在namespaceURI对应的dtd、xsd文件中

 

 

 

<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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

//这个是key、value,key就是上面这个url,value就是xsd文件 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

这样,那EntityResovler接口,有点豁然开朗了吧

public interface EntityResolver {

    public abstract InputSource resolveEntity (String publicId,
                                               String systemId)
        throws SAXException, IOException;

}

这个接口,就是我们自定义一个方法,来解析xml外部实体,一般传入参数如下:

 

 

 

 

 

 即,publicId为null,systemId为xsd的uri,这个uri一般是可以通过网络获取的

http://www.springframework.org/schema/context/spring-context.xsd

 

 

但是Spring会自定义自己的EntityResovler,实现类为:org.springframework.beans.factory.xml.ResourceEntityResolver

这个类会在本地查找xsd文件,主要的逻辑就是去查找对应的classpath下的META-INF/spring.schemas

 

 看看spring-beans包下的这个文件

 

 又到饭点了,下面溜溜溜,来了

 

 

在 spring.schemas文件里key-value key为xsd文件名,就是xml上面那些xsd,value为xsd本地的路径,在resources文件下,如上图。

如果不自定义,jdk的dom解析类,就会直接使用https://www.springframework.org/schema/beans/spring-beans.xsd,这个东西

作为URL,建立socket网络连接来获取。而部门环境,比如生产环境,基本是外网隔离的,你是没法下载xsd文件,这时候岂不是没法校验xml文件的语法、格式了吗?

所以,spring要将这个外部的xsd引用,转为在classpath下寻找。

自定义元素解析逻辑

//解析xml文件,获取到Document,代表了整个文件
        Document document = builder.parse(inputSource);
        //获取根元素
        Element root = document.getDocumentElement();

        NodeList nodeList = root.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node instanceof Element){
                Element element = (Element) node;
                System.out.println(element);
            }
        }

骨架代码里,遍历每个元素,在spring里,遍历到每个ele时,要去判断对应的namespace,如果是默认的,交给xxx处理,如果不是默认的,要根据namespace找到对应的

namespaceHandler

util-constant

用法:

<?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:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
https://www.springframework.org/schema/util/spring-util.xsd">


    <util:constant id="chin.age" static-field="java.sql.Connection.TRANSACTION_REPEATABLE_READ"/>


</beans>
public class TestConstant {

public static void main(String[] args) {
Resource resource = new ClassPathResource("springcontext-config.xml");
XmlBeanFactory context = new XmlBeanFactory(resource);

Object bean = context.getBean("chin.age");
BeanDefinition beanDefinition = context.getBeanDefinition("chin.age");
System.out.println(JSON.toJSONString(beanDefinition));
System.out.println("bean:" + bean);
}
}

输出结果:

 输出这个chin.age的BeanDefinition

{"abstract":false,"autowireCandidate":true,"autowireMode":0,"beanClass":"org.springframework.beans.factory.config.FieldRetrievingFactoryBean",
"beanClassName":"org.springframework.beans.factory.config.FieldRetrievingFactoryBean","constructorArgumentValues":{"argumentCount":0,"empty":true
,"genericArgumentValues":[],"indexedArgumentValues":{}},"dependencyCheck":0,"enforceDestroyMethod":true,"enforceInitMethod":true,
"lenientConstructorResolution":true,"methodOverrides":{"empty":true,"overrides":[]},"nonPublicAccessAllowed":true,"primary":false,
"propertyValues":{"converted":false,"empty":false,
"propertyValueList":[{"converted":false,"name":"staticField","optional":false,"originalPropertyValue":{"$ref":"@"},
"value":"java.sql.Connection.TRANSACTION_REPEATABLE_READ"}],"propertyValues":[{"$ref":"$.propertyValues.propertyValueList[0]"}]}
,"prototype":false,"qualifiers":[],"resolvableType":{"array":false,"componentType":{"array":false,"componentType":{"$ref":"@"},
"generics":[],"interfaces":[],"source":{"typeName":"org.springframework.core.ResolvableType$EmptyType@5a8806ef"},"superType":{"$ref":"@"}
,"type":{"$ref":"$.resolvableType.componentType.source"}},"generics":[],"interfaces":
[{"array":false,"componentType":{"$ref":"$.resolvableType.componentType"},"generics":[{"array":false,"componentType":
{"$ref":"$.resolvableType.componentType"},"generics":[],"interfaces":[],"rawClass":"java.lang.Object","source":"java.lang.Object","superType"
:{"$ref":"$.resolvableType.componentType"},"type":"java.lang.Object"}],"interfaces":[],"rawClass":"org.springframework.beans.factory.FactoryBean","source":{"actualTypeArguments":["java.lang.Object"],"rawType":"org.springframework.beans.factory.FactoryBean","typeName":"org.springframework.beans.factory.FactoryBean<java.lang.Object>"},
"superType":{"$ref":"$.resolvableType.componentType"},"type":{"$ref":"$.resolvableType.interfaces[0].source"}}
,{"array":false,"componentType":{"$ref":"$.resolvableType.componentType"},"generics":[],"interfaces":[{"array":false,"componentType"
:{"$ref":"$.resolvableType.componentType"},"generics":[],"interfaces":[],"rawClass":"org.springframework.beans.factory.Aware",
"source":"org.springframework.beans.factory.Aware","superType":{"$ref":"$.resolvableType.componentType"},"type":
"org.springframework.beans.factory.Aware"}],"rawClass":"org.springframework.beans.factory.BeanNameAware",
"source":"org.springframework.beans.factory.BeanNameAware","superType":{"$ref":"$.resolvableType.componentType"},
"type":"org.springframework.beans.factory.BeanNameAware"},{"array":false,"componentType":{"$ref":"$.resolvableType.componentType"},
"generics":[],"interfaces":[{"array":false,"componentType":{"$ref":"$.resolvableType.componentType"},"generics":[],"interfaces":[],
"rawClass":"org.springframework.beans.factory.Aware","source":"org.springframework.beans.factory.Aware","superType":
{"$ref":"$.resolvableType.componentType"},"type":"org.springframework.beans.factory.Aware"}],
"rawClass":"org.springframework.beans.factory.BeanClassLoaderAware","source":"org.springframework.beans.factory.BeanClassLoaderAware"
,"superType":{"$ref":"$.resolvableType.componentType"},"type":"org.springframework.beans.factory.BeanClassLoaderAware"},
{"array":false,"componentType":{"$ref":"$.resolvableType.componentType"},"generics":[],"interfaces":[],"rawClass":
"org.springframework.beans.factory.InitializingBean","source":"org.springframework.beans.factory.InitializingBean",
"superType":{"$ref":"$.resolvableType.componentType"},"type":"org.springframework.beans.factory.InitializingBean"}]
,"rawClass":"org.springframework.beans.factory.config.FieldRetrievingFactoryBean","source":
"org.springframework.beans.factory.config.FieldRetrievingFactoryBean","superType":{"array":false,"componentType":
{"$ref":"$.resolvableType.componentType"},"generics":[],"interfaces":[],"rawClass":"java.lang.Object","source":
"java.lang.Object","superType":{"$ref":"$.resolvableType.componentType"},"type":"java.lang.Object"},
"type":"org.springframework.beans.factory.config.FieldRetrievingFactoryBean"},"resolvedAutowireMode":0,"role":0,"scope":"","singleton":true,
"synthetic":false}

 

可以看到这个是个FactoryBean,按理说应该是int类型的bean

因为util:constant被解析成了工厂bean,类型为org.springframework.beans.factory.config.FieldRetrievingFactoryBean,

当我们去getBean的时候,spring发现bean类型是factoryBean,就会去调用这个工厂bean的工厂方法去生产。

具体的解析过程

从之前的骨架代码入口

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        //一般来说,我们的根节点都是beans,这个是默认namespace的
        //这个方法,里面可以看到通过root得到了childern,可以对children进行遍历
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();
            // 遍历xml <beans>下的每个元素
            for (int i = 0; i < nl.getLength(); i++) {
                Node node = nl.item(i);
                if (node instanceof Element) {
                    Element ele = (Element) node;
                    // 判断元素是不是默认命名空间的,比如<bean>是,
                    // <context:component-scan>不是
                    if (delegate.isDefaultNamespace(ele)) {
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        //<context:component-scan>,<aop:xxxx>走这边
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }

 进入org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseCustomElement

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {

        String namespaceUri = getNamespaceURI(ele);
        if (namespaceUri == null) {
            return null;
        }
        //这里通过nameuri找到对应的NamespaceHandler
        //我们这里,会得到:org.springframework.beans.factory.xml.UtilNamespaceHandler,这里resolve()里面初始化了init()方法
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        }
        //org.springframework.beans.factory.xml.UtilNamespaceHandler
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }

NamespaceHandler预览

public class UtilNamespaceHandler extends NamespaceHandlerSupport {

    private static final String SCOPE_ATTRIBUTE = "scope";


    @Override
    public void init() {
        registerBeanDefinitionParser("constant", new ConstantBeanDefinitionParser());
        registerBeanDefinitionParser("property-path", new PropertyPathBeanDefinitionParser());
        registerBeanDefinitionParser("list", new ListBeanDefinitionParser());
        registerBeanDefinitionParser("set", new SetBeanDefinitionParser());
        registerBeanDefinitionParser("map", new MapBeanDefinitionParser());
        registerBeanDefinitionParser("properties", new PropertiesBeanDefinitionParser());
    }


    private static class ConstantBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser {

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

        @Override
        protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) {
            String id = super.resolveId(element, definition, parserContext);
            if (!StringUtils.hasText(id)) {
                id = element.getAttribute("static-field");
            }
            return id;
        }
    }
.......省略

这里很明显,NamespaceHandler定义了,什么元素交给什么类来去处理

接着往下看

handler.parse(ele, new ParserContext(this.readerContext, this, containingBd))

父类NamespaceHandlerSupport

public BeanDefinition parse(Element element, ParserContext parserContext) {
        //这里,调用了本类的另外一个方法获取parser
        BeanDefinitionParser parser = findParserForElement(element, parserContext);
        return (parser != null ? parser.parse(element, parserContext) : null);
    }
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        String localName = parserContext.getDelegate().getLocalName(element);
        BeanDefinitionParser parser = this.parsers.get(localName);
        if (parser == null) {
            parserContext.getReaderContext().fatal(
                    "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
        }
        return parser;
    }

这两个在父类中实现的,整体来说,NamespaceHandlerSupport维护了一个Map用来保存Namespace下,具体的元素及其对应的parser。

/**
     * Stores the {@link BeanDefinitionParser} implementations keyed by the
     * local name of the {@link Element Elements} they handle.
     * 维护了一个map,里面保存了本namespace下的,具体的元素,以及对应的parser
     */
    private final Map<String, BeanDefinitionParser> parsers = new HashMap<>();

那parsers是在什么存元素进去的呢?

NamespaceHandlerSupport#registerBeanDefinitionParser

protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
        this.parsers.put(elementName, parser);
    }

卧槽,这个BeanDefinitionParser看得人吐血

posted @ 2021-12-30 18:23  正能量教官  阅读(224)  评论(0编辑  收藏  举报