XmlWebApplicationContext的loadBeanDefinitionsjava
此時到了真正的加載xml到spring過程spring
大概分爲加載xml資源,而後xmlreader去解析相應的配置生成BeanDefinition,註冊到spring工程內存中express
一、xml資源加載apache
@Override 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(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繼承圖app
這個類須要兩個對象的功能,即正合了資源加載和註冊的兩個功能less
註冊就是實現了BeanDifinitionRegisitry DafaultListableBeanFactory工廠類,ide
和實現了資源加載的AbstractRefreshableApplicationContext工廠類,ui
可見spring的工廠類實質是資源加載獲取跟獲取BeanDefinie並註冊兩個類this
具體的XmlBeanDefinitionReader的初始化如上url
由於DefaultListableBeanFactory沒有實現ResourceLoader,因此設置ResourceLoader是
this.resourceLoader = new PathMatchingResourcePatternResolver();
同理Envrioment也
this.environment = new StandardEnvironment();
可是到
loadBeanDefinitions時候
// resource loading environment. beanDefinitionReader.setEnvironment(getEnvironment()); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
又設置成當前Factory的Enviroment和ResourceLoader
到loadBeanDefinitions(beanDefinitionReader)開始執行加載Resource
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException { String[] configLocations = getConfigLocations(); if (configLocations != null) { for (String configLocation : configLocations) { reader.loadBeanDefinitions(configLocation); } } }
AbstractBeanDefinitionReader
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { return loadBeanDefinitions(location, null); }
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
AbstractApplicationContext:
@Override public Resource[] getResources(String locationPattern) throws IOException { return this.resourcePatternResolver.getResources(locationPattern); }
AbstractApplicationContext的resourcePatternResolver是
PathMatchingResourcePatternResolver
public Resource[] getResources(String locationPattern) throws IOException { Assert.notNull(locationPattern, "Location pattern must not be null"); //處理以classpath*開頭的路徑: if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) { // a class path resource (multiple resources for same name possible) if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) { // a class path resource pattern return findPathMatchingResources(locationPattern); } else { // all class path resources with the given name return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length())); } } else { //classpath:開頭的路徑 // Only look for a pattern after a prefix here // (to not get fooled by a pattern symbol in a strange prefix). int prefixEnd = locationPattern.indexOf(":") + 1; if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) { // a file pattern return findPathMatchingResources(locationPattern); } else { // a single resource with the given name return new Resource[] {getResourceLoader().getResource(locationPattern)}; } } }
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException { String rootDirPath = determineRootDir(locationPattern); String subPattern = locationPattern.substring(rootDirPath.length()); Resource[] rootDirResources = getResources(rootDirPath); Set<Resource> result = new LinkedHashSet<Resource>(16); for (Resource rootDirResource : rootDirResources) { rootDirResource = resolveRootDirResource(rootDirResource); if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) { result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher())); } else if (isJarResource(rootDirResource)) { result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern)); } else { result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern)); } } if (logger.isDebugEnabled()) { logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result); } return result.toArray(new Resource[result.size()]); }
public boolean isPattern(String path) { //判斷路徑中是否包含:* ? return (path.indexOf('*') != -1 || path.indexOf('?') != -1); }
以classpath:*.xml爲例子
而後獲取到rootDirPath:classpath: , subPattern:*.xml
而後再次調用 findPathMatchingResources 走
return new Resource[] {getResourceLoader().getResource(locationPattern)};
調用到DefaultResourceLoader
public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); if (location.startsWith("/")) { return getResourceByPath(location); } else if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { // Try to parse the location as a URL... URL url = new URL(location); return new UrlResource(url); } catch (MalformedURLException ex) { // No URL -> resolve as resource path. return getResourceByPath(location); } } }
而後爲「」因此走
new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
最後回到AbstractBeanDefinitionReader的
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException { Assert.notNull(resources, "Resource array must not be null"); int counter = 0; for (Resource resource : resources) { counter += loadBeanDefinitions(resource); } return counter; }
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.isInfoEnabled()) { logger.info("Loading XML bean definitions from " + encodedResource.getResource()); } Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); //講Resource放到本地的ThreadLocal對象中去 if (currentResources == null) { currentResources = new HashSet<EncodedResource>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { InputStream inputStream = encodedResource.getResource().getInputStream(); try { //讀取資源獲取流傳入doLoadBeanDefinitions InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } //致此處文件流獲取到啦,開始讀取配置 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); return registerBeanDefinitions(doc, resource); } }
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
protected EntityResolver getEntityResolver() { if (this.entityResolver == null) { // Determine default EntityResolver to use. ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader != null) { this.entityResolver = new ResourceEntityResolver(resourceLoader); } else { this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader()); } } return this.entityResolver; }
繼而出現了新的幾個類:
EntityResolver : 加載dtd、xsd文件的資源加載類
ErrorHandler : 在xml解析過程出錯處理報告錯誤的處理類
首先看下EntityResolver:
ResourceEntityResolver
這個是負責獲取dtd、xsd的文件inputsource流的,尋找spring xml中的引用的dtd、xsd的文件
DelegatingEntityResolver中引入了這個兩個對象作處理
ErrorHandler:
public class SimpleSaxErrorHandler implements ErrorHandler { private final Log logger; /** * Create a new SimpleSaxErrorHandler for the given * Commons Logging logger instance. */ public SimpleSaxErrorHandler(Log logger) { this.logger = logger; } @Override public void warning(SAXParseException ex) throws SAXException { logger.warn("Ignored XML validation warning", ex); } @Override public void error(SAXParseException ex) throws SAXException { throw ex; } @Override public void fatalError(SAXParseException ex) throws SAXException { throw ex; }
根據xml內容去決定是否dtd的仍是xsd的驗證模式
public static final int VALIDATION_XSD = 3; //xsd驗證
public static final int VALIDATION_DTD = 2; //dtd驗證
protected int detectValidationMode(Resource resource) { if (resource.isOpen()) { throw new BeanDefinitionStoreException( "Passed-in Resource [" + resource + "] contains an open stream: " + "cannot determine validation mode automatically. Either pass in a Resource " + "that is able to create fresh streams, or explicitly specify the validationMode " + "on your XmlBeanDefinitionReader instance."); } InputStream inputStream; try { inputStream = resource.getInputStream(); } catch (IOException ex) { throw new BeanDefinitionStoreException( "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " + "Did you attempt to load directly from a SAX InputSource without specifying the " + "validationMode on your XmlBeanDefinitionReader instance?", ex); } try { return this.validationModeDetector.detectValidationMode(inputStream); } catch (IOException ex) { throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + resource + "]: an error occurred whilst reading from the InputStream.", ex); } }
驗證xml的XmlValidationModeDetector 以下:
/* * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.util.xml; import java.io.BufferedReader; import java.io.CharConversionException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import org.springframework.util.StringUtils; /** * Detects whether an XML stream is using DTD- or XSD-based validation. * * @author Rob Harrop * @author Juergen Hoeller * @since 2.0 */ public class XmlValidationModeDetector { /** * Indicates that the validation should be disabled. */ public static final int VALIDATION_NONE = 0; /** * Indicates that the validation mode should be auto-guessed, since we cannot find * a clear indication (probably choked on some special characters, or the like). */ public static final int VALIDATION_AUTO = 1; /** * Indicates that DTD validation should be used (we found a "DOCTYPE" declaration). */ public static final int VALIDATION_DTD = 2; /** * Indicates that XSD validation should be used (found no "DOCTYPE" declaration). */ public static final int VALIDATION_XSD = 3; /** * The token in a XML document that declares the DTD to use for validation * and thus that DTD validation is being used. */ private static final String DOCTYPE = "DOCTYPE"; /** * The token that indicates the start of an XML comment. */ private static final String START_COMMENT = "<!--"; /** * The token that indicates the end of an XML comment. */ private static final String END_COMMENT = "-->"; /** * Indicates whether or not the current parse position is inside an XML comment. */ private boolean inComment; /** * Detect the validation mode for the XML document in the supplied {@link InputStream}. * Note that the supplied {@link InputStream} is closed by this method before returning. * @param inputStream the InputStream to parse * @throws IOException in case of I/O failure * @see #VALIDATION_DTD * @see #VALIDATION_XSD */ public int detectValidationMode(InputStream inputStream) throws IOException { // Peek into the file to look for DOCTYPE. BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); try { boolean isDtdValidated = false; String content; while ((content = reader.readLine()) != null) { content = consumeCommentTokens(content); if (this.inComment || !StringUtils.hasText(content)) { continue; } if (hasDoctype(content)) { isDtdValidated = true; break; } if (hasOpeningTag(content)) { // End of meaningful data... break; } } return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD); } catch (CharConversionException ex) { // Choked on some character encoding... // Leave the decision up to the caller. return VALIDATION_AUTO; } finally { reader.close(); } } /** * Does the content contain the the DTD DOCTYPE declaration? */ private boolean hasDoctype(String content) { return content.contains(DOCTYPE); } /** * Does the supplied content contain an XML opening tag. If the parse state is currently * in an XML comment then this method always returns false. It is expected that all comment * tokens will have consumed for the supplied content before passing the remainder to this method. */ private boolean hasOpeningTag(String content) { if (this.inComment) { return false; } int openTagIndex = content.indexOf('<'); return (openTagIndex > -1 && (content.length() > openTagIndex + 1) && Character.isLetter(content.charAt(openTagIndex + 1))); } /** * Consumes all the leading comment data in the given String and returns the remaining content, which * may be empty since the supplied content might be all comment data. For our purposes it is only important * to strip leading comment content on a line since the first piece of non comment content will be either * the DOCTYPE declaration or the root element of the document. */ private String consumeCommentTokens(String line) { if (!line.contains(START_COMMENT) && !line.contains(END_COMMENT)) { return line; } while ((line = consume(line)) != null) { if (!this.inComment && !line.trim().startsWith(START_COMMENT)) { return line; } } return line; } /** * Consume the next comment token, update the "inComment" flag * and return the remaining content. */ private String consume(String line) { int index = (this.inComment ? endComment(line) : startComment(line)); return (index == -1 ? null : line.substring(index)); } /** * Try to consume the {@link #START_COMMENT} token. * @see #commentToken(String, String, boolean) */ private int startComment(String line) { return commentToken(line, START_COMMENT, true); } private int endComment(String line) { return commentToken(line, END_COMMENT, false); } /** * Try to consume the supplied token against the supplied content and update the * in comment parse state to the supplied value. Returns the index into the content * which is after the token or -1 if the token is not found. */ private int commentToken(String line, String token, boolean inCommentIfPresent) { int index = line.indexOf(token); if (index > - 1) { this.inComment = inCommentIfPresent; } return (index == -1 ? index : index + token.length()); } }
而後 DefaultDocumentLoader 的loadDocument獲取Document對象
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { //建立DocumentBuilderFactory DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); if (logger.isDebugEnabled()) { logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); } //建立DocumentBuilder DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); //建立Document return builder.parse(inputSource); }
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); return registerBeanDefinitions(doc, resource); } }
註冊BeanDefinitions:XmlBeanDefinitionReader
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
註冊BeanDefinitionDocumentReader
private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() { return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass)); }
public XmlReaderContext createReaderContext(Resource resource) { return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor, this, getNamespaceHandlerResolver()); }
public NamespaceHandlerResolver getNamespaceHandlerResolver() { if (this.namespaceHandlerResolver == null) { this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver(); } return this.namespaceHandlerResolver; }
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() { return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader()); }
DefaultBeanDefinitionDocumentReader 的registerBeanDefinitions
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root); }