蓝易云

Spirng 是如何解析第三方 xml 标签的

353次阅读
没有评论

共计 11925 个字符,预计需要花费 30 分钟才能阅读完成。

本文主要了解 Spring 是如何解析第三方 xml 标签的,为什么我们引入了对应的名称空间和指定相应的 xsd 文件就可以使用第三方标签。

xsd 的全称是 XML Schema Definition,它是一种用于定义 XML 文档结构的语言。XSD 是一种基于 XML 的规范,用于描述 XML 文档中元素的结构、数据类型和约束。

这里以 spring-context 为例。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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.xsd">

    <context:property-placeholder location="mysql.properties"/>
</beans>

需要使用 spring-context 的前提必然是引入其 jar 包,然后 xmlns:context 声明名称空间,后面可以使用 context: 前缀来限定属于该命名空间的元素。xsi:schemaLocation 指定了 context 名称空间 xsd 文件位置。

目前就算配置好了这些,那么 spring 是如何开始解析的呢?

那这里就要进入到源码查看,这里步骤较多,指出关键的几步。

首先 ClassPathXmlApplicationContext 先调用自身的 refresh 方法,实际调用的是AbstractApplicationContext 的 refresh 方法,这个方法也就是 spring 容器的初始化的入口方法。

而解析 xml 的方法在 obtainFreshBeanFactory 方法中调用,这个方法实际也就是获取 bean 工厂。

public void refresh() throws BeansException, IllegalStateException {
    synchronized(this.startupShutdownMonitor) {
        this.prepareRefresh();
        ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
        this.prepareBeanFactory(beanFactory);

        try {
            this.postProcessBeanFactory(beanFactory);
            this.invokeBeanFactoryPostProcessors(beanFactory);
            this.registerBeanPostProcessors(beanFactory);
            this.initMessageSource();
            this.initApplicationEventMulticaster();
            this.onRefresh();
            this.registerListeners();
            this.finishBeanFactoryInitialization(beanFactory);
            this.finishRefresh();
        } catch (BeansException var9) {
            if (this.logger.isWarnEnabled()) {
                this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
            }

            this.destroyBeans();
            this.cancelRefresh(var9);
            throw var9;
        } finally {
            this.resetCommonCaches();
        }

    }
}

obtainFreshBeanFactory 方法里面会调用 refreshBeanFactory 方法。

protected final void refreshBeanFactory() throws BeansException {
    if (this.hasBeanFactory()) {
        this.destroyBeans();
        this.closeBeanFactory();
    }

    try {
        DefaultListableBeanFactory beanFactory = this.createBeanFactory();
        beanFactory.setSerializationId(this.getId());
        this.customizeBeanFactory(beanFactory);
        this.loadBeanDefinitions(beanFactory);
        this.beanFactory = beanFactory;
    } catch (IOException var2) {
        throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var2);
    }
}

这个方法里面可以看见 loadBeanDefinitions 方法,之前讲过 spring 会将 xml 里面的标签解析成一个个 BeanDefinitions,那么就是这个方法了。

点进去会发现需要一直往下找 loadBeanDefinitions 方法,直到执行到 XmlBeanDefinitionReader 的 doLoadBeanDefinitions 方法。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
    try {
        Document doc = this.doLoadDocument(inputSource, resource);
        int count = this.registerBeanDefinitions(doc, resource);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Loaded " + count + " bean definitions from " + resource);
        }

        return count;
    } catch (BeanDefinitionStoreException var5) {
        throw var5;
    } catch (SAXParseException var6) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var6.getLineNumber() + " in XML document from " + resource + " is invalid", var6);
    } catch (SAXException var7) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var7);
    } catch (ParserConfigurationException var8) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var8);
    } catch (IOException var9) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var9);
    } catch (Throwable var10) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var10);
    }
    }

这个方法里面会执行 registerBeanDefinitions,同样一直执行往下找相同的方法,直到执行到 DefaultBeanDefinitionDocumentReader 的 doRegisterBeanDefinitions 方法。

protected void doRegisterBeanDefinitions(Element root) {
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
    if (this.delegate.isDefaultNamespace(root)) {
        String profileSpec = root.getAttribute("profile");
        if (StringUtils.hasText(profileSpec)) {
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
            if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
                }

                return;
            }
        }
    }

    this.preProcessXml(root);
    this.parseBeanDefinitions(root, this.delegate);
    this.postProcessXml(root);
    this.delegate = parent;
}

可以看见该方法执行了 parseBeanDefinitions 方法,这个方法就是执行解析的关键了。

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

这里判断是否为默认命名空间,如果是默认名称空间就执行自身的 parseDefaultElement 方法进行解析。

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    if (delegate.nodeNameEquals(ele, "import")) {
        this.importBeanDefinitionResource(ele);
    } else if (delegate.nodeNameEquals(ele, "alias")) {
        this.processAliasRegistration(ele);
    } else if (delegate.nodeNameEquals(ele, "bean")) {
        this.processBeanDefinition(ele, delegate);
    } else if (delegate.nodeNameEquals(ele, "beans")) {
        this.doRegisterBeanDefinitions(ele);
    }
}

可以看见默认的标签就这几种。如果判断非默认的命名空间,那么 spring 自身肯定解析不了,因为它并不了解标签的含义,此时会调用 delegate 的 parseCustomElement 方法,接下来会执行到 BeanDefinitionParserDelegate 的 parseCustomElement 方法。

@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    String namespaceUri = this.getNamespaceURI(ele);
    if (namespaceUri == null) {
        return null;
    } else {
        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));
        }
    }
}

可以看见 namespaceUri 是在 xml 文件定义命名空间时设置的内容。

Spirng 是如何解析第三方 xml 标签的

这里会通过 namespaceUri 去获取 NamespaceHandler,NamespaceHandler 就是自定义的处理器,获取到自定义处理器自然就可以将自定义的标签进行解析了。

这里首先获取到 NamespaceHandlerResolver,然后调用 resolve 方法,最终会进入到 DefaultNamespaceHandlerResolver 的 resolve 方法。

@Nullable
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("Could not find NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", var7);
        } catch (LinkageError var8) {
            throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", var8);
        }
    }
}

可以看见它是通过 map 去获取的,那么这个 handlerMappings 又是哪里来的呢?

可以看见 DefaultNamespaceHandlerResolver 的构造方法如下:

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

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

public DefaultNamespaceHandlerResolver(@Nullable 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;
}

这里是直接传递了一个 handlerMapping 的地址 META-INF/spring.handlers,这个就是在自己 jar 包中定义的 handlerMapping 的相关信息。

Spirng 是如何解析第三方 xml 标签的

同时,这里可以看见 spring.schemas 文件,由 org.springframework.beans.factory.xml.PluggableSchemaResolver 类加载,主要是对 xml 标签的一些约束。

Spirng 是如何解析第三方 xml 标签的

这里可以看见 http://www.springframework.org/schema/context 名称空间对应的 NamespaceHandler 路径是 org.springframework.context.config.ContextNamespaceHandler。

那么又回到刚刚 DefaultNamespaceHandlerResolver 的 resolve 方法,这里获取到 NamespaceHandler 以后,执行了它的 init 方法,以 ContextNamespaceHandler 为例。

public class ContextNamespaceHandler extends NamespaceHandlerSupport {
    public ContextNamespaceHandler() {
    }

    public void init() {
        this.registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
        this.registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
        this.registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
        this.registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
        this.registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
        this.registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
        this.registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
        this.registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
    }
}

可以看见这里都是注入标签的解析器,不同的标签使用不同的标签解析器。

然后又回到 BeanDefinitionParserDelegate 的 parseCustomElement 方法,可以看见返回了handler.parse,点击进入到 NamespaceHandlerSupport 的 parse 方法。

@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
    BeanDefinitionParser parser = this.findParserForElement(element, parserContext);
    return parser != null ? parser.parse(element, parserContext) : null;
}

这个方法会根据标签去寻找对应的解析器,这里以 PropertyPlaceholderBeanDefinitionParser 为例,这是一个实现了 BeanDefinitionParser 接口的类。

提示:PropertyPlaceholderBeanDefinitionParser 是刚刚 ContextNamespaceHandler 的 init 方法注册的类。

class PropertyPlaceholderBeanDefinitionParser extends AbstractPropertyLoadingBeanDefinitionParser {
    private static final String SYSTEM_PROPERTIES_MODE_ATTRIBUTE = "system-properties-mode";
    private static final String SYSTEM_PROPERTIES_MODE_DEFAULT = "ENVIRONMENT";

    PropertyPlaceholderBeanDefinitionParser() {
    }

    protected Class<?> getBeanClass(Element element) {
        return "ENVIRONMENT".equals(element.getAttribute("system-properties-mode")) ? PropertySourcesPlaceholderConfigurer.class : PropertyPlaceholderConfigurer.class;
    }

    protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
        super.doParse(element, parserContext, builder);
        builder.addPropertyValue("ignoreUnresolvablePlaceholders", Boolean.valueOf(element.getAttribute("ignore-unresolvable")));
        String systemPropertiesModeName = element.getAttribute("system-properties-mode");
        if (StringUtils.hasLength(systemPropertiesModeName) && !systemPropertiesModeName.equals("ENVIRONMENT")) {
            builder.addPropertyValue("systemPropertiesModeName", "SYSTEM_PROPERTIES_MODE_" + systemPropertiesModeName);
        }

        if (element.hasAttribute("value-separator")) {
            builder.addPropertyValue("valueSeparator", element.getAttribute("value-separator"));
        }

        if (element.hasAttribute("trim-values")) {
            builder.addPropertyValue("trimValues", element.getAttribute("trim-values"));
        }

        if (element.hasAttribute("null-value")) {
            builder.addPropertyValue("nullValue", element.getAttribute("null-value"));
        }

    }
}

可以看见这里首先调用父类的 doParse 方法,如果有些属性父类不能解析再由自己解析,大致的流程就是这样了。

总结:

  • 将自定义标签的约束与物理约束文件与网络约束名称的约束以键值对形式存储到一个 spring.schemas 文件里,该文件存储在类加载路径的 META-INF 里,Spring 会自动加载到

  • 将自定义命名空间的名称与自定义命名空间的处理器映射关系以键值对形式存在到一个叫 spring.handlers 文件里,该文件存储在类加载路径的 META-INF 里,Spring 会自动加载到

  • 准备好 NamespaceHandler,如果命名空间只有一个标签,那么直接在 parse 方法中进行解析即可,一般解析结果就是注册该标签对应的 BeanDefinition。如果命名空间里有多入标签,那么可以在 init 方法中为每个标签都注册一个 BeanDefinitionParser,在执行 NamespaceHandler 的 parse 方法时在分流给不同的 BeanDefinitionParser 进行解析(重写 doParse 方法即可)

其实一般在 doParse 方法中,无非干两件事,一是向容器里面注入一个 Bean,二是注入一个 BeanPostProcessor,作用于 bean 的生命周期。

提醒:本文发布于459天前,文中所关联的信息可能已发生改变,请知悉!

AD:【腾讯云服务器大降价】2核4G 222元/3年 1核2G 38元/年
正文完
 0
阿蛮君
版权声明:本站原创文章,由 阿蛮君 于2023-09-10发表,共计11925字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)
Copyright © 2022-2024 阿蛮君博客 湘ICP备2023001393号
本网站由 亿信互联 提供云计算服务 | 蓝易云CDN 提供安全防护和加速服务
Powered by Wordpress  Theme by Puock