本文共 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); } SetcurrentResources = 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则返回} 就是很简单的深度搜索,遍历DOM节点树,找到id相同的Element则返回
好的,关于XML的解析大概到这就OK了
现在往回走
我们在refreshBeanFactory()完成了
loadBeanDefinitions(beanFactory)
然后返回到refresh()中,我们读取完Bean 的配置信息后,继续往下执行
BeanPostProcessors 这个是负责于Bean配置信息的加载
好啦,也就讲解到这里~
总结:
1.这一层一层往下了解后,回头看真的是豁然开朗的那种感觉
2.看源码有一段时间了,感觉现在不需要去API文档一层一层地查
直接看类的名字就大概知道实现类应该会在哪,自己还要可靠些
对一些关键词敏感如:Context之间的继承关系,Abstract关系
3.这就是Context的魅力!简简单单一句new 背后的工作量有如此之大
4.一下源码中找不到答案时,还是求助互联网吧
转载地址:http://iliyb.baihongyu.com/