Spring 啓動記錄(6)

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);
}
相關文章
相關標籤/搜索