3. Initialization process of spring IOC container
In short, the initialization of IoC container is started by the refresh() method introduced earlier, which marks the official start of IoC container. Specifically, this startup includes three basic processes: Resource location, loading and registration of BeanDefinition.
The first process is the Resource location process. This Resource location refers to the Resource location of BeanDefinition, which is completed by ResourceLoader through a unified Resource interface: this Resource provides a unified interface for the use of various forms of BeanDefinition. For the existing forms of these beandefinitions, for example, the Bean definition information in the file system can be abstracted using FileSystemResource, and the Bean definition information in the classpath can be used using ClassPathResource. This positioning process is similar to the process of finding data in a container, just like finding water in a bucket.
The second process is the loading of BeanDefinition. This loading process represents the user-defined Bean as the data structure inside the IoC container, and the data structure inside the container is * * BeanDefinition * *. Specifically, this BeanDefinition is actually the abstraction of pojo objects in the IoC container. Through the data structure defined by this BeanDefinition, the IoC container can easily manage pojo objects, that is, beans.
The third process is to register these beandefinitions with the IoC container. This procedure is called
The implementation of beandefinitionbrightness interface. This registration process registers the BeanDefinition parsed during loading with the IoC container. Through analysis, we can see that BeanDefinition is injected into a HashMap inside the IoC container, and the IoC container holds these BeanDefinition data through this HashMap.
3.1 Resource positioning of beandefinition
When using DefaultListablcBeanFactory, first define a Resource to locate the BeanDefinition to be used by the container. In this case, the ClassPathResource is used. This means that Spring will look for BeanDefinition information in the classpath in the form of a file.
ClassPathResource res = new ClassPathResource("spring-application.xml")
The Resource defined here cannot be directly used by DefaultListableBeanFactory. Spring processes this information through BeanDefinitionReader.
We can also see the advantages of using DefaultListablcBeanFactory over using ApplicationContext directly. In the ApplicationContext, Spring has provided us with a series of implementations of readers loaded with different resources, and * * DefaoltListableBeanFactory is just a pure IOC container, which needs to be configured with specific readers to complete these functions** Of course, there are advantages and disadvantages. Using a lower level container such as DefauhListableBeanFactory can improve the flexibility of customizing IOC containers. Facilitate our understanding of IOC containers.
Go back to the application Context we often use, such as filesystemxmlapplicationcontext and ClassPathXmlApplicationContext. Simply analyzing the names of these classes, you can clearly see what different Resource reading functions they can provide. For example, filesystemxmlapplicationcontext can load resources from the file system, classpathxmlapplication, and Context can load resources from Class Path.
Next, take FileSystemXmlApplicationContext as an example to see how it completes the Resource location process by analyzing the implementation of this ApplicationContext.
Inheritance system of FileSystemXmlApplicationContext:
It can be seen that this filesystemxmlapplicationcontext has the ability of ResourceLoaderi to read in the BeanDefinition defined by Resource by inheriting AbstractApplicationContext, because the base class of AbstractApplicationContext is DefaultResourceLoader. Let's take a look at the specific implementation of filesystemxmlapplicationcontext:
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext { public FileSystemXmlApplicationContext() { }
// Specify the IOC container for your parents public FileSystemXmlApplicationContext(ApplicationContext parent) { super(parent); }
//configLocation is the path to the file where the BeanDefinition is located public FileSystemXmlApplicationContext(String configLocation) throws BeansException { this(new String[] {configLocation}, true, null); } public FileSystemXmlApplicationContext(String... configLocations) throws BeansException { this(configLocations, true, null); } public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException { this(configLocations, true, parent); } public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException { this(configLocations, refresh, null); }
//In the initialization process of the object, the refresh function is called to load the BeanDefinition, and the refresh is activated.
//The loading process of BeanDefinition is analyzed in detail below public FileSystemXmlApplicationContext( String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } } // This is the implementation of the Resource applied to the file system. A BeanDefinition of the file system location is obtained by constructing a fileSystemResource @Override protected Resource getResourceByPath(String path) { if (path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); } }
In the filesystenapplicationcontext, we can see that the constructor implements the function of processing the configuration, so that all beandefinitions configured in the file system and existing as XML files can be effectively processed. For example, we implement the getResourceByPath method, which is a template method and serves to read resources. The implementation of loC container function is not involved here because it inherits AbstractApplicationContext. Initialize the IoC container by refresh in the constructor.
The whole process of BeanDefinition resource location. The process of locating BeanDefinition resources is initially triggered by refresh, which is called in the constructor of FileSystemXmlBeanFactory. The getResourceByPath() method is then called.
Focus on the implementation of the refreshBeanFactory method of AbstractRefreshableApplicationContext,
This refreshBeanFactory is invoked in the refresh() method called in the FileSystemXmlApplicationContext constructor in AbstractApplicationContext. In this method, a container is built through createBeanFactroy for Applicationcontext. This loC container is the DefaultListableBeanFactory we mentioned earlier. At the same time, it starts loadBeanDefinitions to load BeanDefinition. This process is very similar to the previous process of using loC container (XmlBeanFactory).
//Construction method of filesystemxlapplicationcontext public FileSystemXmlApplicationContext( String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { // Call the method in the base class AbstractApplicationContext refresh(); } } //refresh method of AbstractApplicationContext @Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh"); // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. // Call your own method, start the container, and then return to the container ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); // The following code is not fully expanded } } // protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { // Start the container and load the BeanDefinition refreshBeanFactory(); return getBeanFactory(); } //Methods in AbstractRefreshableApplicationContext protected final void refreshBeanFactory() throws BeansException { if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { //Build an IOC container DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); // Start BeanDefinitions to load BeanDefinitions loadBeanDefinitions(beanFactory); this.beanFactory = beanFactory; } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } } //This is where the DefaultListableBeanFactory is created in the context //getInternalParentBeanFactory() will be generated based on the existing parent IOC container information of the container //Parental IOC container for DefaultLastableBeanFactory protected DefaultListableBeanFactory createBeanFactory() { return new DefaultListableBeanFactory(getInternalParentBeanFactory()); } //Here, the BeanDefinitionReader is used to load the place where the Bean definition is, and multiple loading methods are allowed, //Here, the concrete implementation is delegated to subclasses through an abstract function protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException; //Let's take a look at the rewriting of this method in AbstractApplicationContext. The rewriting of this class can be seen from the name of the BeanDefinition loaded into the XML file @Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. // Create an Xml BeanDefinition reader 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. // Allow Reader initialization initBeanDefinitionReader(beanDefinitionReader); //Loading BeanDefinitions using BeanDefinitionReader loadBeanDefinitions(beanDefinitionReader); } //This method in AbstractXmlApplicationContext class loads BeanDefinition. Before loading, you must find the Resource location of BeanDefinition protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { //Obtain the Resource location of the configuration file in the form of Resource Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } // Get the location of the configuration file in the form of String String[] configLocations = getConfigLocations(); if (configLocations != null) { //After finding the Resource, load the BeanDefinition reader.loadBeanDefinitions(configLocations); } } // This method is to load the Resource in the AbstractBeanDefinitionReader class public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException { // Get the ResourceLoader. DefaultResourceLoader is used here ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available"); } //Here, the path mode of the Resource is analyzed. For example, we set various Ant format path definitions to get the required Resource set //These Resource collections point to the defined BeanDefinition information, which can be multiple files if (resourceLoader instanceof ResourcePatternResolver) { // Resource pattern matching available. try { //Call getRe source of DefaultResourceLoader to complete the specific Resource location Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); int count = loadBeanDefinitions(resources); if (actualResources != null) { Collections.addAll(actualResources, resources); } if (logger.isTraceEnabled()) { logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]"); } return count; } catch (IOException ex) { throw new BeanDefinitionStoreException( "Could not resolve bean definition resource pattern [" + location + "]", ex); } } else { // Can only load single resources by absolute URL. // Load via absolute URL path Resource resource = resourceLoader.getResource(location); int count = loadBeanDefinitions(resource); if (actualResources != null) { actualResources.add(resource); } if (logger.isTraceEnabled()) { logger.trace("Loaded " + count + " bean definitions from location [" + location + "]"); } return count; } } //See how to find a resource and get a resource location through the DefaultResourceLoader class, public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); for (ProtocolResolver protocolResolver : getProtocolResolvers()) { Resource resource = protocolResolver.resolve(location, this); if (resource != null) { return resource; } } //Locate the resource using the subclass's own implementation if (location.startsWith("/")) { return getResourceByPath(location); } //Handle the resource identified by the classpath 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 (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url)); } catch (MalformedURLException ex) { // No URL -> resolve as resource path. // Find the resource using the subclass implementation return getResourceByPath(location); } } }
getResourceByPath will be implemented by the subclass FileSystemXmlApplicationContext. This method returns a FileSystemResource object through which Spring can perform relevant I/O operations and complete the positioning of BeanDefinition. It's clear from the analysis here. What it implements is to parse the path, generate a FileSystemResource object and return it,
@Override protected Resource getResourceByPath(String path) { if (path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); }
If it is other ApplicationContext, other kinds of resources will be generated accordingly, such as ClassPathResource, ServletContextResource, etc. You can learn about the types of resources in Spring in the inheritance relationship of Resource classes. As an interface, a Resource defines many I/O-related operations, which can also be seen from the Resource's interface definition. These interfaces represent different meanings for different Resource implementations, which should be considered in Resource implementations. The implementation of the Resource interface in Spring is as follows:
Summary: through the analysis of the previous implementation principle, we take the implementation principle of FileSystemXmlApplicationcontext as an example to understand the solution to the Resource location problem, that is, the location implementation of resources in the form of FileSystem.
Looking back at the process: first we see that the refresh() method is called in the constructor of FileSystemXMLApplicationContext, and the initialization process starts from the container. The refresh() method is the method in the base class AbstractApplicationContext. In this method, the refreshBeanFactory() method in AbstractRefreshableApplicationContext is called in this method. Create the default container for defaultlistablebeanfactory IOC and start loading BeanDefinition. First, load all resources through the loadBeanDefinitions() method in the AbstractBeanDefinitionReader class, and store them through the DefaultResourceLoader structure. Then look at how to find the location of a Resource. Find a Resource location through the method defined in DefaultResourceLoader, which will be found through classPath, URL absolute path, and getResourceByPath() method implemented by subclasses. The above is to start a container and find the Resource location.
After the BeanDefinition positioning is completed, you can load the BeanDefinition through the returned Resource object. After the positioning process is completed, I/O operation conditions are created for the loading of BeanDefinition, but the specific data has not started to be read. The reading of these data will be completed in the loading and parsing of BeanDefinition described below. Still take the bucket as an example. It's like using a bucket to draw water. You have to find the water source first. The positioning of the Resource here is similar to that the water source has been found. The following is the process of playing forever, similar to the process of loading the found water into the bucket. Finding water is not easy, but compared with fetching water, we find that fetching water requires more skills.
3.2 loading and parsing of beandefinition
After analyzing the Resource location representing BeanDefinition, let's understand the loading process of BeanDefinition information. For the loC container, this loading process is equivalent to the process of transforming the defined BeanDefinition into a data structure represented inside Spring (this data structure is BeanDefinition) in the IoC container** The management of beans and the implementation of dependency injection function by the loC container are completed by performing various related operations on the BeanDefinition it holds** These BeanDefinition data are maintained and maintained in the IoC container through a concurrent HashMap. Of course, this is only a relatively simple maintenance method. If you need to improve the performance and capacity of IoC containers, you can make some extensions yourself.
Starting with the design of DefaultListableBeanFactory, let's see how IOC container completes BeanDefinition loading. The DefaultListableBeanFactory is the IOC default container, which implements the basic functions of the class container. Compared with the ApplicationContext container, it is a lower level container. ApplicationContext is a higher form container. It is just a structure for Bean operation, and ApplicationContext includes it and adds many functions, such as Resource positioning, events and so on.
Take another look at the initialization population of the IoC container, that is, the refresh() method. This method is initially called in the constructor of filesystemxmlapplicationcontext. Its call marks the beginning of container initialization. These initialization objects are BeanDefinition data and initialization population (take filesystemxlapplicationcontext as an example):
public FileSystemXmlApplicationContext( String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); //Start the container and start initialization if (refresh) { refresh(); } }
refresh() is a very important method for container startup. Let's introduce its implementation. This method is found in the AbstractApplicationContext class (which is the base class of FileSystemXmlApplicationContext). It describes the initialization process of the whole ApplicationContext in detail, such as the update of BeanFactory, the registration of MessageSource and PostProcessor. It looks more like a template or execution outline for initializing ApplicationContext, which provides conditions for Bean life cycle management.
//AbstractApplicationContext class @Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh"); // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. // Call your own method, first let the subclass start the container, and then return to the container 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. // Set the post processor of BeanFactory postProcessBeanFactory(beanFactory); StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process"); // Invoke factory processors registered as beans in the context. //Call the post processors of BeanFactory, which are registered with the container in the Bean definition invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. //Register the postprocessing of Bean, and call it in the process of Bean creation. registerBeanPostProcessors(beanFactory); beanPostProcess.end(); // Initialize message source for this context. //Initializes the message source in the context initMessageSource(); // Initialize event multicaster for this context. //Initializes the event mechanism in the context initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. //Initialize other special beans onRefresh(); // Check for listener beans and register them. //Check the listening beans and register them with the container registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. //Instantiate all (non lazy init) singletons finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. //Publish container events End the Refresh process 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. //In order to prevent Bean resource occupation, in exception handling, destroy the singleton Bean generated in the previous process destroyBeans(); // Reset 'active' flag. //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(); contextRefresh.end(); } } }
Enter the refreshBeanFaetory() method of AbstractRefreshableApplicationContext, where BeanFactory is created. Before creating a loC container, if there is already a container, you need to destroy and close the existing container to ensure that the newly established loC container is used after refresh. So, this refresh is very much like restarting the container. After the current loC container is established, the initialization process of the container is started, such as the manned operation of BeanDefinition. See the following figure for the specific process:
//AbstractRefreshableApplicationContext class: @Override protected final void refreshBeanFactory() throws BeansException { if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { //Build an IOC container DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); // Start BeanDefinition to load BeanDefinition loadBeanDefinitions(beanFactory); this.beanFactory = beanFactory; } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
Interactive process of BeanDefinition loading:
The loadbean definitions called here is actually an abstract method, so where does the actual loading process take place? Let's take a look at the implementation of loadbean definitions mentioned earlier in the subclass AbstractXmlApplicationcontext of abstractrefreshableappnextioncontext. In this loadbean definitions, the reader is initialized
XmlBeanDefinitionReader, then set the reader in the IoC container, and finally start the reader to complete the loading of BeanDefinition in the IoC container.
//Implementation of loadBeanDefinitions method in AstractXmlApplicationContext @Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. // Create an Xml BeanDefinition reader and set it to BeanFactory through callback. XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. beanDefinitionReader.setEnvironment(this.getEnvironment()); //Configure the xmlbean definitionreader with a ResourceLoader. Because DefaultResourceLoader is the parent class, this can be used directly 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. // The following is the process of starting Bean definition information loading // Allow Reader initialization initBeanDefinitionReader(beanDefinitionReader); //Loading BeanDefinitions using BeanDefinitionReader loadBeanDefinitions(beanDefinitionReader); }
The next step is where loadBeanDefinitions is called. First, get the Resource location of BeanDefinition information, and then directly call xmlbeandefinitionreader to read. The specific manned process is entrusted to BeanDefinitionReader. Because the BeanDefinition here is defined through an XML file, the xmlbeandefinitionreader is used to load the BeanDefinition into the container.
//XmlBeanDefinitionReader to load BeanDefinition into the container. protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { //Obtain the Resource location of the configuration file in the form of Resource Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } // Get the location of the configuration file in the form of String String[] configLocations = getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } }
Through the above analysis of the implementation principle, we can see that during the initialization of FileSystmXmlApplicationContext, the whole BeanDefinition loading process is started by calling refresh() of IoC container. This initialization is completed through the defined XmlBeanDefinitionReader. At the same time, we also know that the IoC container actually used is the definitlistablebeanfactory, and the specific Resource loading is implemented when the XmlBeanDefinitionReader reads the BeanDefinition. Because Spring can correspond to different forms of BeanDefinition. Because the XML definition is used here, you need to use XmlBeanDefinitionReader. If other BeanDefinition methods are used, you need to use other kinds of BeanDefinitionReader to load data.
As you can see in the implementation of xmlbeandefinitionreader, it is in reader Start in loadbeandefinitions()
BeanDefinition is manned. At this time, the parent class AbstractBeanDefinitionReader of XmlBeanDefinitionReader is ready for BeanDefinition loading.
@Override public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException { // If the Resource is empty, stop loading the BeanDefinition // Then start the process of loading BeanDefinition, which will traverse the BeanDefinition information contained in the whole Resource collection Assert.notNull(resources, "Resource array must not be null"); int count = 0; for (Resource resource : resources) { count += loadBeanDefinitions(resource); } return count; }
The loadBeanDefinitions(Resource res) method is called here, but it is not implemented in the AbstractBeanDefinitionReader class. It is an interface method. The specific implementation is in the XmlBeanDefinitionReader. In the reader, you need to get the Resource representing the XML file, because this Resource object encapsulates the I/O operation on the XML file, Therefore, the reader can get the XML file object after opening the IO stream. With this file object, you can parse the XML document tree according to the Bean definition rules of Spring. This parsing is completed by BeanDefinitionParserDelegate. It seems that the implementation context is very clear. For details, please refer to the code implementation:
//This method of loading BeanDefinition is not implemented in the AbstractBeanDefinitionReader class. It is a method defined by the BeanDefinitionReader interface and implemented by various subclasses of loading BeanDefinition. What we analyze here is the filesystemxlapplicationcontext container, which loads XML files, Therefore, XMLBeanDefinitionReader is used to load the Resource location of BeanDefinition int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException; //Let's look at the implementation of this method in the xmlbean definitionreader class // Call entry @Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); } // Continue in this class 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!"); } //Here you get the Xml file and the IO InputSource ready for reading try (InputStream inputStream = encodedResource.getResource().getInputStream()) { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } //Specific read operations 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(); } } } // Next, let's look at the specific xml file for reading the bean definition // The specific reading process, which is where the BeanDefinition is actually loaded from a specific XML file protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { // Here, the Document object of the XML file is obtained, and the parsing process is completed by the documentLoader // Is the DefaultDocumentLoader, which is created where the documentLoader is defined Document doc = doLoadDocument(inputSource, resource); //What starts here is the detailed process of BeanDefinition parsing, which will use Spring's Bean configuration rules. 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); } }
Then, how Spring's beandefinition is parsed according to Spring's Bean semantic requirements and transformed into the internal data structure of the container is completed in registerBeanDefinitions(doc, resource). The specific process is completed by BeanDefinitionDocumentReader. This registerBeanDefinition also counts the number of loaded beans.
// Methods in xmlbean definitionreader: public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // Here we get the BeanDefinitionDocumentReader to parse the bean definition in XML BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); // The specific parsing process is completed in this registerBeanDefinitions documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
The loading of BeanDefinition is divided into two parts. Firstly, the document objects are obtained by calling the XML parser, but these document objects are not parsed according to Spring's Bean rules. After the general XML parsing is completed, it is the place to parse according to the Spring Bean rules. The parsing process according to the Spring Bean rules is implemented in the documentReader. The documentReader used here is the DefaultBeanDefinitionDocumentReader set by default. The creation of this DefaultBeanDefinitionDocumentReader is completed in the following methods, and then the BeanDefinition processing is completed. The processing results are held by the BeanDefinitionHolder object. In addition to holding the BeanDefinition object, this BeanDefinitionHolder also holds other information related to the use of BeanDefinition, such as the name and alias collection of the Bean. This BeanDefinitionHolder is generated by parsing the contents of the document tree. It can be seen that this parsing process is implemented by the DefaultBeanDefinitionDocumentReader class (specifically implemented in the processBeanDefinition method). At the same time, this parsing is closely related to Spring's configuration rules for BeanDefinition. See the following code for the specific implementation principle:
// Methods in xmlbean definitionreader: //After obtaining the documentReader, the data is ready for the parsing process of the specific Spring Bean protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() { return BeanUtils.instantiateClass(this.documentReaderClass); } //In the DefaultBeanDefinitionDocumentReader class: // This is where the BeanDefinition is handled. The specific processing is delegated to the BeanDefinitionParserDelegate class // root corresponds to the XML element defined in the Spring BeanDefinition protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // BeanDefinitionHolder is the encapsulated class of BeanDefinition object, which encapsulates BeanDefinition, Bean name and alias, // Use it to complete the registration with the IOC container. Obtaining the BeanDefinitionHolder means that the BeanDefinition is registered through BeanDefinitionParserDelegate // The information of the XML element is parsed according to the Bean rules of Spring BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance. // This is where the parsed BeanDefinition is registered with the IOC container BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // Send registration event. // After the BeanDefinition is registered with the IOC container, a message is sent getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
The specific resolution of Spring BeanDefinition is completed in BeanDefinitionParserDelegate. This class contains the processing of various Spring Bean definition rules. For example, we are most familiar with how to deal with Bean elements, that is, how to deal with the most common element information in the XML definition file. Here you will see the definition of those familiar BeanDefinition
Management, such as id, name, alias and other attribute elements. After reading the values of these elements from the attributes of the corresponding elements in the XML file, set them to the generated BeanDefinitionHolder. The resolution of these attributes is relatively simple. For the resolution of other element configurations, such as the attribute configuration of various beans, a more complex resolution process is completed by parseBeanDefinitionElement. After parsing, the parsing result will be put into the BeanDefinition object and set to the BeanDefinitionHolder, as shown in the following code:
//In the BeanDefinitionParserDelegate class: @Nullable public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) { Get here<bean>Element id, name and aliase The value of the property String id = ele.getAttribute(ID_ATTRIBUTE); String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); List<String> aliases = new ArrayList<>(); if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); } String beanName = id; if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { beanName = aliases.remove(0); if (logger.isTraceEnabled()) { logger.trace("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases"); } } if (containingBean == null) { checkNameUniqueness(beanName, aliases, ele); } //Detailed analysis of Bean AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { if (!StringUtils.hasText(beanName)) { try { if (containingBean != null) { beanName = BeanDefinitionReaderUtils.generateBeanName( beanDefinition, this.readerContext.getRegistry(), true); } else { beanName = this.readerContext.generateBeanName(beanDefinition); // Register an alias for the plain bean class name, if still possible, // if the generator returned the class name plus a suffix. // This is expected for Spring 1.2/2.0 backwards compatibility. String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } if (logger.isTraceEnabled()) { logger.trace("Neither XML 'id' nor 'name' specified - " + "using generated bean name [" + beanName + "]"); } } catch (Exception ex) { error(ex.getMessage(), ele); return null; } } String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null; }
The above describes the process of parsing bean elements, that is, the process of creating BeanDefinition according to the definition of XML. This * * BeanDefinition can be regarded as an abstract * * of the definition of < bean >.
Most of the data encapsulated in this data object is related to the definition of < bean > < / bean >, and many of them are seen when defining beans
Those Spring tags, such as init method, destroy method, factory method, and so on
The BeanDefinition data type is the most important. It encapsulates a lot of basic data needed by the IoC container. With these basic data, IoC container can process Bean configuration and realize corresponding container features.
**This BeanDefinition is a very important core data structure in the IoC container system** After parsing, these data are ready to show their skills in the IoC container.
Let's take a look at how to generate a BeanDefinition:
@Nullable public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, @Nullable BeanDefinition containingBean) { this.parseState.push(new BeanEntry(beanName)); //Here, only the class name set in the defined < bean > is read, and then brought into the BeanDefinition. It's just a record, not a record // It involves the instantiation process of objects, which is actually completed during dependency injection String className = null; if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } String parent = null; if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } try { // The required BeanDefinition object is generated here to prepare for the loading of Bean definition information AbstractBeanDefinition bd = createBeanDefinition(className, parent); // Here, the attribute of the current Bean element is parsed and the description information is set parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); //You can clearly see from the name that this is the place to parse the information of various < bean > elements parseMetaElements(ele, bd); parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); // Resolve constructor settings for < bean > parseConstructorArgElements(ele, bd); // Resolve the property settings of < bean > parsePropertyElements(ele, bd); parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource()); bd.setSource(extractSource(ele)); return bd; } //These exceptions are often seen when there is a problem configuring the Bean catch (ClassNotFoundException ex) { error("Bean class [" + className + "] not found", ele, ex); } catch (NoClassDefFoundError err) { error("Class that bean class [" + className + "] depends on not found", ele, err); } catch (Throwable ex) { error("Unexpected failure during bean definition parsing", ele, ex); } finally { this.parseState.pop(); } return null; }
The above is where the BeanDefinition is generated. Here, let's take an example of property parsing to complete the analysis of the whole BeanDefinition manned process, or parse the definitions in BeanDefinition layer by layer in the code of BeanDefinitionParserDelegate class, such as integrating attribute elements into each specific attribute element, and then processing the specific attribute values. According to the parsing results, the processing of these property values will be encapsulated into a Property Value object and set to the BeanDefinition object.
//In the BeanDefinitionParserDelegate class: //Here, the collection of property child elements of the specified Bean element is resolved public void parsePropertyElements(Element beanEle, BeanDefinition bd) { //Traverse all property elements defined under the Bean element NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) { // The process of parsing a property element after determining that it is a property element parsePropertyElement((Element) node, bd); } } } //Parse a single attribute. After the value is parsed, put it into beanDefinition public void parsePropertyElement(Element ele, BeanDefinition bd) { //Get the name of the property String propertyName = ele.getAttribute(NAME_ATTRIBUTE); if (!StringUtils.hasLength(propertyName)) { error("Tag 'property' must have a 'name' attribute", ele); return; } this.parseState.push(new PropertyEntry(propertyName)); try { //If a property with the same name already exists in the same Bean, it will be returned without parsing. //That is, if there is a property setting with the same name in the same Bean, there is only one function if (bd.getPropertyValues().contains(propertyName)) { error("Multiple 'property' definitions for property '" + propertyName + "'", ele); return; } //This is where the property is parsed. The returned object sets the parsing result for the property property defined by the Bean, //The parsing result is encapsulated in the propertyValue object, and then set to beanDefinition Object val = parsePropertyValue(ele, bd, propertyName); PropertyValue pv = new PropertyValue(propertyName, val); parseMetaElements(ele, pv); pv.setSource(extractSource(ele)); bd.getPropertyValues().addPropertyValue(pv); } finally { this.parseState.pop(); } } // Here you get the value of a Property element public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) { String elementName = (propertyName != null ? "<property> element for property '" + propertyName + "'" : "<constructor-arg> element"); // Should only have one child element: ref, value, list, etc. NodeList nl = ele.getChildNodes(); Element subElement = null; for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) && !nodeNameEquals(node, META_ELEMENT)) { // Child element is what we're looking for. if (subElement != null) { error(elementName + " must not contain more than one sub-element", ele); } else { subElement = (Element) node; } } } // Here, judge whether the property is ref (dependent object) or value (normal value). It is not allowed to be ref and value at the same time boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE); boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE); if ((hasRefAttribute && hasValueAttribute) || ((hasRefAttribute || hasValueAttribute) && subElement != null)) { error(elementName + " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele); } // If it is ref, create a ref data object RuntimeBeanReference, which encapsulates the ref information if (hasRefAttribute) { String refName = ele.getAttribute(REF_ATTRIBUTE); if (!StringUtils.hasText(refName)) { error(elementName + " contains empty 'ref' attribute", ele); } RuntimeBeanReference ref = new RuntimeBeanReference(refName); ref.setSource(extractSource(ele)); return ref; } // If it is value, create a value data object TypedStringValue, which encapsulates the information of value else if (hasValueAttribute) { TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE)); valueHolder.setSource(extractSource(ele)); return valueHolder; } //If there are child elements, the resolution of child elements is triggered else if (subElement != null) { return parsePropertySubElement(subElement, bd); } else { // Neither child element nor "ref" or "value" attribute found. error(elementName + " must specify a ref or value", ele); return null; } }
Here is the parsing process of the property child elements, Array and list Set. Map, Prop and other elements will be parsed here to generate corresponding data objects, such as ManagedList, ManagedArray, ManagedSet, etc. These Managed classes are Spring's data encapsulation of specific BeanDefinition., For example, parsearrayelement, parselistelement, parsesetelement, parsemapelement Parsepropeiment corresponds to different types of data parsing, and these specific parsing methods
It can also be found in the BeanDefinitionParserDelegate class. Because the method name is very clear, you can quickly find it from the method name. The following takes the process of parsing the element of Property as an example to illustrate how the specific parsing process is completed through its implementation:
Definition of XML:
<bean id="moreComplexObject" class="example.ComplexObject"> <!-- results in a setAdminEmails(java.util.Properties) call --> <property name="adminEmails"> <props> <prop key="administrator">administrator@example.org</prop> <prop key="support">support@example.org</prop> <prop key="development">development@example.org</prop> </props> </property> <!-- results in a setSomeList(java.util.List) call --> <property name="someList"> <list> <value>a list element followed by a reference</value> <ref bean="myDataSource" /> </list> </property> <!-- results in a setSomeMap(java.util.Map) call --> <property name="someMap"> <map> <entry key="an entry" value="just some string"/> <entry key="a ref" value-ref="myDataSource"/> </map> </property> <!-- results in a setSomeSet(java.util.Set) call --> <property name="someSet"> <set> <value>just some string</value> <ref bean="myDataSource" /> </set> </property> </bean>
Resolve child elements in a Property
// Parse the child elements in property, such as list, map, prop, etc public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd, @Nullable String defaultValueType) { if (!isDefaultNamespace(ele)) { return parseNestedCustomElement(ele, bd); } else if (nodeNameEquals(ele, BEAN_ELEMENT)) { BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd); if (nestedBd != null) { nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd); } return nestedBd; } else if (nodeNameEquals(ele, REF_ELEMENT)) { // A generic reference to any name of any bean. String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE); boolean toParent = false; if (!StringUtils.hasLength(refName)) { // A reference to the id of another bean in a parent context. refName = ele.getAttribute(PARENT_REF_ATTRIBUTE); toParent = true; if (!StringUtils.hasLength(refName)) { error("'bean' or 'parent' is required for <ref> element", ele); return null; } } if (!StringUtils.hasText(refName)) { error("<ref> element contains empty target attribute", ele); return null; } RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent); ref.setSource(extractSource(ele)); return ref; } else if (nodeNameEquals(ele, IDREF_ELEMENT)) { return parseIdRefElement(ele); } else if (nodeNameEquals(ele, VALUE_ELEMENT)) { return parseValueElement(ele, defaultValueType); } else if (nodeNameEquals(ele, NULL_ELEMENT)) { // It's a distinguished null value. Let's wrap it in a TypedStringValue // object in order to preserve the source location. TypedStringValue nullHolder = new TypedStringValue(null); nullHolder.setSource(extractSource(ele)); return nullHolder; } else if (nodeNameEquals(ele, ARRAY_ELEMENT)) { return parseArrayElement(ele, bd); } //The child element is List else if (nodeNameEquals(ele, LIST_ELEMENT)) { return parseListElement(ele, bd); } else if (nodeNameEquals(ele, SET_ELEMENT)) { return parseSetElement(ele, bd); } else if (nodeNameEquals(ele, MAP_ELEMENT)) { return parseMapElement(ele, bd); } else if (nodeNameEquals(ele, PROPS_ELEMENT)) { return parsePropsElement(ele); } else { error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele); return null; } }
Let's take a look at how the attribute configuration such as List is resolved. It is still in BeanDefinitionParserDelegate: a List object is returned. This List is the ManagedList defined by Spring, which is used as the data package for encapsulating the configuration definitions such as List, as shown in the following code:
public List<Object> parseListElement(Element collectionEle, @Nullable BeanDefinition bd) { String defaultElementType = collectionEle.getAttribute(VALUE_TYPE_ATTRIBUTE); NodeList nl = collectionEle.getChildNodes(); ManagedList<Object> target = new ManagedList<>(nl.getLength()); target.setSource(extractSource(collectionEle)); target.setElementTypeName(defaultElementType); target.setMergeEnabled(parseMergeAttribute(collectionEle)); // Specific List element parsing process parseCollectionElements(nl, target, bd, defaultElementType); return target; } protected void parseCollectionElements( NodeList elementNodes, Collection<Object> target, @Nullable BeanDefinition bd, String defaultElementType) { //Traverse all Element nodes and determine whether their type is Element for (int i = 0; i < elementNodes.getLength(); i++) { Node node = elementNodes.item(i); if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT)) { //Added to the target. The target is a ManagedList. At the same time, it triggers the parsing process of the next level of child elements and calls recursively target.add(parsePropertySubElement((Element) node, bd, defaultElementType)); } } }
After this layer by layer parsing, the BeanDefinition defined in the XML file is cut into the IOC container, and the data mapping is established in the container. The corresponding data structures are established in the IOC container, or can be regarded as the abstraction of POJO objects in the IOC container. These data structures can take the AbstractBeanDefinition as the population to allow the IOC container to perform indexing, query and operation. The simple POJO operation actually contains a complex abstract process. After the above loading process, the IOC container roughly completes the data preparation (or initialization process) for managing Bean objects. However, important dependency injection has not actually occurred at this time. Now, only some static configuration information exists in the IOC container BeanDefinition. Strictly speaking, the container is not fully functional at this time. To fully play the role of the container, you need to complete the registration of data to the container.
3.3 registration of beandefinition in IOC container
The process of loading and parsing BeanDefinition in IoC container has been analyzed earlier. After these actions are completed, the user-defined BeanDefinition information has established its own data structure and corresponding data representation in the loC container, but at this time, these data can not be used directly by the IoC container, so these BeanDefinition data need to be registered in the IoC container. This registration provides a more friendly way to use the IoC container. In the DefaultListableBeanFactory, the loaded BeanDefinition is held through a HashMap. The definition of this HashMap can be seen in the DefaultListableBeanFactory, as shown below:
/** Map of bean definition objects, keyed by bean name. bean Define the mapping of the object, with the bean name as the key.*/ private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
The process of registering the parsed BeanDefinition with beanDefinitionMap in IOC container is carried out after loading BeanDefinition, as shown in the following code:
You can see that the registration starts immediately after the parsing is completed.
//In the DefaultBeanDefinitionDocumentReader class: // This is where the BeanDefinition is handled. The specific processing is delegated to the BeanDefinitionParserDelegate class // root corresponds to the XML element defined in the Spring BeanDefinition. After processing the BeanDefinition, it will register with the container. Here, the method in BeanDefinitionReaderUtils is called. The final implementation of the registration logic is the registerBeanDefinition() method in DefaultListableBeanFactory protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // BeanDefinitionHolder is the encapsulated class of BeanDefinition object, which encapsulates BeanDefinition, Bean name and alias, // Use it to complete the registration with the IOC container. Obtaining the BeanDefinitionHolder means that the BeanDefinition is registered through BeanDefinitionParserDelegate // The information of the XML element is parsed according to the Bean rules of Spring BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance. // This is where the parsed BeanDefinition is registered with the IOC container BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // Send registration event. // After the BeanDefinition is registered with the IOC container, a message is sent getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
We trace the above code calls to see the specific registration implementation. The interface of BeanDefinitionRegistry is implemented in DefaultListableBeanFactory. The implementation of this interface completes the registration of BeanDefinition to the container. The registration process is not complicated, that is, set the parsed BeanDefinition to the hashMap. It should be noted that if a BeanDefinition with the same name is encountered, the processing needs to be completed according to the configuration of allowBeanDefinitionOverriding. The specific implementation code is as follows:
<pre spellcheck="false" class="md-fences mock-cm md-end-block md-fences-with-lineno" lang="java" cid="n77" mdtype="fences">//Register the implementation of BeanDefinition in the DefaultListableBeanFactory class: @Override public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { Assert.hasText(beanName, "Bean name must not be empty"); Assert.notNull(beanDefinition, "BeanDefinition must not be null"); if (beanDefinition instanceof AbstractBeanDefinition) { try { ((AbstractBeanDefinition) beanDefinition).validate(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex); } } //Here, check whether a BeanDefinition with the same name has been registered in the IOC container. If there is a BeanDefinition with the same name, but it is not allowed to be overwritten, an exception will be thrown BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName); if (existingDefinition != null) { if (!isAllowBeanDefinitionOverriding()) { throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition); } else if (existingDefinition.getRole() < beanDefinition.getRole()) { // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE if (logger.isInfoEnabled()) { logger.info("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } else if (!beanDefinition.equals(existingDefinition)) { if (logger.isDebugEnabled()) { logger.debug("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } else { if (logger.isTraceEnabled()) { logger.trace("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } this.beanDefinitionMap.put(beanName, beanDefinition); } else { if (hasBeanCreationStarted()) { // Cannot modify startup-time collection elements anymore (for stable iteration) synchronized (this.beanDefinitionMap) { this.beanDefinitionMap.put(beanName, beanDefinition); List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1); updatedDefinitions.addAll(this.beanDefinitionNames); updatedDefinitions.add(beanName); this.beanDefinitionNames = updatedDefinitions; removeManualSingletonName(beanName); } } // This is the normal process of registering BeanDefinition. While saving the name of the Bean into BeanDefinitionNames, // Take beanName as the key of the Map and beanDefinition as the value and store it in the beanDefinitionMap held by the IOC container else { // Still in startup registration phase this.beanDefinitionMap.put(beanName, beanDefinition); this.beanDefinitionNames.add(beanName); removeManualSingletonName(beanName); } this.frozenBeanDefinitionNames = null; } if (existingDefinition != null || containsSingleton(beanName)) { resetBeanDefinition(beanName); } else if (isConfigurationFrozen()) { clearByTypeCache(); } } </pre>
Once the BeanDefinition is registered, the initialization of the loC container is completed. At this time, the configuration information of the whole Bean has been established in the used IoC container DefaultListableBeanFactory, and these beandefinitions can be used by the container. They are retrieved and used in beanDefinitionMap. The function of the container is to process and maintain this information. This information is the basis for the container to establish dependency inversion. With these basic data, let's see how dependency injection is completed in the IoC container.
3.4 initialization summary
**Summary: * * start with the refresh() method in the filesystemxlapplicationcontext. The specific implementation of this method is in the AbstractApplicationContext. In the refresh() method, first let the subclass AbstractRefreshableApplicationContext create a DefaultListableBeanFactory container in the refreshBeanFactory() method in the refreshBeanFactory() method. It is an IOC default container, Then continue to execute in this method and call loadBeanDefinitions() to load BeanDefinition. This method is an interface method. The specific implementation classes vary from the source of BeanDefinition. If it is a loaded XML file, XMLBeanDefinitionReader loads BeanDefinition. To load BeanDefinition, you need to locate the Resource first, load it to all resources through the loadbeandefinitions () method in the AbstractBeanDefinitionReader class, and store it through the DefaultResourceLoader structure. After finding the Resource location, start loading the BeanDefinition. Through the Resource, you can use IO to operate XML. First, the XML is parsed into a Document tree, and then parsed by the documentReader according to the rules of SpringBean. The specific parsing operations are parsed by the methods in the BeanDefinitionParserDelegate class, which has many parsing methods, Including child elements of Property, etc. After parsing, a BeanDefinitionHolder is returned. This structure includes all the parsed beandefinitions. So far, the parsing process is completed. The registration operation starts randomly. The registration operation is relatively simple. The main process is completed by DefaultListableBeanFactory.
3.5 work after initialization
As can be seen from the following code, after the container initialization process is completed and the container is obtained, there are other operations, such as setting the post processor of BeanFactory, setting the post processor of Bean, initializing the event mechanism of context, etc.
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh"); // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. // Call your own method, first let the subclass start the container, and then return to the container 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. // Set the post processor of BeanFactory postProcessBeanFactory(beanFactory); StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process"); // Invoke factory processors registered as beans in the context. //Call the post processors of BeanFactory, which are registered with the container in the Bean definition invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. //Register the postprocessing of Bean, and call it in the process of Bean creation. registerBeanPostProcessors(beanFactory); beanPostProcess.end(); // Initialize message source for this context. //Initializes the message source in the context initMessageSource(); // Initialize event multicaster for this context. //Initializes the event mechanism in the context initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. //Initialize other special beans onRefresh(); // Check for listener beans and register them. //Check the listening beans and register them with the container registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. //Instantiate all (non lazy init) singletons finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. //Publish container events End the Refresh process 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. //In order to prevent Bean resource occupation, in exception handling, destroy the singleton Bean generated in the previous process destroyBeans(); // Reset 'active' flag. //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(); contextRefresh.end(); } }