博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring XML的解析
阅读量:2208 次
发布时间:2019-05-04

本文共 20640 字,大约阅读时间需要 68 分钟。

版本说明:Spring 5.2.9(文档引用多是5.3.0)

本文侧重于源码的解读

这里以最简单的ClassPathXmlApplicationContext为例

ApplicationContext context =new ClassPathXmlApplicationContext("applicationContext.xml");	Hello hello=context.getBean("helloImpl",Hello.class);

在XML里我们配置好Bean后,Spring会帮我们实例化Bean,也就是我们常说的控制反转,依赖注入

那,Spring是怎么通过XML获取到Bean的配置信息的呢?

首先来看看我们创建ClassPathXmlApplicationContext时发生了什么

public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {
configLocation}, true, null); }public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, null); }public ClassPathXmlApplicationContext(String[] configLocations, @Nullable ApplicationContext parent) throws BeansException {
this(configLocations, true, parent); }public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
this(configLocations, refresh, null); }public ClassPathXmlApplicationContext( String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
super(parent); setConfigLocations(configLocations); if (refresh) {
refresh(); } }

以上是ClassPathXmlApplicationContext的源码

可以看到,在最后一个构造函数中,Spring干了三件事

1.super(parent)

创建Context 其实这里没干什么,主要是往父类一层一层地调用构造函数

2.setConfigLocations(configLocations)

这里就是告诉Context我们配置文件的位置

public void setConfigLocations(@Nullable String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null"); this.configLocations = new String[locations.length]; for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim(); } } else {
this.configLocations = null; } }

关于resolvePath(),其中牵扯的东西有点多,这里不展开了

简单说就是JVM并不知道我们传进去的“applicationContext.xml”在哪,还要对这个路径进行resolve

(可以理解为包装、修饰)

3. r e f r e s h ( ) \color{red}{refresh()} refresh()

这个方法很重要,我们new ClassPathXmlApplicationContext()的大部分工作都是在这

这个方法的实现在AbstractApplicationContext

public abstract class AbstractApplicationContext extends DefaultResourceLoader		implements ConfigurableApplicationContext
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try {
// Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally {
// Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }

根据注释,基本知道它干了啥

获取一个refresh后的BeanFactory

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory(); return getBeanFactory(); }

在AbstractRefreshableApplicationContext中有refreshBeanFactory()的实现

protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans(); closeBeanFactory(); } try {
DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); loadBeanDefinitions(beanFactory); this.beanFactory = beanFactory; } catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }

重点关注loadBeanDefinitions(beanFactory)

实现在AbstractXmlApplicationContext:

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory. XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. beanDefinitionReader.setEnvironment(this.getEnvironment()); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader, // then proceed with actually loading the bean definitions. initBeanDefinitionReader(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader); }

可以看到,这里初始化了一个XmlBeanDefinitionReader

Bean definition reader for XML bean definitions. Delegates the actual XML document reading to an implementation of the BeanDefinitionDocumentReader interface.

没错,这个就是Spring用于解析XML文件的类

接着看loadBeanDefinitions(beanDefinitionReader)

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources(); if (configResources != null) {
reader.loadBeanDefinitions(configResources); } String[] configLocations = getConfigLocations(); if (configLocations != null) {
reader.loadBeanDefinitions(configLocations); } }

loadBeanDefinitions(configResources)

这里的getConfigLocations();就是我们之前setConfigLocations(configLocations) 设置的XML文件的位置

再进一步

在XmlBeanDefinitionReader中将完成实现:

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource)); } public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource); } Set
currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding()); } return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } catch (IOException ex) {
throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally {
currentResources.remove(encodedResource); if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove(); } } }public int loadBeanDefinitions(InputSource inputSource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(inputSource, "resource loaded through SAX InputSource"); } public int loadBeanDefinitions(InputSource inputSource, @Nullable String resourceDescription) throws BeanDefinitionStoreException {
return doLoadBeanDefinitions(inputSource, new DescriptiveResource(resourceDescription)); } protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource); int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } catch (BeanDefinitionStoreException ex) {
throw ex; } catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); } catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }

我这里简单概括一下

根据文件位置,获得一个Inputstream,然后把它包装成InputSource

然后获取DOM

Document doc = doLoadDocument(inputSource, resource);

这个DOM,没错,可以联想HTML的DOM(Document Object Model)

把XML中的各个标签元素,加载到DOM节点树上

再看具体的代码实现

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }

然后到DefaultDocumentLoader

public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,			ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]"); } DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); return builder.parse(inputSource); }

这里最后面的parse(inputSource)就是解析XML文件了

其实现在org\apache\harmony\xml\parsers\DocumentBuilderImpl.java

public Document parse(InputSource source) throws SAXException, IOException {
if (source == null) {
throw new IllegalArgumentException("source == null"); } String namespaceURI = null; String qualifiedName = null; DocumentType doctype = null; String inputEncoding = source.getEncoding(); String systemId = source.getSystemId(); DocumentImpl document = new DocumentImpl( dom, namespaceURI, qualifiedName, doctype, inputEncoding); document.setDocumentURI(systemId); KXmlParser parser = new KXmlParser(); try {
parser.keepNamespaceAttributes(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, namespaceAware); if (source.getByteStream() != null) {
parser.setInput(source.getByteStream(), inputEncoding); } else if (source.getCharacterStream() != null) {
parser.setInput(source.getCharacterStream()); } else if (systemId != null) {
URL url = new URL(systemId); URLConnection urlConnection = url.openConnection(); urlConnection.connect(); // TODO: if null, extract the inputEncoding from the Content-Type header? parser.setInput(urlConnection.getInputStream(), inputEncoding); } else {
throw new SAXParseException("InputSource needs a stream, reader or URI", null); } if (parser.nextToken() == XmlPullParser.END_DOCUMENT) {
throw new SAXParseException("Unexpected end of document", null); } parse(parser, document, document, XmlPullParser.END_DOCUMENT); parser.require(XmlPullParser.END_DOCUMENT, null, null); } catch (XmlPullParserException ex) {
Throwable detail = ex.getDetail(); if (detail instanceof IOException) {
throw (IOException) detail; } if (detail instanceof RuntimeException) {
throw (RuntimeException) detail; } LocatorImpl locator = new LocatorImpl(); locator.setPublicId(source.getPublicId()); locator.setSystemId(systemId); locator.setLineNumber(ex.getLineNumber()); locator.setColumnNumber(ex.getColumnNumber()); SAXParseException newEx = new SAXParseException(ex.getMessage(), locator); if (errorHandler != null) {
errorHandler.error(newEx); } throw newEx; } finally {
IoUtils.closeQuietly(parser); } return document; } private void parse(KXmlParser parser, DocumentImpl document, Node node, int endToken) throws XmlPullParserException, IOException {
int token = parser.getEventType(); while (token != endToken && token != XmlPullParser.END_DOCUMENT) {
if (token == XmlPullParser.PROCESSING_INSTRUCTION) {
String text = parser.getText(); int dot = text.indexOf(' '); String target = (dot != -1 ? text.substring(0, dot) : text); String data = (dot != -1 ? text.substring(dot + 1) : ""); node.appendChild(document.createProcessingInstruction(target, data)); } else if (token == XmlPullParser.DOCDECL) {
String name = parser.getRootElementName(); String publicId = parser.getPublicId(); String systemId = parser.getSystemId(); document.appendChild(new DocumentTypeImpl(document, name, publicId, systemId)); } else if (token == XmlPullParser.COMMENT) {
if (!ignoreComments) {
node.appendChild(document.createComment(parser.getText())); } } else if (token == XmlPullParser.IGNORABLE_WHITESPACE) {
if (!ignoreElementContentWhitespace && document != node) {
appendText(document, node, token, parser.getText()); } } else if (token == XmlPullParser.TEXT || token == XmlPullParser.CDSECT) {
appendText(document, node, token, parser.getText()); } else if (token == XmlPullParser.ENTITY_REF) {
String entity = parser.getName(); if (entityResolver != null) {
// TODO Implement this... } String resolved = resolvePredefinedOrCharacterEntity(entity); if (resolved != null) {
appendText(document, node, token, resolved); } else {
node.appendChild(document.createEntityReference(entity)); } } else if (token == XmlPullParser.START_TAG) {
if (namespaceAware) {
// Collect info for element node String namespace = parser.getNamespace(); String name = parser.getName(); String prefix = parser.getPrefix(); if ("".equals(namespace)) {
namespace = null; } // Create element node and wire it correctly Element element = document.createElementNS(namespace, name); element.setPrefix(prefix); node.appendChild(element); for (int i = 0; i < parser.getAttributeCount(); i++) {
// Collect info for a single attribute node String attrNamespace = parser.getAttributeNamespace(i); String attrPrefix = parser.getAttributePrefix(i); String attrName = parser.getAttributeName(i); String attrValue = parser.getAttributeValue(i); if ("".equals(attrNamespace)) {
attrNamespace = null; } // Create attribute node and wire it correctly Attr attr = document.createAttributeNS(attrNamespace, attrName); attr.setPrefix(attrPrefix); attr.setValue(attrValue); element.setAttributeNodeNS(attr); } // Recursive descent token = parser.nextToken(); parse(parser, document, element, XmlPullParser.END_TAG); // Expect the element's end tag here parser.require(XmlPullParser.END_TAG, namespace, name); } else {
// Collect info for element node String name = parser.getName(); // Create element node and wire it correctly Element element = document.createElement(name); node.appendChild(element); for (int i = 0; i < parser.getAttributeCount(); i++) {
// Collect info for a single attribute node String attrName = parser.getAttributeName(i); String attrValue = parser.getAttributeValue(i); // Create attribute node and wire it correctly Attr attr = document.createAttribute(attrName); attr.setValue(attrValue); element.setAttributeNode(attr); } // Recursive descent token = parser.nextToken(); parse(parser, document, element, XmlPullParser.END_TAG); // Expect the element's end tag here parser.require(XmlPullParser.END_TAG, "", name); } } token = parser.nextToken(); } }

这里内容很多,我挑一些代码出来,大家就能理解了

1.命名空间

String namespace = parser.getNamespace();

即我们XML文件最顶上声明的那些命名空间

具体可以参考的末尾,有简单讲解

2.Element

Element element = document.createElement(name);

新 建 E l e m e n t 节 点 即 标 签 对 象 \color{blue}{新建Element 节点即标签对象 } Element 例:<Bean></Bean>

(这里其实是关于DOM的知识,篇幅也不小,就不展开了,大家自行查资料)

3.Attribute

Attr attr = document.createAttribute(attrName);attr.setValue(attrValue);element.setAttributeNode(attr);

这里是设置attr 即标签的属性 例:<Bean id=“User”> </Bean>

setValue()即是设置attr的值

4.Node节点

node.appendChild(element);

这个即是把创建的Element 加到节点树上

而关于上面这几个函数的具体实现则是在DocumentImpl

public ElementImpl createElement(String tagName) {
return new ElementImpl(this, tagName); }

ElementImpl

ElementImpl(DocumentImpl document, String namespaceURI, String qualifiedName) {
super(document); setNameNS(this, namespaceURI, qualifiedName); } ElementImpl(DocumentImpl document, String name) {
super(document); setName(this, name); }

很有意思的,我们的getElementById()的实现也在这里

Element getElementById(String name) {
for (Attr attr : attributes) {
if (attr.isId() && name.equals(attr.getValue())) {
return this; } } if (name.equals(getAttribute("id"))) {
return this; } for (NodeImpl node : children) {
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = ((ElementImpl) node).getElementById(name); if (element != null) {
return element; } } } return null; }

就 是 很 简 单 的 深 度 搜 索 , 遍 历 D O M 节 点 树 , 找 到 i d 相 同 的 E l e m e n t 则 返 回 \color{blue}{就是很简单的深度搜索,遍历DOM节点树,找到id相同的Element则返回} DOMidElement

好的,关于XML的解析大概到这就OK了


现在往回走

我们在refreshBeanFactory()完成了

loadBeanDefinitions(beanFactory)

然后返回到refresh()中,我们读取完Bean 的配置信息后,继续往下执行

BeanPostProcessors 这个是负责于Bean配置信息的加载

好啦,也就讲解到这里~


总结:

1.这一层一层往下了解后,回头看真的是豁然开朗的那种感觉

2.看源码有一段时间了,感觉现在不需要去API文档一层一层地查

直接看类的名字就大概知道实现类应该会在哪,自己还要可靠些

对一些关键词敏感如:Context之间的继承关系,Abstract关系

3.这就是Context的魅力!简简单单一句new 背后的工作量有如此之大

4.一下源码中找不到答案时,还是求助互联网吧

转载地址:http://iliyb.baihongyu.com/

你可能感兴趣的文章
(PAT 1040) Longest Symmetric String (DP-最长回文子串)
查看>>
(PAT 1145) Hashing - Average Search Time (哈希表冲突处理)
查看>>
(1129) Recommendation System 排序
查看>>
PAT1090 Highest Price in Supply Chain 树DFS
查看>>
(PAT 1096) Consecutive Factors (质因子分解)
查看>>
(PAT 1019) General Palindromic Number (进制转换)
查看>>
(PAT 1073) Scientific Notation (字符串模拟题)
查看>>
(PAT 1080) Graduate Admission (排序)
查看>>
Play on Words UVA - 10129 (欧拉路径)
查看>>
mininet+floodlight搭建sdn环境并创建简答topo
查看>>
【linux】nohup和&的作用
查看>>
Set、WeakSet、Map以及WeakMap结构基本知识点
查看>>
【NLP学习笔记】(一)Gensim基本使用方法
查看>>
【NLP学习笔记】(二)gensim使用之Topics and Transformations
查看>>
【深度学习】LSTM的架构及公式
查看>>
【python】re模块常用方法
查看>>
剑指offer 19.二叉树的镜像
查看>>
剑指offer 20.顺时针打印矩阵
查看>>
剑指offer 21.包含min函数的栈
查看>>
剑指offer 23.从上往下打印二叉树
查看>>