• Spring Reading Directory

Bean label parsing and registration

Of the four tags I saw in the last post, bean parsing is the most complex and important. Enter the class DefaultBeanDefinitionDocumentReader processBeanDefinition (ele, delegate) functions

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
		/ / entrust BeanDefinitionParserDelegate parseBeanDefinitionElement method for element analysis of a class,
		// Returns the BeanDefinitionHolder object, which contains
		/ / BeanDefinition beanName, and aliases
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); >1
		if(bdHolder ! =null) {
			// When the returned bdHolder is not empty, if there is a custom label under the child of the default label,
			// The custom tag needs to be parsed again
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				// Register the final decorated instance.
				// Register the resolved BeanDefinition
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); >2
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to register bean definition with name '" +
						bdHolder.getBeanName() + "'", ele, ex);
			}
			// Send registration event.
			// Notify the relevant monitor that the bean load is complete
			getReaderContext().fireComponentRegistered(newBeanComponentDefinition(bdHolder)); }}Copy the code

Parsing BeanDefinition

First to delegate. ParseBeanDefinitionElement (ele); function

@Nullable
	public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
		// Parse the ID attribute
		String id = ele.getAttribute(ID_ATTRIBUTE);
		// Parse the name attribute
		String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

		List<String> aliases = new ArrayList<>();
		// Put the name of the bean element into an alias array. Spring supports two ways to define an alias,
		
      
		if (StringUtils.hasLength(nameAttr)) {
			/ / nameAttr segmentation
			String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			aliases.addAll(Arrays.asList(nameArr));
		}

		String beanName = id;
        // If beanName is empty and the alias collection is not empty, the first alias is used as beanName
		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"); }}// The inner Bean is empty
		// When not empty? That is, when you have inner classes
		/** * 
      
        * 
       
         * 
        
          * 
          * 
          * 
         * 
        * 
       * */
		if (containingBean == null) {
			// Verify that the specified bean name (that is, the ID attribute) and alias already exist
			checkNameUniqueness(beanName, aliases, ele);
		}
		// The other attributes of the bean tag are parsed and encapsulated in an instance of type GenericBeanDefinition
		AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); >1
		if(beanDefinition ! =null) {
          // If there is no beanName
			if(! StringUtils.hasText(beanName)) {try {
					if(containingBean ! =null) {
						/ / then according to the naming rules for the current BeanDefinitionReaderUtils provide generate corresponding beanName bean
						beanName = BeanDefinitionReaderUtils.generateBeanName(
								beanDefinition, this.readerContext.getRegistry(), true);
					}
					else {
                // Generate the corresponding beanName according to XmlReaderContext
                        // Similar to com.gongj.bean.user# 0
						beanName = this.readerContext.generateBeanName(beanDefinition);	
                      // Similar to: com.gongj.bean.user
						String beanClassName = beanDefinition.getBeanClassName();
						if(beanClassName ! =null &&
								beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
								!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                            // Put it into the alias collectionaliases.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);
			// Encapsulate the obtained information into a BeanDefinitionHolder instance
			return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
		}

		return null;
	}
Copy the code

Enter the tag > 1, parseBeanDefinitionElement (ele, beanName, containingBean) functions, to label the other attributes of the parsing process.

@Nullable
	public AbstractBeanDefinition parseBeanDefinitionElement(
			Element ele, String beanName, @Nullable BeanDefinition containingBean) {

		this.parseState.push(new BeanEntry(beanName));

		String className = null;
		//class
		if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
			className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
		}
		String parent = null;
		//parent
		if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
			parent = ele.getAttribute(PARENT_ATTRIBUTE);
		}

		try {
			// Create an instance of GenericBeanDefinition with class and parentName as arguments
			AbstractBeanDefinition bd = createBeanDefinition(className, parent); >1
			// Parse the various attributes of the default bean label, assigning the attributes through the set method
			parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); >2
			/ / the description
			bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

			// Parse meta metadata
			parseMetaElements(ele, bd);
			// resolve the lookup-method property
			parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
			// Resolve the appet-method attribute
			parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

			// Parse the constructor arguments
			parseConstructorArgElements(ele, bd);
			// Parse the property child element
			parsePropertyElements(ele, bd);
			Parse the qualifier child element
			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

Enter the createBeanDefinition(className, parent) function

	protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName)
			throws ClassNotFoundException {

		return BeanDefinitionReaderUtils.createBeanDefinition(
				parentName, className, this.readerContext.getBeanClassLoader());
	}
Copy the code

Then debug into createBeanDefinition (parentName, className, enclosing readerContext. GetBeanClassLoader ()) function

	public static AbstractBeanDefinition createBeanDefinition(
			@Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {

		GenericBeanDefinition bd = new GenericBeanDefinition();
		//parentName may be empty
		bd.setParentName(parentName);
		if(className ! =null) {
			if(classLoader ! =null) {
				// If the classLoader is not empty, use the passed classLoader to load the class object, otherwise just
				/ / record the className
				bd.setBeanClass(ClassUtils.forName(className, classLoader));
			}
			else{ bd.setBeanClassName(className); }}return bd;
	}
Copy the code

If it is an XML configuration, all attributes will be parsed and unified encapsulated to the GenericBeanDefinition type instance, and then gradually parsed.

2 the function and then look at the marked points parseBeanDefinitionAttributes (ele, beanName containingBean, bd); Parse the various properties of the default bean label.

public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
			@Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {

		// Parse the singleton attribute
		if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
			// The 'singleton' property is upgraded to the 'scope' declaration
			error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
		}
		// Parse the scope attribute
		else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
			bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
		}
		// if the scope property is not explicitly used and the current Bean is resolved to Bean internal Bean,
		// The scope of the external Bean is used as the scope of the current Bean by default
		else if(containingBean ! =null) {
			// Take default from containing bean in case of an inner bean definition.
			bd.setScope(containingBean.getScope());
		}
		// Parse the abstract attribute
		if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
			bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
		}
		// Parse the lazy-init attribute
		String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
		if (isDefaultValue(lazyInit)) {
			lazyInit = this.defaults.getLazyInit();
		}
		// All other characters are set to false if none is set
		bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
		// Parse the autowire attribute
		String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
		bd.setAutowireMode(getAutowireMode(autowire));
		// Parse depends-on attributes
		if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
			String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
			bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
		}
		// Resolve the autowre-candidate attribute
		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));
		}
		// Parse the primary attribute
		if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
			bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
		}
		// Parse the init-method property
		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);
		}
		// Resolve the destroy-method attribute
		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);
		}
		// Parse the factory-method attribute
		if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
			bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
		}
		// Parse the factory-bean property
		if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
			bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
		}

		return bd;
	}
Copy the code

The function of each attribute can be seen in the author’s previous source: Spring source code (a)-Bean definition -BeanDefinition

Registered BeanDefinition

Now that Spring has wrapped the configuration information in the XML into the BeanDefinitionHolder object, you can register BeanDefinition. Let’s move on. Debugging into BeanDefinitionReaderUtils. RegisterBeanDefinition (bdHolder, getReaderContext () getRegistry ()) function

public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {

		// Register bean definition under primary name.
		// Register the bean definition
		String beanName = definitionHolder.getBeanName();
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); >1

		// Register aliases for bean name, if any.
		// Register an alias for the bean name (if any)
		String[] aliases = definitionHolder.getAliases(); 
		if(aliases ! =null) {
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias); >2}}}Copy the code

As you can see from the above code, parsed BeanDefinitionDefinitions are registered with instances of type BeanDefinitionRegistry. Registration of BeanDefinitions is divided into two parts: Register by beanName as well as by alias.


Register through beanName

First into the registry. RegisterBeanDefinition (beanName, definitionHolder getBeanDefinition () function, enter the function into the BeanDefinitionRegistry interface, The interface has three subclasses, we choose to enter the default use DefaultListableBeanFactory subclasses

@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 {
				/* * The last check before registration is mainly for the property check of methodOverrides in AbstractBeanDefinition, * Verify whether methodOverrides coexist with the factory method or the corresponding method does not exist at all */
				((AbstractBeanDefinition) beanDefinition).validate();
			}
			catch (BeanDefinitionValidationException ex) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Validation of bean definition failed", ex); }}// beanDefinitionMap: The bean defines the mapping key of the object as beanName
		// Get BeanDefinition according to beanName
		BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
		// Handle the case of registered beans
		if(existingDefinition ! =null) {
			// If the Bean is registered and configured not to be overridden,
			// Throws an exception, which defaults to true, allowing overwriting
			if(! isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
			}
			// The getRole method is a BeanDefinition
        // Overwrite the loaded BeanDefinition with a new BeanDefinition
			else if (existingDefinition.getRole() < beanDefinition.getRole()) {
				// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
				if (logger.isInfoEnabled()) {
					logger.info("Overriding user-defined bean definition for bean '" + beanName +
							"' with a framework-generated bean definition: replacing [" +
							existingDefinition + "] with [" + beanDefinition + "]"); }}else if(! beanDefinition.equals(existingDefinition)) {if (logger.isDebugEnabled()) {
					logger.debug("Overriding bean definition for bean '" + beanName +
							"' with a different definition: replacing [" + existingDefinition +
							"] with [" + beanDefinition + "]"); }}else {
				if (logger.isTraceEnabled()) {
					logger.trace("Overriding bean definition for bean '" + beanName +
							"' with an equivalent definition: replacing [" + existingDefinition +
							"] with [" + beanDefinition + "]"); }}// Register beanDefinitionMap, add beanDefinitionMap cache.
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		// Handle unregistered cases
		else {
			// If beanDefinition is already marked as created (to solve the problem of loop dependencies for singleton beans)
			if (hasBeanCreationStarted()) { 
				// Cannot modify startup-time collection elements anymore (for stable iteration)
				synchronized (this.beanDefinitionMap) {
					/ / register BeanDefinition
					this.beanDefinitionMap.put(beanName, beanDefinition);
					// Create List
      
        and add cached beanDefinitionNames and new beanName to the collection
      
					List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
					updatedDefinitions.addAll(this.beanDefinitionNames);
					updatedDefinitions.add(beanName);
					this.beanDefinitionNames = updatedDefinitions;
					removeManualSingletonName(beanName); >1}}else {
				// Still in startup registration phase
				// Still in the registration stage
				this.beanDefinitionMap.put(beanName, beanDefinition);
				// The beanName collection is maintained
				this.beanDefinitionNames.add(beanName);
				removeManualSingletonName(beanName); >1
			}
			this.frozenBeanDefinitionNames = null;
		}
		// The definition of the currently registered bean already exists in the beanDefinitionMap cache,
		// Resets the BeanDefinition of the current bean if the instance already exists in the singleton bean cache
		if(existingDefinition ! =null|| containsSingleton(beanName)) { resetBeanDefinition(beanName); }}protected boolean hasBeanCreationStarted(a) {
		return !this.alreadyCreated.isEmpty();
	}
Copy the code

We look at the removeManualSingletonName (beanName) this function, the function USES the java8 functional interface.

private void removeManualSingletonName(String beanName) {
		updateManualSingletonNames(set -> set.remove(beanName), set -> set.contains(beanName));
	}

	private void updateManualSingletonNames(Consumer<Set<String>> action, Predicate<Set<String>> condition) {
		if (hasBeanCreationStarted()) {
			// Cannot modify startup-time collection elements anymore (for stable iteration)
			synchronized (this.beanDefinitionMap) {
				// If manualSingletonNames contains the newly registered beanName
              Set -> set. Contains (beanName)
				if (condition.test(this.manualSingletonNames)) {
					// Create a set and add manualSingletonNames to the newly created set
					Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
					// Then remove the newly registered beanName
                  / / corresponds to the set. Remove (beanName)
					action.accept(updatedSingletons);
					this.manualSingletonNames = updatedSingletons; }}}else {
			// Still in startup registration phase
			if (condition.test(this.manualSingletonNames)) {
				action.accept(this.manualSingletonNames); }}}Copy the code

The first function takes a Consumer interface that takes an input parameter and returns nothing. The second function takes a Predicate interface that takes an input parameter and returns a Boolean result.


Let’s look at the data storage container used by the code above:

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
private volatile Set<String> manualSingletonNames = new LinkedHashSet<>(16);

Copy the code
  • BeanDefinitionMap: The bean defines a mapping of objects, with key beanName and value BeanDefinition
  • BeanDefinitionNames: beanName list
  • AlreadyCreated: Stores the name of a bean that was created at least once
  • ManualSingletonNames: The purpose of this Set is not clear. A list of names for manually registered singletons

Register by alias

Entering the registry. RegisterAlias (beanName, alias) function jumps to the AliasRegistry interface, The interface under two implementation class GenericApplicationContext and SimpleAliasRegistry, we choose SimpleAliasRegistry

@Override
	public void registerAlias(String name, String alias) {
		Assert.hasText(name, "'name' must not be empty");
		Assert.hasText(alias, "'alias' must not be empty");
		synchronized (this.aliasMap) {
			// The alias is the same as beanName
			if (alias.equals(name)) {
				// Remove the alias from the Map
				this.aliasMap.remove(alias);
				if (logger.isDebugEnabled()) {
					logger.debug("Alias definition '" + alias + "' ignored since it points to same name"); }}// Alias is not the same as beanName
			else {
				// Get beanName based on the alias passed in
				String registeredName = this.aliasMap.get(alias);
				if(registeredName ! =null) {
					// The registered name is equal to the beanName that needs to be registered
					/* 
       
       */
					if (registeredName.equals(name)) {
						// An existing alias - no need to re-register
						// Existing alias - no need to re-register
						return;
					}
					// An exception is thrown if the alias does not allow overwriting. The default is true to allow overwriting
					if(! allowAliasOverriding()) {throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
								name + "': It is already registered for name '" + registeredName + "'.");
					}
					if (logger.isDebugEnabled()) {
						logger.debug("Overriding alias '" + alias + "' definition for registered name '" +
								registeredName + "' with new target name '" + name + "'"); }}// Check whether the given name (user2) points to the given alias (user) as an alias (user2)
				/* 2, 
      
				checkForAliasCircle(name, alias);
				// Cache alias
				this.aliasMap.put(alias, name);
				if (logger.isTraceEnabled()) {
					logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'"); }}}}Copy the code