版本说明: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.
这个方法很重要,我们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<EncodedResource> 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文件最顶上声明的那些命名空间
<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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
具体可以参考这篇文章的末尾,有简单讲解
2.Element
Element element = document.createElement(name);
(这里其实是关于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;
}
好的,关于XML的解析大概到这就OK了
现在往回走
我们在refreshBeanFactory()完成了
loadBeanDefinitions(beanFactory)
然后返回到refresh()中,我们读取完Bean 的配置信息后,继续往下执行
BeanPostProcessors 这个是负责于Bean配置信息的加载
好啦,也就讲解到这里~
总结:
1.这一层一层往下了解后,回头看真的是豁然开朗的那种感觉
2.看源码有一段时间了,感觉现在不需要去API文档一层一层地查
直接看类的名字就大概知道实现类应该会在哪,自己还要可靠些
对一些关键词敏感如:Context之间的继承关系,Abstract关系
3.这就是Context的魅力!简简单单一句new 背后的工作量有如此之大
4.一下源码中找不到答案时,还是求助互联网吧