IOC chapter of Spring source code analysis

Learning focus

Don't think back! Don't think back! Don't think about reciting!: Gradually understand along the process and ideas.

Unity of knowledge and practice! Unity of knowledge and practice! Unity of knowledge and practice!: After learning each stage of knowledge, be sure to document output or practice.

OK, finish the point, let's start!

What is IOC?

IOC (Inversion Of Control): it is to hand over the code that needs to be manually created and relied on in the project to the container created by Spring for implementation and unified management, so as to realize the Inversion Of Control.

According to this explanation, we must think that there must be many object instances in this container (Spring defines BeanDefinition objects to wrap native objects), so we can get them directly when we need them.

Here's a question: what exactly does the container for storing objects look like? Or what data structure is used to define the storage, Map or List or others?

preparation

First of all, we already know that the container stores the objects created by Spring for us, so we certainly need a description to let the container know the relationship between the objects to be created and the objects, that is, the configuration metadata.

So before we really understand the container initialization process, we need to do some preparatory work.

Step 1: configure metadata

There are three ways to configure metadata in Spring:

  • XML: the traditional configuration method is simple, intuitive and easy to manage
<beans>
    <bean id="" class=""/> 
</beans>
  • Java code: define bean s outside the application class
@Configuration
public class ConfigA {

    @Bean
    public A a() {
        return new A();
    }
}
  • Note: the configuration is simplified, but the dispersion is difficult to control. Use class level annotations @ Controller, @ Service, @ Component, etc., and class internal annotations @ Autowire, @ Value, @ Resource, etc.

At present, the most commonly used method in daily development may be annotation based to simplify configuration. Of course, Spring allows these three methods to be mixed together, which depends on the actual development situation.

Step 2: configure resolution

After configuring the metadata, you need to parse the configuration. Obviously, the syntax and data description of different configuration methods are different.

Therefore, Spring encapsulates parsers with different configuration methods, such as:

  • XML: use ClasspathXmlApplicationContext as the entry and XmlBeanDefinitionReader to parse the metadata of the Bean
  • Java code and annotation: use AnnotationConfigApplicationContext as the entry, and AnnotatedBeanDefinitionReader to parse the metadata of the Bean

Step 3: key classes

Here, let's familiarize ourselves with several key classes in IOC in advance, namely:

  • BeanFactory: it is the top-level interface. See the name and meaning. It is a typical factory mode.Through its source code, we can find that BeanFactory only defines the basic behavior of IOC container and obtains beans and various properties of beans. Next, let's look at its subclass diagram

    It has three important subclasses below: listablebeanfactory < < can enumerate all bean instances >, hierarchalbeanfactory < < can configure the bean's parent class with inheritance relationship >, and autowirecapablebeanfactory < < provide the implementation of automatic assembly of beans >. At the same time, it can be found that DefaultListableBeanFactory implements all interfaces, which means that it has all the above "big brother" functions and its own extension functions (crazy and familiar), so it is also the default implementation class.

  • ApplicationContext: the BeanFactory factory above helps us get objects. How the factory generates objects depends on it. It provides specific IOC implementation, GenericApplicationContext, ClasspathXmlApplicationContext, etc. it also provides the following additional services:

    • Support information source, internationalization < implement MessageSource interface >

    • Access resources < implement ResourcePatternResolver interface >

    • Support application events < implement ApplicationEventPublisher interface >

  • BeanDefinition: the description and definition of Bean objects in Spring. It can be understood that each Bean object in the container is saved in BeanDefinition.

  • BeanDefinitionReader: a parser that configures the metadata of a Bean, such as XmlBeanDefinitionReader and AnnotatedBeanDefinitionReader

Initialization of IOC (from scratch)

TIPS

It is very difficult to read the source code of a mature framework. Almost 100% will be "carsick", In particular, don't be curious (obsessive-compulsive disorder) to understand the call of each method (the call chain will make you feel like infinite recursion), and the details of each line of code (believe me, it will be forgotten after reading). That will really kill the cat! We must explore the architecture, not the details, and gradually check some details according to the actual situation after understanding the whole idea process.

Reading articles is the same. If you don't understand knowledge articles, read the outline first. If you don't, read and refine the outline first. After you have an understanding of the whole architecture, look at the detailed analysis under each outline.

Now that we understand the essence of the container and have made some preparations, we will start to explore how Spring completes the initialization of the container?

From scratch, we need to start with the entry, that is, ClasspathXmlApplicationContext and AnnotationConfigApplicationContext. The reason has been mentioned in the configuration analysis of the preparatory work just now.

Let's explore it respectively.

Initialization of IOC container based on XML

1. Positioning

First, let's think about the previous preparations. After the Bean resource file is defined, the < configuration metadata >, and then we will find a way to parse the defined data and register it in the container. The logic is correct, but before parsing, we need to consider how to find the parsing file, that is, the configuration path. Therefore, in the first step (in fact, this is the second step. The first step is to configure metadata, which has been implemented by defau lt here). We need to resolve the path, that is, location.

First, we will start through the main() method:

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); 

Let's briefly understand the class diagram of ClassPathXmlApplicationContext in advance:

Next, click to see the source code of the actually called ClassPathXmlApplicationContext:

	public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
		// Set resource loader for Bean
		super(parent);
		// Resolve and set the path of the Bean's resource file
		setConfigLocations(configLocations);
		if (refresh) {
			// Load Bean's configuration resources
			refresh();
		}
	}

1. super(parent): call the construction method of the parent class to set the Bean's resource loader. Click to the end to know that the getResourcePatternResolver() method of the parent class AbstractApplicationcontext is called,

	protected ResourcePatternResolver getResourcePatternResolver() {
		//Because AbstractApplicationContext inherits DefaultResourceLoader, it is also a resource loader, and its getResource(String location) method is used to load resources
        return new PathMatchingResourcePatternResolver(this);
	}

2. setConfigLocations(configLocations): call the specific implementation of the parent class AbstractRefreshableConfigApplicationContext. You can understand the path of the resource file that parses and processes the Bean, and this path supports the incoming string array

	public void setConfigLocations(@Nullable String... locations) {
		if (locations != null) {
			Assert.noNullElements(locations, "Config locations must not be null");
			this.configLocations = new String[locations.length];
			for (int i = 0; i < locations.length; i++) {
				// Method to parse a string into a path
				this.configLocations[i] = resolvePath(locations[i]).trim();
			}
		}
		else {
			this.configLocations = null;
		}
	}

3. refresh(): load the configuration resources of the Bean. Obviously, the specific implementation of this method is what we need to focus on, "Let we look look look" Click in and find that the refresh() method of the parent class AbstractApplicationContext is called. Here, it is found that refresh() is actually a template method. Let's take a look at the specific logical processing,

	@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			//1. Call the method that the container is ready to refresh, obtain the current time of the container, and set the synchronization ID for the container
			prepareRefresh();

			//2. Tell the subclass to start the refreshBeanFactory() method, and the loading of the Bean definition resource file starts from the refreshBeanFactory() method of the subclass
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			//3. Configure container features for BeanFactory, such as class loader, event handler, and so on
			prepareBeanFactory(beanFactory);

			try {
				//4. Specify a special BeanPost event handler for some subclasses of the container
				postProcessBeanFactory(beanFactory);

				//5. Call all registered beanfactoryprocessor beans
				invokeBeanFactoryPostProcessors(beanFactory);

				//6. Register the BeanPost event handler for BeanFactory BeanPostProcessor is a Bean post processor, which is used to listen for events triggered by the container
				registerBeanPostProcessors(beanFactory);

				//7. Initialize the information source, which is related to internationalization
				initMessageSource();

				//8. Initialize the container event propagator
				initApplicationEventMulticaster();

				//9. Call some special Bean initialization methods of subclasses
				onRefresh();

				//10. Register event listeners for event propagators
				registerListeners();

				//11. Initialize all remaining singleton beans
				finishBeanFactoryInitialization(beanFactory);

				//12. Initialize the container's lifecycle event handler and publish the container's lifecycle events
				finishRefresh();
			}
			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				//13. Destroy the created Bean
				destroyBeans();

				//14. Cancel the refresh operation and reset the synchronization ID of the container.
				cancelRefresh(ex);

				throw ex;
			}
			finally {
				//15. Reset public cache
				resetCommonCaches();
			}
		}
	}

We mainly look at the obtainFreshBeanFactory() method, which implements the Bean loading registration. The subsequent code is the initialization information source of the container and some events in the life cycle. Here, the Bean resource file is located. Next, let's look at the specific code implementation loaded.

2. Loading

After setting the path of the configuration file, we also need to create a container before parsing the metadata, so that the parsed object can have a place to store the call. Let's take a look at the specific implementation of obtainFreshBeanFactory(),

	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		refreshBeanFactory();
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		if (logger.isDebugEnabled()) {
			logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
		}
		return beanFactory;
	}

Here, the refreshBeanFactory() method uses the delegation design pattern and defines the abstract method in the parent class, but the specific implementation calls the method of the child class. Click in to see that the method actually called is overridden by AbstractRefreshableApplicationContext,

	@Override
	protected final void refreshBeanFactory() throws BeansException {
		//If there is already a container, destroy the bean s in the container and close the container
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			//Create IOC container
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			//Customize the container, such as setting to allow Bean override and Bean circular reference
			customizeBeanFactory(beanFactory);
			//Call the method defined by the load Bean. Here, a delegation mode is used. Only the abstract loadBeanDefinitions method is defined in the current class, and the specific implementation calls the subclass container
			loadBeanDefinitions(beanFactory);
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}

From the implementation, we can know whether BeanFactory already exists before creating the container. If so, destroy all beans in the container < < clear() > the HashMap storing beans, and close BeanFactory < < set to null >., On the contrary, if it does not exist, create the IOC container DefaultListableBeanFactory. Is it very familiar here? Yes, it is the key class that we are crazy familiar with before, and the default implementation class that is more powerful than all "big brothers", In fact, it is the IOC container (tips: I may feel dizzy here. They are all containers. It seems that they are all different. Here, there are many containers in Spring. In addition to the subclasses of BeanFactory, we can call the inherited or implemented subclasses under ApplicationContext, such as those in the ClassPathXmlApplicationContext class diagram above, containers, Needless to say, the inherited subclasses have the attribute private ApplicationContext parent; in the implementation class;, So they can all be called containers). Before that, we left a question: what is the container for storing objects like? Here we know the specific appearance of the container, but in fact, the container that really stores Bean objects, or the data structure called the container, continue to explore, and we will know later.

Next, look at the loadBeanDefinitions() method, which is also an abstract method. The real implementation is implemented by its subclass AbstractXmlApplicationContext. The specific implementation is as follows:

	@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		//Create a Bean configuration data parser and set it into the container through callback
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
		//Set resource environment and resource loader
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		//Set up SAX xml parser
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
		//Enables the verification mechanism when parsing Xml resource files
		initBeanDefinitionReader(beanDefinitionReader);
		//Load Bean
		loadBeanDefinitions(beanDefinitionReader);
	}

Next, continue to explore the loadBeanDefinitions() method,

	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
		//Get the location of Bean configuration data
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
			reader.loadBeanDefinitions(configResources);
		}
		//When the location of obtaining Bean configuration data in the subclass is empty, obtain the resource set by setConfigLocations method in FileSystemXmlApplicationContext constructor
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			reader.loadBeanDefinitions(configLocations);
		}
	}

	@Nullable
	protected Resource[] getConfigResources() {
		return null;
	}

Here, getConfigResources() also uses the delegate mode, which is really implemented in ClassPathXmlApplicationContext, reader Loadbean definitions (configresources) actually calls the parent class AbstractBeanDefinitionReader to parse the Bean's configuration data,

	@Override
	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;
	}
	@Override
	public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
		//The parsed XML resources are specially encoded
		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<>(4);
			this.resourcesCurrentlyBeingLoaded.set(currentResources);
		}
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
		try {
			//Convert resource file to IO stream of InputStream
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				//Get the parsing source of XML
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				//Specific analysis process
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
			finally {
				//Close IO stream
				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();
			}
		}
	}

We continue along the reader The loadBeanDefinitions (configResources) method looks down and finds that the last loadBeanDefinitions is called the loadBeanDefinitions() method, which is the xml parser that we are going to talk about. The concrete parsing process is in the doLoadBeanDefinitions() method.

	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
			//Converting XML files to DOM objects
			Document doc = doLoadDocument(inputSource, resource);
			//Parsing Bean data
			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);
		}
	}

Through analysis, the configuration data of the Bean is loaded. Finally, the xml is converted into a Document object and then parsed. We first look at the doLoadDocument() method and know that the loadDocument() method of DefaultDocumentLoder will be called,

	@Override
	public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
			ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
		//Create a file parser factory
		DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
		if (logger.isDebugEnabled()) {
			logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
		}
		//Create document parser
		DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
		//Parsing resource data
		return builder.parse(inputSource);
	}

The above parsing process calls the JAXP standard of the Java EE standard for processing. Next, we continue to analyze how to parse the Bean object managed in the IOC container and register it in the container after converting the bit to a Document object. Let's continue to look at the registerBeanDefinition() method,

	public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		//Create BeanDefinitionDocumentReader
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		//Get the number of registered beans in the container
		int countBefore = getRegistry().getBeanDefinitionCount();
		//Specific analysis process
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		//Returns the number of parsed beans
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}

In the introduction to the key classes in the previous preparatory work, we know that the actual storage in the IOC container is the BeanDefinition object, which encapsulates the Bean object, so here we first create the BeanDefinitionDocumentReader to complete the parsing and encapsulation of the Bean object in xml. The main parsing process is in documentreader It is completed in registerbeandefinitions (DOC, createreadercontext (resource)). Let's continue,

	@Override
	public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		this.readerContext = readerContext;
		logger.debug("Loading bean definitions");
		//Get the root element of the Document
		Element root = doc.getDocumentElement();
		doRegisterBeanDefinitions(root);
	}

	protected void doRegisterBeanDefinitions(Element root) {
		//The specific parsing process is implemented by BeanDefinitionParserDelegate,
		//Various elements of the Spring Bean definition XML file are defined in BeanDefinitionParserDelegate
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);
		if (this.delegate.isDefaultNamespace(root)) {
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			if (StringUtils.hasText(profileSpec)) {
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
					if (logger.isInfoEnabled()) {
						logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
								"] not matching: " + getReaderContext().getResource());
					}
					return;
				}
			}
		}
		//Preprocess xml and perform custom parsing before parsing Bean definitions to enhance the scalability of the parsing process
		preProcessXml(root);
		//The Document object defined by the Bean starts from the root element of the Document
		parseBeanDefinitions(root, this.delegate);
		//After the Bean definition is parsed, custom parsing is performed to increase the scalability of the parsing process
		postProcessXml(root);
		this.delegate = parent;
	}

We found that registerBeanDefinitions() is actually implemented by the implementation class DefaultBeanDefinitionDocumentReader of BeanDefinitionDocumentReader. Through source code analysis, we can see that BeanDefinitionParserDelegate is a key class. Click in and find that it defines various elements in XML files, so we know that more specific parsing depends on it, Select preProcessXml(root); And postProcessXml(root); We can know the pre - and post-processing XML by name, and click it to find that it is a reserved empty implementation, mainly to enhance the scalability in the parsing process. We mainly look at the implementation of parseBeanDefinitions(),

//Use Spring's Bean rules to define the Document object of the Bean from the root element of the Document
	protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		//Is the Document object the default XML namespace
		if (delegate.isDefaultNamespace(root)) {
			//All child nodes of the root element of the Document object
			NodeList nl = root.getChildNodes();
			for (int i = 0; i < nl.getLength(); i++) {
				Node node = nl.item(i);
				//Is the Document node an element node of XML
				if (node instanceof Element) {
					Element ele = (Element) node;
					//Is the element node of Document the default XML namespace
					if (delegate.isDefaultNamespace(ele)) {
						//Default element node resolution
						parseDefaultElement(ele, delegate);
					}
					else {
						//Custom element node resolution
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			//Custom element node resolution
			delegate.parseCustomElement(root);
		}
	}

	private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		// < import > Import resolution
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			importBeanDefinitionResource(ele);
		}
		// < alias > alias resolution
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			processAliasRegistration(ele);
		}
		// < bean > bean rule parsing
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			processBeanDefinition(ele, delegate);
		}
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
             // recursion
			doRegisterBeanDefinitions(ele);
		}
	}

We can see that Xml data can be parsed from here. At the same time, there are two parsing methods, namely parseDefaultElement() Default element parsing and parseCustomElement() custom element parsing. Customization requires additional implementation. We mainly look at the process of Default element parsing and find that there are three different parsing processes, namely import, alias For object parsing, let's look at their implementation respectively,

	protected void importBeanDefinitionResource(Element ele) {
		//Gets the location attribute of the configured import element
		String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
		//If location is empty, it will end directly
		if (!StringUtils.hasText(location)) {
			getReaderContext().error("Resource location must not be empty", ele);
			return;
		}
		//Resolve the location attribute value using the system variable value
		location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
		Set<Resource> actualResources = new LinkedHashSet<>(4);
		//Identifies whether the configured location is an absolute path
		boolean absoluteLocation = false;
		try {
			absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
		}
		catch (URISyntaxException ex) {

		}
		if (absoluteLocation) {
			try {
				//The parser loads the Bean configuration data of the location path
				int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
				if (logger.isDebugEnabled()) {
					logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]");
				}
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error(
						"Failed to import bean definitions from URL location [" + location + "]", ele, ex);
			}
		}
		else {
			//The configured location is a relative path
			try {
				int importCount;
				//Convert location to relative path
				Resource relativeResource = getReaderContext().getResource().createRelative(location);
				//Does the path resource exist
				if (relativeResource.exists()) {
					//The parser loads the Bean configuration data of the location path
					importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
					actualResources.add(relativeResource);
				}
				else {
					//Get the basic path of the parser
					String baseLocation = getReaderContext().getResource().getURL().toString();
					//The parser loads the Bean configuration data of the location path
					importCount = getReaderContext().getReader().loadBeanDefinitions(
							StringUtils.applyRelativePath(baseLocation, location), actualResources);
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]");
				}
			}
			catch (IOException ex) {
				getReaderContext().error("Failed to resolve current resource location", ele, ex);
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]",
						ele, ex);
			}
		}
		Resource[] actResArray = actualResources.toArray(new Resource[actualResources.size()]);
		//After parsing the < import > element, send the import resource processing completion event
		getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
	}

Here, the import elements are mainly parsed, and the Bean configuration resources are loaded into the IOC container according to the configured import path.

	protected void processAliasRegistration(Element ele) {
		//Get the property value of name
		String name = ele.getAttribute(NAME_ATTRIBUTE);
		//Get the attribute value of alias
		String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
		boolean valid = true;
		//The name attribute value is empty
		if (!StringUtils.hasText(name)) {
			getReaderContext().error("Name must not be empty", ele);
			valid = false;
		}
		//The alias attribute value is null
		if (!StringUtils.hasText(alias)) {
			getReaderContext().error("Alias must not be empty", ele);
			valid = false;
		}
		if (valid) {
			try {
				//Register alias with container
	getReaderContext().getRegistry().registerAlias(name, alias);
			}
			catch (Exception ex) {
				getReaderContext().error("Failed to register alias '" + alias +
						"' for bean with name '" + name + "'", ele, ex);
			}
			//After resolving the < alias > element, send the alias processing completion event
			getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
		}
	}

Here, the alias element is mainly resolved and the alias is registered in the IOC container for the Bean.

	protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
         // Encapsulation of BeanDefinition
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				// Register the Bean into the IOC container
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to register bean definition with name '" +
						bdHolder.getBeanName() + "'", ele, ex);
			}
			//After the registration is completed, the registration completion event is sent
			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
		}
	}

Here, we mainly resolve the object elements and register the Bean's encapsulated object BeanDefinition into the IOC container. We found that the object of BeanDefinitionHolder, which is the encapsulation of BeanDefinition, registers and passes parameters to the container. We can see how it is parsed and converted to BeanDefinitionHolder,

	@Nullable
	public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
		return parseBeanDefinitionElement(ele, null);
	}

	@Nullable
	public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
		//Get id attribute value
		String id = ele.getAttribute(ID_ATTRIBUTE);
		//Get the value of the name attribute
		String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
		//Get alias attribute value
		List<String> aliases = new ArrayList<>();
		//Adds all the name attribute values to the alias collection
		if (StringUtils.hasLength(nameAttr)) {
			String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			aliases.addAll(Arrays.asList(nameArr));
		}
		String beanName = id;
		//If the id property is not configured, beanName is assigned the first value in the alias
		if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
			beanName = aliases.remove(0);
			if (logger.isDebugEnabled()) {
				logger.debug("No XML 'id' specified - using '" + beanName +
						"' as bean name and " + aliases + " as aliases");
			}
		}
		if (containingBean == null) {
			//Check whether the configured id, name or alias are duplicate
			checkNameUniqueness(beanName, aliases, ele);
		}
		//Where the Bean definition configured in the < Bean > element is parsed in detail
		AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
		if (beanDefinition != null) {
			if (!StringUtils.hasText(beanName)) {
				try {
					if (containingBean != null) {
						//Generate a unique beanName for the resolved Bean
						beanName = BeanDefinitionReaderUtils.generateBeanName(
								beanDefinition, this.readerContext.getRegistry(), true);
					}
					else {
						//Generate a unique beanName for the resolved Bean
						beanName = this.readerContext.generateBeanName(beanDefinition);
						String beanClassName = beanDefinition.getBeanClassName();
						if (beanClassName != null &&
								beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
								!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
							aliases.add(beanClassName);
						}
					}
					if (logger.isDebugEnabled()) {
						logger.debug("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 code mainly deals with resolving the id, name and alias attributes of the element. Looking down the parseBeanDefinitionElement() method, you will know that some configurations such as meta, qualifier and property will be resolved. We can see how the attributes of the Bean are set in the resolution. We won't take a closer look here.

Here, the configuration has been loaded into memory, that is, the Bean object has been loaded, but the more important actions have not been done. We need to register the prepared BeanDefinition object into the container.

3. Registration

From the initial configuration metadata to this step, do you feel that everything is ready and only registration is needed.

Let's look at the registerBeanDefinition() method,

	//Register the resolved BeanDefinitionHold into the container
	public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {
		String beanName = definitionHolder.getBeanName();
		//Register BeanDefinition with IOC container
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
		//If the resolved BeanDefinition has an alias, register the alias with the container
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias);
			}
		}
	}

In the end, we found that the registration function is really completed by our familiar boss. The DefaultListableBeanFactory implemented by default is a registration policy assigned to Spring. Let's uncover the mystery of the real container,

//Register resolved BeanDefiniton with IOC container
	@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");
		//Verify resolved BeanDefiniton
		if (beanDefinition instanceof AbstractBeanDefinition) {
			try {
				((AbstractBeanDefinition) beanDefinition).validate();
			}
			catch (BeanDefinitionValidationException ex) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Validation of bean definition failed", ex);
			}
		}
		BeanDefinition oldBeanDefinition;
		oldBeanDefinition = this.beanDefinitionMap.get(beanName);
		if (oldBeanDefinition != null) {
			if (!isAllowBeanDefinitionOverriding()) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
						"': There is already [" + oldBeanDefinition + "] bound.");
			}
			else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
				// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
				if (this.logger.isWarnEnabled()) {
					this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
							"' with a framework-generated bean definition: replacing [" +
							oldBeanDefinition + "] with [" + beanDefinition + "]");
				}
			}
			else if (!beanDefinition.equals(oldBeanDefinition)) {
				if (this.logger.isInfoEnabled()) {
					this.logger.info("Overriding bean definition for bean '" + beanName +
							"' with a different definition: replacing [" + oldBeanDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			else {
				if (this.logger.isDebugEnabled()) {
					this.logger.debug("Overriding bean definition for bean '" + beanName +
							"' with an equivalent definition: replacing [" + oldBeanDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		else {
			if (hasBeanCreationStarted()) {
				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;
					if (this.manualSingletonNames.contains(beanName)) {
						Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
						updatedSingletons.remove(beanName);
						this.manualSingletonNames = updatedSingletons;
					}
				}
			}
			else {
				this.beanDefinitionMap.put(beanName, beanDefinition);
				this.beanDefinitionNames.add(beanName);
				this.manualSingletonNames.remove(beanName);
			}
			this.frozenBeanDefinitionNames = null;
		}
		//Check whether a BeanDefinition with the same name has been registered in the IOC container
		if (oldBeanDefinition != null || containsSingleton(beanName)) {
			//Reset the cache of all registered beandefinitions
			resetBeanDefinition(beanName);
		}
	}

Looking at the whole method, did you notice this beanDefinitionMap. put(beanName, beanDefinition); This code, and beandefinition is the Bean object that we have prepared for a long time. We know that beandefinitionmap is a ConcurrentHashMap, a thread safe Map. We recall our initial question, "what data structure is used to define the storage, Map or List or others?", Here is the answer. Beandefinitionmap is the real container. At the same time, we can also see that other ConcurrentHashMap, ArrayList and LinkedHashSet are defined in the class, as well as those inherited from the parent class. They store the relevant information of different objects in the container. When you contact and use them, you will naturally understand their corresponding uses.

	/** Map from dependency type to corresponding autowired value */
	private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap<>(16);

	/** Map of singleton and non-singleton bean names, keyed by dependency type */
	private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<>(64);

	/** Map of singleton-only bean names, keyed by dependency type */
	private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap<>(64);

	/** List of bean definition names, in registration order */
	private volatile List<String> beanDefinitionNames = new ArrayList<>(256);

	/** List of names of manually registered singletons, in registration order */
	private volatile Set<String> manualSingletonNames = new LinkedHashSet<>(16);

At the same time, we see a synchronized modified code, because thread synchronization is required in the process of registering objects to ensure data consistency.

Here, the initialization process of IOC container based on Xml has been completed. After basically looking at the complete process, we also combed the initialization sequence diagram and flow chart to deepen the understanding of the whole container initialization process. At the same time, we know that the initialization of IOC container mainly consists of three steps: positioning, loading and registration.

Next, we will explore another annotation based initialization method, which is basically the same. I believe it will be easier to understand.

Initialization of IOC container based on Annotation

Referring to the Spring version data, we know that the Annotation based configuration is introduced in the version after 2.0, which is mainly used to simplify the Bean configuration and improve the development efficiency. Up to now, SpringBoot has become popular. It also basically realizes zero configuration based on annotations. In actual development and use, it can only be a cool word. In the previous preparatory work, we learned that annotations can be divided into two types: class level and class internal annotations, and the Spring container has different processing strategies according to these two different methods. The class level Annotation scans and reads the definition class of the Annotation Bean according to the Annotation filtering rules, and the internal Annotation of the class parses the internal Annotation of the Bean through the post Annotation processor of the Bean.

We already know that the entry is AnnotationConfigApplicationContext. In fact, it also has a brother called AnnotationConfigWebApplicationContext. It is the Web version of AnnotationConfigApplicationContext. Their functions and usage are basically the same. Next, we will take AnnotationConfigApplicationContext as an example to explore the initialization process of IOC container.

1. Entrance

First, we will start through the main() method:

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); 

perhaps

ApplicationContext applicationContext = new AnnotationConfigApplicationContext("Package path"); 

Let's look at the source code of AnnotationConfigApplicationContext:

	// Directly resolve the class of configuration annotation
	public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
		this();
		register(annotatedClasses);
		refresh();
	}
	// Scan all classes under the specified package path
	public AnnotationConfigApplicationContext(String... basePackages) {
		this();
		scan(basePackages);
		refresh();
	}

From the above source code, we can see that there are two processing methods:

  • Directly resolve the class of configuration annotation
  • Scan all classes under the specified package path

At the same time, we can see that the refresh() method is called. It is the same method as the refresh() method in the previous XML parsing. The specific implementation is the same. It is to load the Bean's configuration resources. So here we mainly focus on register (annotated classes); And scan (base packages); These two methods, they are the unique implementation. Next, we will explore the specific implementation of these two processing methods.

2. Directly resolve the class of configuration annotation

Before we start, let's continue to look at the source code of the annotationconfigapplicationcontext we haven't finished reading just now,

	// Parser
	private final AnnotatedBeanDefinitionReader reader;
	// Scanner
	private final ClassPathBeanDefinitionScanner scanner;

	public AnnotationConfigApplicationContext() {
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}

OK, here we know that the annotationconfigapplicationcontext initializes the AnnotatedBeanDefinitionReader object when it is created, and we know that this object is the parser for parsing annotation beans, which will be used later.

Let's continue to look at the register() method implementation,

	public void register(Class<?>... annotatedClasses) {
		Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified");
		this.reader.register(annotatedClasses);
	}

We know that the actual parsing is the responsibility of the reader parser just created. Let's go to AnnotatedBeanDefinitionReader to see its specific implementation process,

	//It supports parsing multiple annotated classes
	public void register(Class<?>... annotatedClasses) {
		for (Class<?> annotatedClass : annotatedClasses) {
			registerBean(annotatedClass);
		}
	}
	
	public void registerBean(Class<?> annotatedClass) {
		doRegisterBean(annotatedClass, null, null, null);
	}

	<T> void doRegisterBean(Class<T> annotatedClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name,
			@Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {
		//The subclass under BeanDefinition encapsulates the Bean object
		AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
		if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
			return;
		}
		abd.setInstanceSupplier(instanceSupplier);
		//Resolve the scope of the Bean, such as @ Scope("prototype"), prototype type@ Scope("singleton"), singleton type
		ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
		//Set scope
		abd.setScope(scopeMetadata.getScopeName());
		//Generate unique beanName
		String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
		//Parsing and processing general annotations
		AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
		//If qualifiers exist
		if (qualifiers != null) {
			for (Class<? extends Annotation> qualifier : qualifiers) {
				//If the @ Primary annotation is configured, the Bean is the priority for automatic assembly
				if (Primary.class == qualifier) {
					abd.setPrimary(true);
				}
				//If @ Lazy annotation is configured, Bean initialization will be delayed, otherwise pre instantiation will be performed
				else if (Lazy.class == qualifier) {
					abd.setLazyInit(true);
				}
				//When assembling automatically, the Bean specified by the name assembly qualifier
				else {
					abd.addQualifier(new AutowireCandidateQualifier(qualifier));
				}
			}
		}
		for (BeanDefinitionCustomizer customizer : definitionCustomizers) {
			customizer.customize(abd);
		}
		//Encapsulation of BeanDefinition
		BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
		//Create the corresponding proxy object according to the scope
		definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
		//Register Bean object
		BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
	}

Let's briefly analyze the above source code. First, we will resolve the scope of the Bean through the resolveScopeMetadata() method. The default value is scopedproxymode No, which means that the proxy class will not be created later; Then, we will process the general annotations in the class through processCommonDefinitionAnnotations(), and resolve @ Lazy, @ prime, @ DependsOn, @ Role, @ Description annotation classes. If the @ DependsOn annotation is included, the container will ensure that the dependent Bean will be instantiated before instantiating the Bean; Next, create the proxy class applyscope dproxymode () according to the scope, which is mainly used in AOP aspect; Finally, register beans to the container through the registerBeanDefinition() method, and we are also familiar with the BeanDefinitionHolder object. Like in the initialization of XML container, we finally add BeanDefinition to ConcurrentHashMap.

This completes the direct parsing process of the configuration class. We also sorted out the timing diagram of the initialization process

3. Scan all classes under the specified package path

Next, let's explore the analysis of scanning the specified package path. Let's take a look at scan (base packages); The specific implementation of the method,

	public void scan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		this.scanner.scan(basePackages);
	}

Here is this Scanner is the ClassPathBeanDefinitionScanner scanner initialized when annotationconfigapplicationcontext is created. The specific implementation is completed by it,

	public int scan(String... basePackages) {
		//Gets the number of registered beans in the container
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
		//Scan specified packages
		doScan(basePackages);
		//Register annotation configuration processor
		if (this.includeAnnotationConfig) {
			AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
		}
		//Returns the number of registered beans
		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
	}

	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		//Traversal scan all specified packages
		for (String basePackage : basePackages) {
			//Call the method of the parent class ClassPathScanningCandidateComponentProvider
			//Scan the given classpath to obtain the qualified Bean definition
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			//Traverse the scanned Bean
			for (BeanDefinition candidate : candidates) {
				//Gets the scope of the Bean
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				//Set scope
				candidate.setScope(scopeMetadata.getScopeName());
				//Generate unique Bean name
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) {
                     //Set the default value of the Bean's properties
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					//Parsing and processing general annotations
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				//Check whether the specified Bean needs to be registered in the container or conflicts in the container according to the Bean name
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					//Create the corresponding proxy object according to the scope
					definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					//Register Bean object
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;

Through the source code, we know that the main processing logic is implemented in the doScan() method. We scan the specified package path to obtain all annotation classes under the package and return a Set set for temporary storage. Let's see how the findCandidateComponents() method parses and processes,

	public Set<BeanDefinition> findCandidateComponents(String basePackage) {
         // Judge whether the filter rule contains @ Component annotation. In fact, @ Repository, @ Service, @ Controller, etc. contain @ component
		if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
			return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
		}
		else {
			return scanCandidateComponents(basePackage);
		}
	}

	private Set<BeanDefinition> addCandidateComponentsFromIndex(CandidateComponentsIndex index, String basePackage) {
		Set<BeanDefinition> candidates = new LinkedHashSet<>();
		try {
			Set<String> types = new HashSet<>();
			for (TypeFilter filter : this.includeFilters) {
				String stereotype = extractStereotype(filter);
				if (stereotype == null) {
					throw new IllegalArgumentException("Failed to extract stereotype from "+ filter);
				}
				types.addAll(index.getCandidateTypes(basePackage, stereotype));
			}
			boolean traceEnabled = logger.isTraceEnabled();
			boolean debugEnabled = logger.isDebugEnabled();
			for (String type : types) {
				//Get metadata parser
				MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(type);
				//Judge whether the configured filtering rules are met
				if (isCandidateComponent(metadataReader)) {
					AnnotatedGenericBeanDefinition sbd = new AnnotatedGenericBeanDefinition(
							metadataReader.getAnnotationMetadata());
					if (isCandidateComponent(sbd)) {
						if (debugEnabled) {
							logger.debug("Using candidate component class from index: " + type);
						}
						candidates.add(sbd);
					}
					else {
						if (debugEnabled) {
							logger.debug("Ignored because not a concrete top-level class: " + type);
						}
					}
				}
				else {
					if (traceEnabled) {
						logger.trace("Ignored because matching an exclude filter: " + type);
					}
				}
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
		}
		return candidates;
	}

The code logic here is easy to understand, which is to judge whether the scanned class meets the parsing rules of annotation class. If so, it will be added to the Set collection and finally returned to the upper layer. In fact, this parsing rule is the default annotation rule of Spring initialized in the construction method of ClassPathBeanDefinitionScanner.

Deja vu as like as two peas, we also go back to the next level, and look at the code after findCandidateComponents(basePackage). We find that the next step is to traverse Bean in the Set set, and whether the logic hungry code is familiar or not is exactly the same as that in the class that we directly parsed and configured annotation. Finally, the registerBeanDefinition() method is called to register the bean object into the IOC container.

This completes the registration process of scanning all classes under the specified package path. We also sorted out the timing diagram of the initialization process

summary

After the source code analysis of the above long article and the sorting of sequence diagram and flow chart, we can know the initialization process of IOC container in detail, but we don't study some processing details too deeply. Those who are interested can find another opportunity to have a look. Finally, let's sort out the initialization process of the whole IOC from the level of top-level design:

  1. Locate the resource file location from classpath, file system, URL, etc. through ResourceLoader;
  2. The files for configuring Bean data will be abstracted into resources for processing;
  3. The container parses the Resource through the BeanDefinitionReader, and the actual processing process is entrusted to BeanDefinitionParserDelegate;
  4. Subclasses that implement the BeanDefinitionRegistry interface register BeanDefinition in the container;
  5. The essence of the container is to maintain a ConcurrentHashMap to save the BeanDefinition. Subsequent Bean operations are implemented around this ConcurrentHashMap;
  6. When using, we obtain these Bean objects through BeanFactory and ApplicaitonContext.

reference material:

  • Spring official website documentation
  • Tom's Spring notes
  • Spring source code depth analysis book

To do one thing to the extreme is talent!

Keywords: Java Spring

Added by radar on Sun, 02 Jan 2022 19:24:13 +0200