Adolph
Engineer
Engineer
  • UID623
  • Fans2
  • Follows1
  • Posts72
Reads:1138Replies:0

Spring source code profiling - Bean configuration and startup (2)

Created#
More Posted time:Sep 27, 2016 13:53 PM
BeanDefinition loading, parsing and registration
 We have created the DefaultListableBeanFactory object and have a container for beans, but we haven’t performed any operations to the factory, as shown in the source code analyzed just now, and there is nothing in the factory yet. So we have to continue analyzing the code below to figure out how the beans configured in the configuration file enter the Spring container. We first analyze the implementation of XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);.
 From the XmlBeanDefinitionReader class name, we see a new term **BeanDefinition**. Let’s first understand the role of BeanDefinition in Spring.
BeanDefinition functions like the Resource interface to unify in an abstract way all the beans and encapsulate various objects into a data structure for coordinated management and scheduling in the Spring. BeanDefinition shields the differences between various objects for the Spring framework.
 Next let’s take a look at the source code of the key part of the XmlBeanDefinitionReader class.
XmlBeanDefinitionReader class
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        //First, encapsulate the Resource parameters. This is mainly used to get the implementation of the getReader() method, and the source code is as follows↓↓↓↓
        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();
        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!");
        }
  
        // Call the getResources method of DefaultResourceLoader to complete the specific Resource positioning 
        try {
                //Get the encapsulated Resource object from EncodedResource and get inputStream again from the Resource 
                InputStream inputStream = encodedResource.getResource().getInputStream();
                try {
                        InputSource inputSource = new InputSource(inputStream);
                        if (encodedResource.getEncoding() != null) {
                                inputSource.setEncoding(encodedResource.getEncoding());
                        }
                        return doLoadBeanDefinitions(inputSource, encodedResource.getResource());    //The real logic core
                }
                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();
                }
        }
}
  
/**
 *  1. Get XML file validation mode and prepare for loading the XML file correctly
 *  2. Load XML file from the located resource location and parse XML file to get the Document object
 *  3. Use the returned Document to register beans in the factory
 */
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
                // Parse the XML file to get the Document object. The parsing process is completed by DefaultDocumentLoader.  
                Document doc = doLoadDocument(inputSource, resource);    
                // Start the detailed parsing process of BeanDefinition, and bean configuration rules of Spring will be used during the parsing process. 
                return registerBeanDefinitions(doc, resource);
        }
        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);
        }
}
  
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        //loadDocument is used directly for registering Document and the getValidationModeForResource method is used for loading XML files.
        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                getValidationModeForResource(resource), isNamespaceAware());
}

EncodedResource class
//Resource encapsulation aims to use the functions of the getReader method
public Reader getReader() throws IOException {
        if (this.charset != null) {
                return new InputStreamReader(this.resource.getInputStream(), this.charset);
        }
        else if (this.encoding != null) {
                return new InputStreamReader(this.resource.getInputStream(), this.encoding);
        }
        else {
                return new InputStreamReader(this.resource.getInputStream());
        }
}
XML verification
The source code above involves the XML file loading process. XML file loading can be divided into three steps:
1. Read the XML file validation mode, that is, determining whether DTD or XSD is used
2. Verify the XML file following the validation mode above
3. Load the XML file
The above process is embodied in the source code as follows:
XmlBeanDefinitionReader class
public static final int VALIDATION_AUTO = XmlValidationModeDetector.VALIDATION_AUTO;
  
public static final int VALIDATION_XSD = XmlValidationModeDetector.VALIDATION_XSD;
  
protected int getValidationModeForResource(Resource resource) {
        int validationModeToUse = getValidationMode();
        //If you have manually appointed a validation mode, the appointed validation mode will be used.
        if (validationModeToUse != VALIDATION_AUTO) {
                return validationModeToUse;
        }
        //Otherwise, Spring will make the decision.
        int detectedMode = detectValidationMode(resource); 
        if (detectedMode != VALIDATION_AUTO) {
                return detectedMode;
        }
        return VALIDATION_XSD;
}
  
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);    //The automatic testing is completed in detectValidationMode.
        }
        catch (IOException ex) {
                throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
                        resource + "]: an error occurred whilst reading from the InputStream.", ex);
        }
}
XmlValidationModeDetector class
//Enumerate the XML validation mode
  
public static final int VALIDATION_NONE = 0;
  
public static final int VALIDATION_AUTO = 1;
  
public static final int VALIDATION_DTD = 2;
  
public static final int VALIDATION_XSD = 3;
  
private static final String DOCTYPE = "DOCTYPE";
  
//Implementation method of automatic detection of XML validation mode
public int detectValidationMode(InputStream inputStream) throws IOException {
        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)) {
                                break;
                        }
                }
                return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
        }
        catch (CharConversionException ex) {
                return VALIDATION_AUTO;
        }
        finally {
                reader.close();
        }
}
  
private boolean hasDoctype(String content) {
        return content.contains(DOCTYPE);
}
  
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)));
}
Parse XML file to get the Document object
 After the above preparations, we can now load the XML file. The loading process is not completed by XmlBeanDefinitionReader, but by the DocumentLoader interface. By tracking the source code, we can find that the specific implementation is DefaultDocumentLoader.loadDocument(), and the source code is as below:
XmlBeanDefinitionReader class
//It does not implement the loadDocment process, but calls loadDocument of documentLoader
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        //The type of documentLoader is DefaultDocumentLoader
        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                getValidationModeForResource(resource), isNamespaceAware());
    }
Next, let’s see the method implementation in DefaultDocumentLoader.
DefaultDocumentLoader class
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
                ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
  
        DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
        if (logger.isDebugEnabled()) {
                logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
        }
        DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
        return builder.parse(inputSource);
}
With this method, we can get the Document object of XML.
Guest