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看得人吐血