Spring source code interpretation continues.

This is the eighth article in the Spring series. If you haven’t read the previous articles in this series, you are advised to do so in order to better understand this article.

  1. Spring source code interpretation plan
  2. Spring source code first open whole! How is the configuration file loaded?
  3. Spring source code second bomb! XML file parsing process
  4. Spring source code third bullet! What the hell is EntityResolver?
  5. Spring source code fourth bomb! Understand the BeanDefinition in depth
  6. Build Spring source code analysis environment for you
  7. Spring source code sixth bullet! The father of scene and people talk about the container DefaultListableBeanFactory

1. Review

I don’t know if you remember that when we talked about the Spring document loading, we involved the following source code:

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
		throws BeanDefinitionStoreException {
	try {
		Document doc = doLoadDocument(inputSource, resource);
		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); }}Copy the code

There are only two core methods in this code:

  1. First call doLoadDocument method to obtain the Spring XML configuration file loaded out of the Document Document object, this method of execution process we have been introduced in the previous, here will not repeat.
  2. The registerBeanDefinitions method is then called to parse the loaded document object and define the corresponding BeanDefinition object.

What is BeanDefinition, what does it do, Songgo in the previous Spring source code fourth play! Understanding BeanDefinition has already been covered in this article, but I won’t repeat it here.

In this article we’ll look at how the Document object is loaded step by step into a BeanDefinition.

2.parseDefaultElement

We’ll start with the registerBeanDefinitions method:

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;
}
Copy the code

Here by calling createBeanDefinitionDocumentReader method to get to a BeanDefinitionDocumentReader instance, The concrete object is DefaultBeanDefinitionDocumentReader, that is to say the next call DefaultBeanDefinitionDocumentReader# registerBeanDefinitions parsing. Moving on to the definition of this method:

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
	this.readerContext = readerContext;
	doRegisterBeanDefinitions(doc.getDocumentElement());
}
Copy the code

Here again call to doRegisterBeanDefinitions method to complete registration:

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;
	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;
			}
		}
	}
	preProcessXml(root);
	parseBeanDefinitions(root, this.delegate);
	postProcessXml(root);
	this.delegate = parent;
}
Copy the code

This method process is relatively simple, first check whether there is a profile to deal with (if you do not know the Spring profile, you can get a free Spring tutorial recorded by Songgo in the background of the public account reply Spring 5). After processing the profile, parsing is followed by preProcessXml and postProcessXml, both of which are empty by default. The real parsing method is parseBeanDefinitions:

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)) {
					parseDefaultElement(ele, delegate);
				}
				else{ delegate.parseCustomElement(ele); }}}}else{ delegate.parseCustomElement(root); }}Copy the code

The node is resolved in this method, which eventually leads to the parseDefaultElement method. Let’s take a look at this method:

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)) {
		processBeanDefinition(ele, delegate);
	}
	else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
		// recursedoRegisterBeanDefinitions(ele); }}Copy the code

Here we are in the long-awaited parseDefaultElement method.

In this method, we can see that nodes are divided into four categories:

  • import
  • alias
  • bean
  • beans

Each node is understandable, because we may have more or less useful in the development, it is important to note that if the beans nodes, again call doRegisterBeanDefinitions recursive analysis method, gave a comment recurse source it, which means recursion.

The four types of node resolution, let’s start with beans, because beans are the most commonly used node, this is clear, the other three nodes can be compared.

Let’s look at the processBeanDefinition method:

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
	BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
	if(bdHolder ! =null) {
		bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
		try {
			// Register the final decorated instance.
			BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
		}
		catch (BeanDefinitionStoreException ex) {
			getReaderContext().error("Failed to register bean definition with name '" +
					bdHolder.getBeanName() + "'", ele, ex);
		}
		// Send registration event.
		getReaderContext().fireComponentRegistered(newBeanComponentDefinition(bdHolder)); }}Copy the code

In this code, the first call the proxy class BeanDefinitionParserDelegate to parse element analytic results are saved in bdHolder, namely bean configuration element in the node class, id, name and other attributes, After this step of parsing, everything is saved to the bdHolder.

If the bdHolder is not empty, the attributes of the child node are then parsed, the bdHolder is registered, and finally an event is issued to inform the bean node that it has finished loading.

In that case, the core of the whole parsing process should be in the delegate. ParseBeanDefinitionElement (ele) method, tracking the execution of the method, we finally came here:

@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
	String id = ele.getAttribute(ID_ATTRIBUTE);
	String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
	List<String> aliases = new ArrayList<>();
	if (StringUtils.hasLength(nameAttr)) {
		String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
		aliases.addAll(Arrays.asList(nameArr));
	}
	String beanName = id;
	if(! StringUtils.hasText(beanName) && ! aliases.isEmpty()) { beanName = aliases.remove(0);
		if (logger.isTraceEnabled()) {
			logger.trace("No XML 'id' specified - using '" + beanName +
					"' as bean name and " + aliases + " as aliases"); }}if (containingBean == null) {
		checkNameUniqueness(beanName, aliases, ele);
	}
	AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
	if(beanDefinition ! =null) {
		if(! StringUtils.hasText(beanName)) {try {
				if(containingBean ! =null) {
					beanName = BeanDefinitionReaderUtils.generateBeanName(
							beanDefinition, this.readerContext.getRegistry(), true);
				}
				else {
					beanName = this.readerContext.generateBeanName(beanDefinition);
					// Register an alias for the plain bean class name, if still possible,
					// if the generator returned the class name plus a suffix.
					// This is expected for Spring 1.2/2.0 backwards.
					String beanClassName = beanDefinition.getBeanClassName();
					if(beanClassName ! =null &&
							beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
							!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); }}if (logger.isTraceEnabled()) {
					logger.trace("Neither XML 'id' nor 'name' specified - " +
							"using generated bean name [" + beanName + "]"); }}catch (Exception ex) {
				error(ex.getMessage(), ele);
				return null;
			}
		}
		String[] aliasesArray = StringUtils.toStringArray(aliases);
		return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
	}
	return null;
}
Copy the code

What is done in this method can be roughly divided into five steps:

  1. Extract the ID and name attribute values.
  2. Check whether beanName is unique.
  3. Further parsing of the node yields the beanDefinition object, which is of type GenericBeanDefinition.
  4. If the beanName property has no value, the beanName is generated using the default rule (the default rule is the class name full path).
  5. Finally, the retrieved information is returned as a BeanDefinitionHolder.

At this level is mainly completed the handling of the id and the name, if the user doesn’t define the name for a bean, then generates a default name, as for the other attributes parsing, are mainly in parseBeanDefinitionElement approach.

@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
		Element ele, String beanName, @Nullable BeanDefinition containingBean) {
	this.parseState.push(new BeanEntry(beanName));
	String className = null;
	if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
		className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
	}
	String parent = null;
	if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
		parent = ele.getAttribute(PARENT_ATTRIBUTE);
	}
	try {
		AbstractBeanDefinition bd = createBeanDefinition(className, parent);
		parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
		bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
		parseMetaElements(ele, bd);
		parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
		parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
		parseConstructorArgElements(ele, bd);
		parsePropertyElements(ele, bd);
		parseQualifierElements(ele, bd);
		bd.setResource(this.readerContext.getResource());
		bd.setSource(extractSource(ele));
		return bd;
	}
	catch (ClassNotFoundException ex) {
		error("Bean class [" + className + "] not found", ele, ex);
	}
	catch (NoClassDefFoundError err) {
		error("Class that bean class [" + className + "] depends on not found", ele, err);
	}
	catch (Throwable ex) {
		error("Unexpected failure during bean definition parsing", ele, ex);
	}
	finally {
		this.parseState.pop();
	}
	return null;
}
Copy the code
  1. First you parse out the className attribute.
  2. Resolve the parent property.
  3. Call the createBeanDefinition method to create a BeanDefinition that holds the object, GenericBeanDefinition.
  4. ParseBeanDefinitionAttributes used to resolve the various node properties.
  5. ParseMetaElements is used to parseMeta data.
  6. ParseLookupOverrideSubElements parsing lookup method attribute.
  7. ParseReplacedMethodSubElements parsing the replace method attribute.
  8. ParseConstructorArgElements parsing constructor parameters.
  9. ParsePropertyElements parses property child elements.
  10. ParseQualifierElements resolves the Qualifier child elements.
  11. Finally, bd is returned.

As you can see, all the properties in the bean node have been resolved, some of which we see everyday, some of which we don’t see very often or even have never seen before, and in either case, all have now been resolved. When parsing is complete, the GenericBeanDefinition that will be obtained is returned.

3. General attribute resolution

Here are some of the properties of resolution may be upset, that I said after a while, there are some relatively routine, such as parseBeanDefinitionAttributes method used to resolve all kinds of node attribute, the nodes attributes may everyone is familiar with, together we look at:

public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
		@Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
	if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
		error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
	}
	else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
		bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
	}
	else if(containingBean ! =null) {
		// Take default from containing bean in case of an inner bean definition.
		bd.setScope(containingBean.getScope());
	}
	if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
		bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
	}
	String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
	if (isDefaultValue(lazyInit)) {
		lazyInit = this.defaults.getLazyInit();
	}
	bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
	String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
	bd.setAutowireMode(getAutowireMode(autowire));
	if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
		String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
		bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
	}
	String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
	if (isDefaultValue(autowireCandidate)) {
		String candidatePattern = this.defaults.getAutowireCandidates();
		if(candidatePattern ! =null) { String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern); bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName)); }}else {
		bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
	}
	if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
		bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
	}
	if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
		String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
		bd.setInitMethodName(initMethodName);
	}
	else if (this.defaults.getInitMethod() ! =null) {
		bd.setInitMethodName(this.defaults.getInitMethod());
		bd.setEnforceInitMethod(false);
	}
	if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
		String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
		bd.setDestroyMethodName(destroyMethodName);
	}
	else if (this.defaults.getDestroyMethod() ! =null) {
		bd.setDestroyMethodName(this.defaults.getDestroyMethod());
		bd.setEnforceDestroyMethod(false);
	}
	if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
		bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
	}
	if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
		bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
	}
	return bd;
}
Copy the code

As you can see, the node attributes parsed here, from top to bottom, are:

  1. Parse the Singleton property (deprecated, replaced by scope).
  2. Resolves the scope property, using the value of the scope property of the containingBean if no scope property is specified but there is a containingBean.
  3. Parse the abstract attribute.
  4. Resolve the lazy-init property.
  5. Parse the Autowire property.
  6. Parse the depends-on attribute.
  7. Parse the autowre-candidate property.
  8. Parse the primary attribute.
  9. Resolve the init-method property.
  10. Resolve the destroy-method property.
  11. Parse the factory-method property.
  12. Parse the factory-bean property.

These properties are familiar. Because it’s used more everyday.

It is important to note that there are some attributes that are close to qualifier, such as lookup-method, replace-method, and qualifier. To speak with you about the use of these unpopular attribute, then we could further analytical parseMetaElements here, parseLookupOverrideSubElements method, etc.

4. Bean generation

With BeanDefinitionHolder in place, Bean generation is easy.

Review the following two articles to understand how BeanDefinition can be converted to a specific Bean:

  • Spring source code fourth bomb! Understand the BeanDefinition in depth
  • Spring source code sixth bullet! The father of scene and people talk about the container DefaultListableBeanFactory

Well, today’s article first say so much ~