Spring source code analysis (15) refresh source code analysis configuration file analysis

Posted by svenski on Sun, 20 Feb 2022 18:16:15 +0100

1, Foreword

In the previous blog, we introduced the otainfreshbeanfactor method in the refresh method. This method is used to obtain the bean factory. During this period, it will also complete the loading of the configuration file and generate the BeanDefinition. It should be noted that the default BeanFactory generated here is DefaultListableBeanFactory. It should be noted that, Some of our customized xsd constraint files are also parsed here by implementing the beandefinitionparser interface and the parse method. The customized tags are also parsed by implementing the NamespaceHandlerSupport interface and the init method. The following is the parsing flow chart of the bean configuration file I drew:

We need to learn how to parse the xsd custom sub section of the blog. If we don't need to parse the xsd custom sub section of the blog in the future, we need to know how to parse it.

2, Source code analysis of Spring configuration file parsing process

2.1 Warehousing: obtainFreshBeanFactory

	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		/**
		 *  Refresh the bean factory to determine whether the bean factory already exists. If so, it needs to be destroyed and closed
		 *  The refreshBeanFactory method of AbstractRefreshableApplicationContext is called by default
		 *  When refreshing the bean factory, the bean definition will be loaded.
		 *  Initialize BeanFactory, read the XML file, and record the obtained BeanFactory in the properties of the current entity
		 */
		refreshBeanFactory();
		return getBeanFactory();
	}

Let's move on to the refreshBeanFactory method:

	@Override
	protected final void refreshBeanFactory() throws BeansException {
		// Judge whether the bean factory exists. If it exists, it needs to be destroyed and closed first. Otherwise, there will be problems
		if (hasBeanFactory()) {
			// Destroy the bean and remove all beans in the bean factory from the map according to the bean name
			destroyBeans();
			// Close the bean factory, set the serialization id of the bean factory to null, and assign the value of beanFactory to null
			closeBeanFactory();
		}
		try {
			// Recreate the bean factory. The default returned bean factory type is DefaultListableBeanFactory
			DefaultListableBeanFactory beanFactory = createBeanFactory();

			// Set serialization ID, ID: hexadecimal value of class name + "@" + object
			beanFactory.setSerializationId(getId());

			// Custom bean factory. Function: set whether the bean definition can be overwritten and whether circular reference is allowed when the bean is created
			customizeBeanFactory(beanFactory);

			// Parse and load the bean definition. By default, it is implemented through loadBeanDefinitions in AbstractXmlApplicationContext class
			loadBeanDefinitions(beanFactory);

			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}

The method will first determine whether there is beanFactory, if it exists, destroy the corresponding factory first, then call createBeanFactory to create a bean factory, default to the DefaultListableBeanFactory we created, and then set some attribute information, which is to parse the configuration file in loadBeanFinitions.

2.2 loadBeanDefinitions(beanFactory)

Create a bean reader in xml format and set some property values.

	@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create an Xml format bean reader, which will set the resource loader and environment information of the bean factory
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// Object is initialized when creating a bean reader Get it directly here
		beanDefinitionReader.setEnvironment(this.getEnvironment());

		// The above has been initialized during creation. You can get it directly here
		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.
		// Initialize beanDefinitionReader object. Set whether the configuration file needs to be verified here
		initBeanDefinitionReader(beanDefinitionReader);

		// The tag attribute in the xml is parsed and added to the bean definition
		loadBeanDefinitions(beanDefinitionReader);
	}

Then look down at loadbean definitions (bean definition reader);

2.3loadBeanDefinitions(beanDefinitionReader)

There will be two branches here. One is to directly resolve resources and the other is to resolve the incoming configuration file list. Here we will take the second branch.

	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
		// The container constructed by another constructor will use the method of configResources to load the bean definition
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
			reader.loadBeanDefinitions(configResources);
		}
		// Get all the configuration file names, for example: {"beans.xml"}. Mainly the name of the xml configuration file
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			reader.loadBeanDefinitions(configLocations);
		}
	}


	@Override
	public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
		Assert.notNull(locations, "Location array must not be null");
		int count = 0;
		for (String location : locations) {
			// Load bean definition
			count += loadBeanDefinitions(location);
		}
		return count;
	}

	@Override
	public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
		return loadBeanDefinitions(location, null);
	}

2.4 loadBeanDefinitions(location,null)

	public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {

		// resourceLoader is a property set in the XmlApplicationContext through the setResourceLoader method
		ResourceLoader resourceLoader = getResourceLoader();

		if (resourceLoader == null) {
			throw new BeanDefinitionStoreException(
					"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
		}
		if (resourceLoader instanceof ResourcePatternResolver) {
			// Resource pattern matching available.
			try {
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				// Load bean definition
				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.
			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;
		}
	}

	@Override
	public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
		Assert.notNull(resources, "Resource array must not be null");
		int count = 0;
		for (Resource resource : resources) {
			// Load bean definition
			count += loadBeanDefinitions(resource);
		}
		return count;
	}

In fact, in the end, it is also called back to loadbean definitions (resource);

2.5loadBeanDefinitions(resource)

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

	public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		if (logger.isTraceEnabled()) {
			logger.trace("Loading XML bean definitions from " + encodedResource);
		}

		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());
				}
				// Perform bean definition loading
				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();
			}
		}
	}

The real work here is doloadbean finishes;

2.6 doLoadBeanDefinitions

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

		try {
			// Parse the xml configuration file into a Document object through JAXP(Java API for XMLProcessing) in JDK
			Document doc = doLoadDocument(inputSource, resource);
			// Register bean definitions
			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);
		}
	}

2.7 registerBeanDefinitions

	public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		// Create a Bean definition document reader through reflection. The default type is DefaultBeanDefinitionDocumentReader
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		// Get the number of bean definitions that have been loaded before, directly through beandefinitionmap Size acquisition
		int countBefore = getRegistry().getBeanDefinitionCount();
		// Execute xml parsing and bean definition registration. When creating the bean definition reader context, the default DefaultNamespaceHandlerResolver will be created
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		// Returns the number of bean definitions registered this time
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}
    
    // To resolve the registered bean definition
	@Override
	public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		this.readerContext = readerContext;
		// Perform the bean defined registration operation
		doRegisterBeanDefinitions(doc.getDocumentElement());
	}

2.8 doRegisterBeanDefinitions

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.
		BeanDefinitionParserDelegate parent = this.delegate;
		// Create a bean to define the parser delegate object, and resolve some global attributes in the < beans / > tag at the same time
		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);
				// We cannot use Profiles.of(...) since profile expressions are not supported
				// in XML config. See SPR-12458 for details.
				if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
					if (logger.isDebugEnabled()) {
						logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
								"] not matching: " + getReaderContext().getResource());
					}
					return;
				}
			}
		}
		// Handle some custom element types before spring parses xml
		preProcessXml(root);

		// Parse bean definition
		parseBeanDefinitions(root, this.delegate);

		// After spring parses the xml, it processes some custom element types
		postProcessXml(root);

		this.delegate = parent;
	}

You can see that preProcessXml and postProcessXml are empty implementations, which can be left for us to expand. This is an expansion point.

2.9 parseBeanDefinitions

	protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		// Judge whether it is a user-defined label according to the uri of nameSpace.
		// The uri corresponding to the nameSpace of the beans of spring is: http://www.springframework.org/schema/beans
		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 the default xml nodes (import,alias,beans) in Spring
						parseDefaultElement(ele, delegate);
					}
					else {
						// Parsing custom label content
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			// If it is not the default namespace of spring, it will be resolved as an extension tag
			delegate.parseCustomElement(root);
		}
	}

	private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			// Handle import tag parsing
			importBeanDefinitionResource(ele);
		}
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			// Handle alias tag parsing
			processAliasRegistration(ele);
		}
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			// Handle bean tag parsing
			processBeanDefinition(ele, delegate);
		}
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// Handle beans tag parsing
			doRegisterBeanDefinitions(ele);
		}
	}

Here you can see that if it is the default tag: import, bean, beans and alias, it will go through the default parsing, parseDefaultElement. Otherwise, it will go through parseCustomElement.

3, Custom profile resolution

3.1 User.java

public class User {
	private String userName;

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}
}

3.2 # customize UserBeanDefinitionParse

	@SuppressWarnings("rawtypes")
	protected Class getBeanClass(Element element) {
		return User.class;
	}

	protected void doParse(Element element, BeanDefinitionBuilder bean) {
		String userName = element.getAttribute("userName");
		String email = element.getAttribute("email");
		if (StringUtils.hasText(userName)) {
			bean.addPropertyValue("userName", userName);
		}
		if (StringUtils.hasText(email)){
			bean.addPropertyValue("email", email);
		}

	}
}

3.3 UserNamespaceHandler

/**
 * MyNamespaceHandler
 */
public class UserNamespaceHandler extends NamespaceHandlerSupport{
	/**
	 * Resolution class of registration label
	 */
	@Override
	public void init() {
		registerBeanDefinitionParser("mqc:user",new UserBeanDefinitionParse());
	}
}

3.4. Create spring Handlers file

Create a META-INF directory under the resource directory, and create three files: spring handlers,Spring. schemas,user.xsd.

        Spring.handlers:

http\://www.mqc.com/schema/user=org.springframework.mqc.selftag.UserNamespaceHandler

        Spring.schemas:

http\://www.mqc.com/schema/user.xsd=META-INF/user.xsd

        user.xsd:

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
		targetNamespace="http://www.mqc.com/schema/user"
		xmlns:tns="http://www.mqc.com/schema/user"
		elementFormDefault="qualified">
	<element name="mqc">
		<complexType>
			<attribute name ="userName" type = "string"/>
		</complexType>
	</element>
</schema>

3.5 creating profiles

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

	<aaa:mqc  userName = "lee" />
</beans>

Here, the spring custom label is probably over. In fact, if there is a demand for custom labels in later companies, you can go to see the label analysis of spring context and do what spring does.