Loading Bean s for Spring Ioc

Posted by burtybob on Mon, 09 Sep 2019 05:55:58 +0200

In the last article Loading Bean s for Spring Ioc (1) , we analyzed 2.2 of the beans loading doGetBean() method in Spring Ioc to get a single bean from the cache and 2.3 to get the final bean instance object. We then analyzed the remaining steps.
Code directly:

//The ability to actually get beans from the IOC container is also where the dependent injection function is triggered
	protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

		//Gets the name of the managed Bean based on the specified name, stripping the container dependency from the specified name
		// If an alias is specified, convert the alias to the canonical Bean name
<1>		final String beanName = transformedBeanName(name);
		Object bean;

		// Eagerly check singleton cache for manually registered singletons.
		// Get a single Bean that has been created from the cache
<2>		Object sharedInstance = getSingleton(beanName);
		//If there is one in the cache
		if (sharedInstance != null && args == null) {
			if (logger.isDebugEnabled()) {
				if (isSingletonCurrentlyInCreation(beanName)) {
					logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
							"' that is not fully initialized yet - a consequence of a circular reference");
				}
				else {
					logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
				}
			}

			//Note: Bean Factory is the factory that manages beans in containers
			//     FactoryBean is a factory Bean that creates objects, and there is a difference between them

			//Gets an instance object of a given Bean, either the bean instance itself or the Bean object created by FactoryBean
			//(Why retrieve it again, because the sharedInstance retrieved above is not necessarily complete)
<3>			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}

		else {
			// Fail if we're already creating this bean instance:
			// We're assumably within a circular reference.
			// Because Spring only resolves circular dependencies in singleton mode, an exception will be thrown if there is a circular dependency in prototype mode.
<4>			if (isPrototypeCurrentlyInCreation(beanName)) {
				throw new BeanCurrentlyInCreationException(beanName);
			}

			// Check if bean definition exists in this factory.
			//Check for BeanDefinition with the specified name in the IOC container, first check for
			//Required beans that can be obtained in the current Bean Factory, or delegate the current container if not
			//Find the parent container of the container, or, if not found, follow its inheritance system to find the parent container
			BeanFactory parentBeanFactory = getParentBeanFactory();
			//The parent container of the current container exists and there is no Bean with the specified name in the current container
			if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
				// Not found -> check parent.
				//Resolve the original name of the specified Bean name
				String nameToLookup = originalBeanName(name);
				// Delegate parent processing if AbstractBeanFactory type
				if (parentBeanFactory instanceof AbstractBeanFactory) {
					return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
							nameToLookup, requiredType, args, typeCheckOnly);
				}
				else if (args != null) {
					// Delegation to parent with explicit args.
					//Delegate parent container finds based on specified name and explicit parameters
					return (T) parentBeanFactory.getBean(nameToLookup, args);
				}
				else {
					// No args -> delegate to standard getBean method.
					//Delegate parent container to find by specified name and type
					return parentBeanFactory.getBean(nameToLookup, requiredType);
				}
			}

			// Whether the beans created need type validation or not, generally not
<5>			if (!typeCheckOnly) {
				//The Bean specified to the container tag has been created
				markBeanAsCreated(beanName);
			}

			try {
				//Get the GenericBeanDefinition object corresponding to the beanName from the container and convert it to a RootBeanDefinition object
				// Mainly solves the problem of subclasses merging common attributes of parent classes when Bean inherits
<6>				final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
				// Check BeanDefinition (abstract class or not) for a given merge
				checkMergedBeanDefinition(mbd, beanName, args);

				// Guarantee initialization of beans that the current bean depends on.
				// Processing dependent bean @DependsOn()
				// Gets the names of all dependent beans for the current Bean
<7>				String[] dependsOn = mbd.getDependsOn();
				//If there is a dependency
				if (dependsOn != null) {
					for (String dep : dependsOn) {
						//Verify that the dependency has been registered with the current Bean
						if (isDependent(beanName, dep)) {
							//Registered, throwing exception
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
						}
						//If not, register the dependent bean s first
						registerDependentBean(dep, beanName);
						//Recursively calls getBean(), Mr. Generates a dependent bean
						getBean(dep);
					}
				}

				// Create bean instance.
				//Create a single Bean
<8>				if (mbd.isSingleton()) {
					//An anonymous internal class is used to create Bean instance objects and register them with dependent objects
					sharedInstance = getSingleton(beanName, () -> {
						try {
							//Creates a specified Bean instance object, merges the definitions of child and parent classes if there is parent inheritance
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							// Explicitly remove instance from singleton cache: It might have been put there
							// eagerly by the creation process, to allow for circular reference resolution.
							// Also remove any beans that received a temporary reference to the bean.
							//Explicitly Clear Instance Objects from Container Singleton Mode Bean Cache
							destroySingleton(beanName);
							throw ex;
						}
					});
					//Gets the instance object for a given Bean
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}

				//Create multiple Bean s
				else if (mbd.isPrototype()) {
					//Prototype creates a new object each time
					Object prototypeInstance = null;
					try {
						//Load preprocessing, the default function is to register the currently created prototype object
						beforePrototypeCreation(beanName);
						//Create an instance of the specified Bean object
						prototypeInstance = createBean(beanName, mbd, args);
					}
					finally {
						//Load post-processing, the default function tells the IOC container that the prototype object for the specified Bean is no longer created
						afterPrototypeCreation(beanName);
					}
					//Gets the instance object for a given Bean
					bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
				}

				//Bean s to be created are neither Singleton s nor Prototype s
				//Lifecycle such as request, session, application, etc.
				else {
					String scopeName = mbd.getScope();
					final Scope scope = this.scopes.get(scopeName);
					//Bean definition is illegal if there is no life cycle scope configured in the Bean definition resource
					if (scope == null) {
						throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
					}
					try {
						//An anonymous internal class is used here to get an instance of a specified lifecycle range
						Object scopedInstance = scope.get(beanName, () -> {
							//pre-processing
							beforePrototypeCreation(beanName);
							try {
								return createBean(beanName, mbd, args);
							}
							finally {
								//Postprocessing
								afterPrototypeCreation(beanName);
							}
						});
						//Gets the instance object for a given Bean
						bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
					}
					catch (IllegalStateException ex) {
						throw new BeanCreationException(beanName,
								"Scope '" + scopeName + "' is not active for the current thread; consider " +
								"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
								ex);
					}
				}
			}
			catch (BeansException ex) {
				cleanupAfterBeanCreationFailure(beanName);
				throw ex;
			}
		}

		// Check if required type matches the type of the actual bean instance.
		//Type check on created Bean instance objects
<9>		if (requiredType != null && !requiredType.isInstance(bean)) {
			try {
				T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
				if (convertedBean == null) {
					throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
				}
				return convertedBean;
			}
			catch (TypeMismatchException ex) {
				if (logger.isDebugEnabled()) {
					logger.debug("Failed to convert bean '" + name + "' to required type '" +
							ClassUtils.getQualifiedName(requiredType) + "'", ex);
				}
				throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
			}
		}
		return (T) bean;
	}

The code is very long and requires some patience. Let's analyze it step by step:

  • <1>: For detailed analysis, see 2.1 Getting the original beanName

  • <2>: For detailed analysis, see 2.2 Getting single bean from cache

  • <3>: For detailed analysis, see 2.3 for the final bean instance object

  • <4>See 2.4 Prototype Dependency Check and Getting Beans from parentBeanFactory for specific analysis

  • <5>: For detailed analysis, see 2.5 tagged bean s created or about to be created

  • <6>: For detailed analysis, see 2.6 Getting BeanDefinition

  • <7>: For detailed analysis, see 2.7 bean dependent processing

  • <8>: For detailed analysis, see 2.8 instantiations of bean s with different scopes

  • <9>See 2.9 Type Conversion for specific analysis

2.4, Prototype Dependency Check and Getting Beans from parentBeanFactory

The prototype pattern dependency check with the following code:

if (isPrototypeCurrentlyInCreation(beanName)) {
	throw new BeanCurrentlyInCreationException(beanName);
	}

Follow in:

        /** Names of beans that are currently in creation */
	private final ThreadLocal<Object> prototypesCurrentlyInCreation =
			new NamedThreadLocal<>("Prototype beans currently in creation");

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
		//Remove the prototype being created from ThreadLocal
		Object curVal = this.prototypesCurrentlyInCreation.get();
		return (curVal != null &&
				(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
	}

Spring only handles circular dependencies in singleton mode, while circular dependencies on prototype mode throw exceptions directly.
Spring stores the prototype pattern beans being created in ThreadLoacl, where ThreadLoacl is used to determine if the current beans have been created.

Get the Bean from parentBeanFactory with the following code:

// Check if bean definition exists in this factory.
	//Check for BeanDefinition with the specified name in the IOC container, first check for
	//Required beans that can be obtained in the current Bean Factory, or delegate the current container if not
	//Find the parent container of the container, or, if not found, follow its inheritance system to find the parent container
	BeanFactory parentBeanFactory = getParentBeanFactory();
	//The parent container of the current container exists and there is no Bean with the specified name in the current container
	if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
		// Not found -> check parent.
		//Resolve the original name of the specified Bean name
		String nameToLookup = originalBeanName(name);
		// Delegate parent processing if AbstractBeanFactory type
		if (parentBeanFactory instanceof AbstractBeanFactory) {
			return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
					nameToLookup, requiredType, args, typeCheckOnly);
		}
		else if (args != null) {
			// Delegation to parent with explicit args.
			//Delegate parent container finds based on specified name and explicit parameters
			return (T) parentBeanFactory.getBean(nameToLookup, args);
		}
		else {
			// No args -> delegate to standard getBean method.
			//Delegate parent container to find by specified name and type
			return parentBeanFactory.getBean(nameToLookup, requiredType);
		}
	}

If there is no corresponding BeanDefinition object in the current container cache, an attempt is made to load from the parent BeanFactory and then recursively call the getBean(...) method

2.5, tag bean s are created or about to be created

The corresponding code is as follows:

//Whether the beans created need type validation or not, generally not
			if (!typeCheckOnly) {
				//The Bean specified to the container tag has been created
				markBeanAsCreated(beanName);
			}

typeCheckOnly is doGetBean(final String name, @Nullable final Class<T> requiredType,@Nullable A parameter in the final Object[] args, boolean typeCheckOnly) method that normally passes false

Then trace the markBeanAsCreated() method:

protected void markBeanAsCreated(String beanName) {
		// Not created
		if (!this.alreadyCreated.contains(beanName)) {
			synchronized (this.mergedBeanDefinitions) {
				// Check again: DCL Dual Check
				if (!this.alreadyCreated.contains(beanName)) {
					clearMergedBeanDefinition(beanName);
					// Add to created bean s collection
					this.alreadyCreated.add(beanName);
				}
			}
		}
	}

A well-known double check in single-case mode is used here

2.6, Get BeanDefinition

The corresponding code is as follows:

        //Get the GenericBeanDefinition object corresponding to the beanName from the container and convert it to a RootBeanDefinition object
	//Mainly solves the problem of subclasses merging common attributes of parent classes when Bean inherits
	final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
	// Check BeanDefinition (abstract class or not) for a given merge
	checkMergedBeanDefinition(mbd, beanName, args);

This code comment is too detailed to explain.

2.7, bean dependency processing

The corresponding code is as follows:

// Guarantee initialization of beans that the current bean depends on.
	// Processing dependent bean @DependsOn()
	//Gets the names of all dependent beans for the current Bean
<1>	String[] dependsOn = mbd.getDependsOn();
	//If there is a dependency
	if (dependsOn != null) {
		for (String dep : dependsOn) {
			//Verify that the dependency has been registered with the current Bean
<2>			if (isDependent(beanName, dep)) {
				//Registered, throwing exception
				throw new BeanCreationException(mbd.getResourceDescription(), beanName,
						"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
			}
			//If not, register the dependent bean s first
<3>			registerDependentBean(dep, beanName);
			//Recursively calls getBean(), Mr. Generates a dependent bean
<4>			getBean(dep);
		}
	}

There is a @DependsOn annotation in spring that relies on loading. For example, if the A object is not loaded until the B object is loaded, you can add the @DependsOn(value = "B") annotation to A to meet our requirements.
In fact, @DependsOn implements this code.

  • <1>, call the mbd.getDependsOn() method through the BeanDefinition we previously got from the IoC container to get all the dependencies of the current bean.

  • <2>, traverse these dependencies to determine if they are registered with the current Bean

  • <3>, no, register dependent beans first

  • <4>, recursively calls getBean(), Mr. Generates dependent bean s

<2>, traverse these dependencies to determine if they are registered with the current Bean

Code:

	
	// Save the mapping relationship between the bean and its dependencies: B - > A
	private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);

        //Save the mapping relationship between the bean and its dependencies: A - > B
	private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);

private boolean isDependent(String beanName, String dependentBeanName, @Nullable Set<String> alreadySeen) {
		if (alreadySeen != null && alreadySeen.contains(beanName)) {
			return false;
		}
		// Get the current original beanName
		String canonicalName = canonicalName(beanName);
		// Get the collection of other beans that this bean depends on
		Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);
		if (dependentBeans == null) {
			return false;
		}
		// Exists, proving that the dependency is registered with the bean
		if (dependentBeans.contains(dependentBeanName)) {
			return true;
		}
		// Recursive Detection Dependency
		for (String transitiveDependency : dependentBeans) {
			if (alreadySeen == null) {
				alreadySeen = new HashSet<>();
			}
			alreadySeen.add(beanName);
			if (isDependent(transitiveDependency, dependentBeanName, alreadySeen)) {
				return true;
			}
		}
		return false;
	}

This code is simple, mainly by getting all the dependent Beans corresponding to the current bean from the dependent BeanMap, then determining if they are registered, then checking the dependent beans recursively for dependencies and, if so, calling isDependent() check recursively

<3>, no, register dependent beans first

If a dependent Bean is not registered with the Bean, then register registerDependentBean(dep, beanName):

	// Save the mapping relationship between the bean and its dependencies: B - > A
	private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);

        //Save the mapping relationship between the bean and its dependencies: A - > B
	private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);

//Inject a dependent Bean for a specified Bean
	public void registerDependentBean(String beanName, String dependentBeanName) {
	// A quick check for an existing entry upfront, avoiding synchronization...
	//Get the original beanName
	String canonicalName = canonicalName(beanName);
	Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);
	if (dependentBeans != null && dependentBeans.contains(dependentBeanName)) {
		return;
	}

	// No entry yet -> fully synchronized manipulation of the dependentBeans Set
	//First from the container: Bean Name --> All Dependent Bean Name Collections Find Dependent Beans for a given Bean Name
	synchronized (this.dependentBeanMap) {
		//Gets all dependent Bean names for a given name Bean
		dependentBeans = this.dependentBeanMap.get(canonicalName);
		if (dependentBeans == null) {
			//Set Dependent Bean Information for Beans
			dependentBeans = new LinkedHashSet<>(8);
			this.dependentBeanMap.put(canonicalName, dependentBeans);
		}
		//Save mapping relationships into a set
		dependentBeans.add(dependentBeanName);
	}
	//Find a dependent Bean for a given named Bean from a container: Bean Name-->Dependent Bean Collection for a specified named Bean
	synchronized (this.dependenciesForBeanMap) {
		Set<String> dependenciesForBean = this.dependenciesForBeanMap.get(dependentBeanName);
		if (dependenciesForBean == null) {
			dependenciesForBean = new LinkedHashSet<>(8);
			this.dependenciesForBeanMap.put(dependentBeanName, dependenciesForBean);
		}
		//Save mapping relationships into a set
		dependenciesForBean.add(canonicalName);
	}
	}

To apply the example above, if A @DependsOn(value = "B"), that is, A depends on B, then in the registerDependentBean(dep, beanName), the parameter dep is B and the beanName is A.
This code actually registers the dependencies between bean s into two map s.

  • dependentBeanMap save (B,A)

  • Dependencies ForBeanMap Save In (A,B)

<4>, recursively calls getBean(dep), Mr. Generates a dependent bean

At this point, the getBean(beanName) method, doGetBean(beanName), is called recursively to rerun the current process to instantiate the dependent beans first.After the dependent beans are instantiated, the current beans execute next.

2.8. Instantiation of bean s in different scopes

Code:

        // Create bean instance.
	//Create a single Bean
	if (mbd.isSingleton()) {
		//An anonymous internal class is used to create Bean instance objects and register them with dependent objects
		sharedInstance = getSingleton(beanName, () -> {
			try {
				//Creates a specified Bean instance object, merges the definitions of child and parent classes if there is parent inheritance
				return createBean(beanName, mbd, args);
			}
			catch (BeansException ex) {
				// Explicitly remove instance from singleton cache: It might have been put there
				// eagerly by the creation process, to allow for circular reference resolution.
				// Also remove any beans that received a temporary reference to the bean.
				//Explicitly Clear Instance Objects from Container Singleton Mode Bean Cache
				destroySingleton(beanName);
				throw ex;
			}
		});
		//Gets the instance object for a given Bean
		bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
	}

	//Create multiple Bean s
	else if (mbd.isPrototype()) {
		//Prototype creates a new object each time
		Object prototypeInstance = null;
		try {
			//Load preprocessing, the default function is to register the currently created prototype object
			beforePrototypeCreation(beanName);
			//Create an instance of the specified Bean object
			prototypeInstance = createBean(beanName, mbd, args);
		}
		finally {
			//Load post-processing, the default function tells the IOC container that the prototype object for the specified Bean is no longer created
			afterPrototypeCreation(beanName);
		}
		//Gets the instance object for a given Bean
		bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
	}

	//Bean s to be created are neither Singleton s nor Prototype s
	//Lifecycle such as request, session, application, etc.
	else {
		String scopeName = mbd.getScope();
		final Scope scope = this.scopes.get(scopeName);
		//Bean definition is illegal if there is no life cycle scope configured in the Bean definition resource
		if (scope == null) {
			throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
		}
		try {
			//An anonymous internal class is used here to get an instance of a specified lifecycle range
			Object scopedInstance = scope.get(beanName, () -> {
				//pre-processing
				beforePrototypeCreation(beanName);
				try {
					return createBean(beanName, mbd, args);
				}
				finally {
					//Postprocessing
					afterPrototypeCreation(beanName);
				}
			});
			//Gets the instance object for a given Bean
			bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
		}
		catch (IllegalStateException ex) {
			throw new BeanCreationException(beanName,
					"Scope '" + scopeName + "' is not active for the current thread; consider " +
					"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
					ex);
		}
	}

This code is clearly divided into three parts:

  • singleton Bean instantiation

  • Prototype Bean instantiation

  • Other types of Bean instantiation (session,request, etc.)

Let's start with singleton Bean instantiation:

if (mbd.isSingleton()) {
	//An anonymous internal class is used to create Bean instance objects and register them with dependent objects
	sharedInstance = getSingleton(beanName, () -> {
	try {
		//Creates a specified Bean instance object, merges the definitions of child and parent classes if there is parent inheritance
		return createBean(beanName, mbd, args);
	}
	catch (BeansException ex) {
		// Explicitly remove instance from singleton cache: It might have been put there
		// eagerly by the creation process, to allow for circular reference resolution.
		// Also remove any beans that received a temporary reference to the bean.
		//Explicitly Clear Instance Objects from Container Singleton Mode Bean Cache
		destroySingleton(beanName);
		throw ex;
	}
	});
	//Gets the instance object for a given Bean
	bean = getObjectForBeanInstance(sharedInstance, name,beanName, mbd);
	}

Spring Bean's scope defaults to singleton.There are other scopes, such as prototype, request, session, and so on.
Different scopes have different initialization strategies.
See in detail bean creation for spring scope s.

2.9, Type Conversion

Code:

// Check if required type matches the type of the actual bean instance.
	//Type check on created Bean instance objects
	if (requiredType != null && !requiredType.isInstance(bean)) {
	try {
	        //Perform conversion
		T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
		// Transform failed, throw exception
		if (convertedBean == null) {
			throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
		}
		return convertedBean;
	}
	catch (TypeMismatchException ex) {
		if (logger.isDebugEnabled()) {
			logger.debug("Failed to convert bean '" + name + "' to required type '" +
					ClassUtils.getQualifiedName(requiredType) + "'", ex);
		}
		throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
	}
	}
	return (T) bean;

The requiredType is a parameter passed in by the get Bean () method that allows you to get beans based on the specified beanName and requiredType.

In general, however, type checking is not required, requiredType is generally null, such as getBean(beanName)

This logic is used when the requiredType is not null.

Summary:

At this point, spring loading beans, or get Bean s (), have been roughly analyzed and a few more articles will be written detailing some of these steps.

Reference resources:
Taro Source

Topics: Programming Spring Session