IoC container initialization of spring framework

Keywords: Java xml Spring encoding Attribute

Analysis example

Startup class

Application, using ClassPathXmlApplicationContext to load xml files

/**
 * @author jianw.li
 * @date 2020/3/16 11:53 PM
 * @Description: TODO
 */
public class MyApplication {
    private static final String CONFIG_LOCATION = "classpath:application_context.xml";

    private static final String BEAN_NAME = "hello";

    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext(CONFIG_LOCATION);
        Hello hello = (Hello) ac.getBean(BEAN_NAME);
        hello.sayHello();
    }
}

Bean

/**
 * @author jianw.li
 * @date 2020/3/16 11:53 PM
 * @Description: TODO
 */
public class Hello {

	public void sayHello() {
		System.out.println("Hello World");
	}
}

configuration file

Create a configuration file named classpath: application menu context.xml under resources, and configure Bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="hello" class="com.boyce.bean.Hello"></bean>

</beans>

overall structure

The inheritance system of ClassPathXmlApplicationContext is as follows:

The general structure of IoC is as follows:

![image-20200322155805716](/Users/lijianwei/Library/Application Support/typora-user-images/image-20200322155805716.png)

Source code analysis

ClassPathXmlApplicationContext

//Constructor to create ClassPathXmlApplicationContext, where configLocation is the file path where Bean is located
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
		this(new String[] {configLocation}, true, null);
}

public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {
    //null
		super(parent);
    //Set configuration path to ApplicationContext
		setConfigLocations(configLocations);
		if (refresh) {
      //Core method, refresh will destroy the old appliationcontext
			refresh();
		}
	}

AbstractApplicationContext

refresh

The core method, refresh, destroys the old ApplicationContext and generates a new ApplicationContext

@Override
	public void refresh() throws BeansException, IllegalStateException {
		//Lock. There is no specific object, just to synchronize a piece of code, you can create Object startupShutdownMonitor = new Object()
		synchronized (this.startupShutdownMonitor) {
			// Prepare for context refresh. Set start time, set activation status, etc
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory
			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.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				//Initialize all non lazy loading instances
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				//Broadcast initialization complete
				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.
				destroyBeans();

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

Tell the subclass to refresh the internal bean factory

Core method, initialize BeanFactory, load Bean, register Bean

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		//Refresh the Bean factory, close and destroy the old Bean factory
		refreshBeanFactory();
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		if (logger.isDebugEnabled()) {
			logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
		}
		return beanFactory;
	}
refreshBeanFactory

Close and destroy the old BeanFactory, create and initialize a new BeanFactory. Why is DefaultListableBeanFactory?

@Override
	protected final void refreshBeanFactory() throws BeansException {
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			//Initialize DefaultListableBeanFactory. Why instantiate DefaultListableBeanFactory? Not the other Bean factories
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			//Bean factory serialization setting id
			beanFactory.setSerializationId(getId());
			//Customize the Bean factory, set not to overwrite the Bean, not to allow circular dependency, etc
			customizeBeanFactory(beanFactory);
			//Load the Bean into the Bean factory
			loadBeanDefinitions(beanFactory);
			//There is an association between the synchronized block and the synchronized block in hasBeanFactory. After locking here, the synchronized block in hasBeanFactory will wait
			//Avoid situations where beanFactory is not destroyed or closed
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}
customizeBeanFactory

Customize BeanFactory. Set Bean overrides, circular dependencies, and so on. What is circular dependence?

protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
		if (this.allowBeanDefinitionOverriding != null) {
			//The default value is false. It is not allowed to overwrite the Bean
			beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		if (this.allowCircularReferences != null) {
			//The default value is false, circular dependency is not allowed
			beanFactory.setAllowCircularReferences(this.allowCircularReferences);
		}
	}

AbstractXmlApplicationContext

loadBeanDefinitions
@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// Configure the bean definition reader with this context's
		// resource loading environment.
		beanDefinitionReader.setEnvironment(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.
		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}
loadBeanDefinitions
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
			//Loading resource objects will eventually return to this way of loading bean s
			reader.loadBeanDefinitions(configResources);
		}
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			//Load resource path
			reader.loadBeanDefinitions(configLocations);
		}
	}

AbstractBeanDefinitionReader

loadBeanDefinitions
@Override
	public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
		Assert.notNull(locations, "Location array must not be null");
		int counter = 0;
		//Loop profile path
		for (String location : locations) {
			counter += loadBeanDefinitions(location);
		}
		return counter;
	}
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
		ResourceLoader resourceLoader = getResourceLoader();
		if (resourceLoader == null) {
			throw new BeanDefinitionStoreException(
					"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
		}

		if (resourceLoader instanceof ResourcePatternResolver) {
			// Resource pattern matching available.
			try {
        // Convert xml to Resource, so the above two Resource loading methods will eventually return to the Resource loading method
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				int loadCount = loadBeanDefinitions(resources);
				if (actualResources != null) {
					for (Resource resource : resources) {
						actualResources.add(resource);
					}
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
				}
				return loadCount;
			}
			catch (IOException ex) {
				throw new BeanDefinitionStoreException(
						"Could not resolve bean definition resource pattern [" + location + "]", ex);
			}
		}
		else {
			// Can only load single resources by absolute URL.
			Resource resource = resourceLoader.getResource(location);
			int loadCount = loadBeanDefinitions(resource);
			if (actualResources != null) {
				actualResources.add(resource);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
			}
			return loadCount;
		}
	}
@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;
	}

XmlBeanDefinitionReader

loadBeanDefinitions

Loading beans from a detailed XML file

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

		//Used to store compiled resources
		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 {
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
			finally {
				inputStream.close();
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}
doLoadBeanDefinitions

Load bean from xml file, convert xml to Document and register bean

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
			//Convert xml to Document
			Document doc = doLoadDocument(inputSource, resource);
			//Registered Bean
			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);
		}
	}
registerBeanDefinitions

Count the number of bean s loaded from the current configuration file

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		int countBefore = getRegistry().getBeanDefinitionCount();
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}

DefaultBeanDefinitionDocumentReader

registerBeanDefinitions

Parse beans from "spring beans" xsd. What is xsd? XML schema definition

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

Register each Bean from the root node

/**
	 * Register each bean definition within the given root {@code <beans/>} element.
	 * Register each Bean from the root node
	 */
	protected void doRegisterBeanDefinitions(Element root) {
		// Any nested <beans> elements will cause recursion in this method. In
		// order to propagate and preserve <beans> default-* attributes correctly,
		// keep track of the current (parent) delegate, which may be null. Create
		// the new (child) delegate with a reference to the parent for fallback purposes,
		// then ultimately reset this.delegate back to its original (parent) reference.
		// this behavior emulates a stack of delegates without actually necessitating one.
		//You can set the active profile of the current environment through spring.profiles.active. For example, you can configure the profile through @ ActiveProfiles

		//Parse bean definition
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);

		if (this.delegate.isDefaultNamespace(root)) {
			//Get the Profile attribute in the element. You can set the Profile of the current bean through xml or @ Profile
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			//Skip directly if profile property is not available
			if (StringUtils.hasText(profileSpec)) {
				//Configure multiple profile s
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				//Whether the profile in the element is one of the profiles specified by the environment
				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 if not in the profile specified by the current environment
					return;
				}
			}
		}

		preProcessXml(root);
		parseBeanDefinitions(root, this.delegate);
		postProcessXml(root);

		this.delegate = parent;
	}
parseBeanDefinitions

Start from the document root node to parse elements, such as import, alias, bean, etc

/**
	 * Parse the elements at the root level in the document:
	 * "import", "alias", "bean".
	 * @param root the DOM root element of the document
	 */
	protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		if (delegate.isDefaultNamespace(root)) {
			NodeList nl = root.getChildNodes();
			for (int i = 0; i < nl.getLength(); i++) {
				Node node = nl.item(i);
				if (node instanceof Element) {
					Element ele = (Element) node;
					if (delegate.isDefaultNamespace(ele)) {
						//Parsing elements under the default namespace (for example: < import >, < alias >, < beans >, < bean >)
						parseDefaultElement(ele, delegate);
					}
					else {
						//Parsing Custom elements (for example, < MVC >, < context >, < AOP >)
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}
parseDefaultElement

Resolve elements under default namespace (for example:,,,)

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			importBeanDefinitionResource(ele);
		}
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			processAliasRegistration(ele);
		}
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			//Understand the source code of bean parsing
			processBeanDefinition(ele, delegate);
		}
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse
			doRegisterBeanDefinitions(ele);
		}
	}
processBeanDefinition

Get bean element resolution and register

/**
	 * Process the given bean element, parsing the bean definition
	 * and registering it with the registry.
	 * Get bean element resolution and register
	 */
	protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
		//Parsing bean elements and encapsulating them into bean definitionholder
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				// Register the final decorated instance.
				//Registering an instance of decoration
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to register bean definition with name '" +
						bdHolder.getBeanName() + "'", ele, ex);
			}
			// Send registration event.
			//Send registration event
			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
		}

DefaultListableBeanFactory

registerBeanDefinition
@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);
			}
		}

		BeanDefinition oldBeanDefinition;
		//Bean definitionmap places all registered beans
		oldBeanDefinition = this.beanDefinitionMap.get(beanName);

		//If bean name already exists
		if (oldBeanDefinition != null) {
			//Do not allow overriding bean s
			if (!isAllowBeanDefinitionOverriding()) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
						"': There is already [" + oldBeanDefinition + "] bound.");
			}
			//Judge who covers whom by comparing BeanRole
			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 + "]");
				}
			}
			//New bean s overwrite old ones
			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 + "]");
				}
			}
			//Place the bean in the bean definition map
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		//beanName is not duplicate in beanDefinitionMap
		else {
			//Under abnormal circumstances, other beans have already been initialized. If there is already bean initialization, the user is already in the process of business operation. It is impossible to guarantee the thread safety of some of the actions of beanDefinitionMap, beanDefinitionNames and manualSingletonNames. Therefore, it is necessary to lock them. Reference: https://blog.csdn.net/qq_41907991/article/details/97614337
			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;
					if (this.manualSingletonNames.contains(beanName)) {
						Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
						updatedSingletons.remove(beanName);
						this.manualSingletonNames = updatedSingletons;
					}
				}
			}
			else {
				// Still in startup registration phase
				//Bean is placed in bean definition map
				this.beanDefinitionMap.put(beanName, beanDefinition);
				//Record bean name
				this.beanDefinitionNames.add(beanName);
				//Beans do not need to be registered manually
				this.manualSingletonNames.remove(beanName);
			}
			this.frozenBeanDefinitionNames = null;
		}

		if (oldBeanDefinition != null || containsSingleton(beanName)) {
			resetBeanDefinition(beanName);
		}
	}

Calling relation

summary

So far, the initialization of IoC container has been completed. Parse the bean element in the xml file into a bean, register the bean to the registry, and send the registration event. DefaultListableBeanFactory establishes bean configuration information, which is stored in BeanDefinitionMap and maintained by IoC container.

In the end, I don't know much and do little. There must be many mistakes in this article. If you find it troublesome and point out in time, in order to avoid misleading more people, I will learn and revise it in time!

Reference reference

[1] Spring technology insider

[2]https://www.javadoop.com/post/spring-ioc

[3]https://github.com/seaswalker/spring-analysis/blob/master/note/Spring.md#classpathxmlapplicationcontext

Posted by eagleweb on Sun, 29 Mar 2020 09:48:58 -0700