Popular understanding of spring source code -- resource location and loading

Popular understanding of spring source (2) -- resource location and loading

When you first learn spring, you usually use this method:

        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        User user = (User)context.getBean("user");

The ApplicationContext here is also a container. It just refers to a DefaultListableBeanFactory. It doesn't matter for the moment. The real container is DefaultListableBeanFactory. Let's take the initialization of DefaultListableBeanFactory as an example. You can write as follows:

public static void main(String[] args) {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
BeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
Resource resource = new ClassPathResource("spring.xml");
reader.loadBeanDefinitions(resource);
User user = (User)factory.getBean("user");
}

1. DefaultListableBeanFactory instantiation

  • new DefaultListableBeanFactory() instantiates a factory object, initializes a container, and finally all beans will be put into this container.
  • In its constructor, first call the parent class construction:
    public DefaultListableBeanFactory() {
        super();
    }
  • In the abstract class AbstractAutowireCapableBeanFactory, AbstractAutowireCapableBeanFactory is the abstract parent class of DefaultListableBeanFactory. If you don't remember, please look at my last blog post and have a general impression. In this abstract class construction:
    public AbstractAutowireCapableBeanFactory() {
        super();
        //Add auto assemble function ignoring given interface
        ignoreDependencyInterface(BeanNameAware.class);
        ignoreDependencyInterface(BeanFactoryAware.class);
        ignoreDependencyInterface(BeanClassLoaderAware.class);
    }
  • Here's super(), you can click in and have a look. There is no operation.
  • ignoreDependencyInterface(), which means to add the automatic assembly function that ignores the given interface, will not be introduced in detail for the moment. You can refer to this article Article.

2. Resource resource encapsulation

  • spring encapsulates all resources into one Resource, including file system Resource, classpath Resource, url Resource, etc.
  • The Resource interface defines the methods to obtain the current Resource properties, such as existence, readability, and open status.
    • public interface InputStreamSource {
      InputStream getInputStream() throws IOException;
      }
      public interface Resource extends InputStreamSource {
      
          boolean exists();
          default boolean isReadable() {
              return exists();
          }
          default boolean isOpen() {
              return false;
          }
          default boolean isFile() {
              return false;
          }
          URL getURL() throws IOException;
          URI getURI() throws IOException;
          File getFile() throws IOException; 
          default ReadableByteChannel readableChannel() throws IOException {
              return Channels.newChannel(getInputStream());
          }
          long contentLength() throws IOException;
          long lastModified() throws IOException;
          Resource createRelative(String relativePath) throws IOException;
          @Nullable
          String getFilename();
          String getDescription(); 

2. XmlBeanDefinitionReader instantiation

  • Reading xml configuration file is the most important function in spring, because most of spring's functions are based on configuration.
  • XmlBeanDefinitionReader is responsible for reading and parsing the Resource, ClassPathResource, that has the xml configuration file encapsulated.

  • Here is a brief introduction to spring's resource loading system:

    • ResourceLoader: defines a Resource loader, which is mainly used to return corresponding resources according to the given Resource file address, such as judging different resources according to different prefixes such as "file:" "http:" "jar:" ".
    • BeanDefinitionReader: it mainly defines the methods of reading and converting resource files to BeanDefinition. Bean definition is the encapsulation of the class, id, alias, property and other properties of the bean you defined. At this time, the bean has not been instantiated.
    • EnvironmentCapable: defines the method to get the Environment, which is the encapsulation of the currently activated profile.
    • DocumentLoader: defines the ability to load from a resource file to convert to a document. Document is the encapsulation of xml document, from which the data of each node can be obtained.
    • AbstractBeanDefinitionReader: implement the functions defined by EnvironmentCapable and BeanDefinitionReader classes.
    • BeanDefinitionDocumentReader: defines to read Document and register BeanDefinition function.
    • BeanDefinitionParserDelegate: defines various methods for parsing elements. This is the class that really parses xml, a typical delegation pattern.

3. loadBeanDefinitions method

loadBeanDefinitions is the starting point of the whole resource loading. The following is the execution sequence diagram of this method:

In this method, the overloaded method loadBeanDefinitions(Resource resource) of XmlBeanDefinitionReader is called.

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource));
    }

EncodedResource deals with the encoding of resource files. The main logic is embodied in the getReader() method. We can set the encoding in the EncodedResource constructor, and spring will use the corresponding encoding as the encoding of the input stream.

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());
        }
    }

After processing the encoding, enter the loadbean definitions (New encoded resource (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);
        }
        //Record loaded resources
        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        //Throw an exception if the resource has been loaded
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
            //from encodedResource Remove the original resource,Get the input stream inputStream
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                //inputSource Not belong to spring,Parsing xml The whole path is org.xml.sax.InputSource
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                //The real core
                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();
            }
        }
    }

First, encapsulate the incoming resource parameter to consider the possible encoding requirements of resource. Second, leave the preparation InputSource object by reading the xml file with SAX. Finally, pass the prepared data to the real core processing part doloadbanedefinitions (InputSource, encodedresource. Getresource()) through the parameter.

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {

        try {
            //Convert from resource file to document object
            Document doc = doLoadDocument(inputSource, resource);
            //analysis document,And registration beanDefiniton To the factory
            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);
        }
    }

The core code of this method is just two sentences,

            //Convert from resource file to document object
            Document doc = doLoadDocument(inputSource, resource);
            //analysis document,And registration beanDefiniton To the factory
            int count = registerBeanDefinitions(doc, resource);

Register bean definitions (DOC, resource) is the core, and the logic is very complex. First, look at the doloaddocument (input source, resource) method:

    protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                getValidationModeForResource(resource), isNamespaceAware());
    }

Here, get the validation mode of xml file, load the xml file, and get the corresponding document. The validation mode of getting xml will be explained in the next blog.

 

Go too far, don't forget why! Now let's look at this Code:

   DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
   BeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
   Resource resource = new ClassPathResource("spring.xml");
   reader.loadBeanDefinitions(resource);

Is it clear? The summary is as follows:

  • Example chemical plant, as the container of bean;
  • Instantiate BeanDefinitionReader to read xml;
  • Encapsulate the resource file path as the corresponding resource object;
  • Calling reader.loadBeanDefinitions(resource) will actually convert resource to document first and then to beanDefinition. This step is the core of the whole resource loading.

Reference: deep analysis of spring source code

Tags: Java xml Spring encoding

Posted on Tue, 14 Apr 2020 06:45:02 -0700 by jnutter