In the previous article on Spring IoC source analysis (annotation-based) package scanning, we described the process of Spring retrieving beans based on annotations. In this article, we’ll explore spring’s process of parsing beans and registering them with the IOC container.

Let’s start with the following code:

ClassPathBeanDefinitionScanner class

// The classpath Bean defines the scanner to scan the given package and its subpackages
	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		// Create a collection of scanned BeanDefinition encapsulated classes
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		// Traversal scans all given packet paths
		for (String basePackage : basePackages) {
			/ / call the superclass ClassPathScanningCandidateComponentProvider method
			// Scan the given classpath to get the Bean definitions that match the criteria
10			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			// Iterate over the scanned beans
			for (BeanDefinition candidate : candidates) {
				// Get the value of the @scope annotation, which gets the Scope of the Bean
14				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				// Set the scope for the Bean
				candidate.setScope(scopeMetadata.getScopeName());
				// Generate a name for the Bean
18				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				// If the scanned Bean is not a Spring annotated Bean, set the default value for the Bean,
				// Set the Bean's auto dependency injection assembly properties, etc
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				// If the scanned Bean is a Spring annotated Bean, its general Spring annotations are processed
				if (candidate instanceof AnnotatedBeanDefinition) {
					// Handle common annotations in annotation beans, which were analyzed when analyzing annotation beans defining class readers
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				// Check whether the specified Bean needs to be registered in the container based on the Bean name, or if there is a conflict in the container
30				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					// Apply the appropriate proxy mode to the Bean according to the scope configured in the annotation
33					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					// Register scanned beans with the container
37					registerBeanDefinition(definitionHolder, this.registry); }}}return beanDefinitions;
	}
Copy the code

In the previous article, we focused on the findCandidateComponents(basePackage) method in line 10, which scans for beans that meet filtering rules from a given package path and stores them into the collection beanDefinitions. The next steps can be divided into the following aspects:

  • Iterate over the bean collection

  • Get the value of the @Scope annotation, which gets the Scope of the Bean

  • Generate a name for the Bean

  • Set default values for some of the Bean properties

  • Check that the Bean is registered in the IOC container

  • Generate the corresponding proxy schema based on the scope of the Bean

  • Put the beans into the IOC container

The second step is to get the value of the @Scope annotation, which is to get the Scope of the Bean

First look at the process of access to the Bean scope, mainly the line 14 ScopeMetadata ScopeMetadata = this. ScopeMetadataResolver. ResolveScopeMetadata (candidate); This code, we continue to follow in: AnnotationScopeMetadataResolver class

public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
		// Default is singleton
		ScopeMetadata metadata = new ScopeMetadata();
		if (definition instanceof AnnotatedBeanDefinition) {
			AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
			// Get the value of the @scope annotation
			AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
					annDef.getMetadata(), this.scopeAnnotationType);
			// Set the value of the @scope annotation to the object to be returned
			if(attributes ! =null) {
				metadata.setScopeName(attributes.getString("value"));
				// Get the value of the proxyMode attribute in the @scope annotation, which is used when creating the proxy object
				ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
				// If @scope's proxyMode attribute is DEFAULT or NO
				if (proxyMode == ScopedProxyMode.DEFAULT) {
					// Set proxyMode to NO
					proxyMode = this.defaultProxyMode;
				}
				// Set proxyMode for returned metadatametadata.setScopedProxyMode(proxyMode); }}// Returns the parsed scope meta-information object
		return metadata;
	}
Copy the code

The Scope of the Bean is implemented through the @scope annotation. Let’s look at the attributes of the @scope annotation:

You can see@ScopeAnnotations have three properties,

  • The value property is the singleton/multi-instance that we use to set up beans

  • ProxyMode is used to set the proxyMode

For a detailed analysis of the @scope annotation principle, please see this article <>, which will not be covered in detail here.

AnnotationAttributes is a wrapper class for the annotated key-value attribute that inherits the LinkedHashMap, so it is also a key-value data structure. Check the value of this variable with debug:

You can see that the values of three properties are obtained, wherevalue = prototypeThis is the scope of the Bean, where I set multiple instances, and then assign the value of the obtained annotation property toScopeMetadata.

Third, generate a name for the Bean

Back to the doScan ClassPathBeanDefinitionScanner class above the line 18 () method, we get to the Bean is assigned to the scope of the Bean. Then as the Bean name, code String beanName = this. BeanNameGenerator. GenerateBeanName (candidate, enclosing registry); Tracking, in AnnotationBeanNameGenerator class:

public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
		if (definition instanceof AnnotatedBeanDefinition) {
			String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
			if (StringUtils.hasText(beanName)) {
				// Explicit bean name found.
				returnbeanName; }}// Fallback: generate a unique default bean name.
		return buildDefaultBeanName(definition, registry);
	}
Copy the code

GetShortName (beanClassName) ¶ This code is easy to understand, first get the Bean name from the annotation, if not set in the annotation, then generate a default Bean name, using classutils.getShortName (beanClassName) to generate a lower case camel name for the class name. Such as studentController.

Step 4. Set default values for some of the Bean properties

These are the following two methods in doScan() :

// If the scanned Bean is not a Spring annotated Bean, set the default value for the Bean,
				// Set the Bean's auto dependency injection assembly properties, etc
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				// If the scanned Bean is a Spring annotated Bean, its general Spring annotations are processed
				if (candidate instanceof AnnotatedBeanDefinition) {
					// Handle common annotations in annotation beans, which were analyzed when analyzing annotation beans defining class readers
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
Copy the code

First see postProcessBeanDefinition ((AbstractBeanDefinition) candidate, beanName) : tracking in to the following methods:

public void applyDefaults(BeanDefinitionDefaults defaults) {
		/ / lazy loading
		setLazyInit(defaults.isLazyInit());
		// Load mode
		setAutowireMode(defaults.getAutowireMode());
		// Dependency check
		setDependencyCheck(defaults.getDependencyCheck());
		// The bean initialization method
		setInitMethodName(defaults.getInitMethodName());
		setEnforceInitMethod(false);
		// Bean destruction method
		setDestroyMethodName(defaults.getDestroyMethodName());
		setEnforceDestroyMethod(false);
	}
Copy the code

Give your bean some default values. BeanDefinitionDefaults is a wrapper class for bean property defaults. Take the default values for individual properties from that class and assign them to the bean. Then we see AnnotationConfigUtils. ProcessCommonDefinitionAnnotations ((AnnotatedBeanDefinition) candidate) method. Track in:

static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
		AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
		// If the Bean definition has an @lazy annotation, the Bean pre-instantiation property is set to the value of the @lazy annotation
		if(lazy ! =null) {
			abd.setLazyInit(lazy.getBoolean("value"));
		}

		else if(abd.getMetadata() ! = metadata) { lazy = attributesFor(abd.getMetadata(), Lazy.class);if(lazy ! =null) {
				abd.setLazyInit(lazy.getBoolean("value")); }}// If the Bean definition has the @primary annotation, set the Bean as the preferred object for autowiring's automatic dependency injection assembly
		if (metadata.isAnnotated(Primary.class.getName())) {
			abd.setPrimary(true);
		}
		// If there is a @dependson annotation in the Bean definition, set the name of the Bean on which the Bean depends.
		// The container will ensure that the dependent Bean is instantiated first before instantiating the Bean
		AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
		if(dependsOn ! =null) {
			abd.setDependsOn(dependsOn.getStringArray("value"));
		}

		if (abd instanceof AbstractBeanDefinition) {
			AbstractBeanDefinition absBd = (AbstractBeanDefinition) abd;
			AnnotationAttributes role = attributesFor(metadata, Role.class);
			if(role ! =null) {
				absBd.setRole(role.getNumber("value").intValue());
			}
			AnnotationAttributes description = attributesFor(metadata, Description.class);
			if(description ! =null) {
				absBd.setDescription(description.getString("value")); }}}Copy the code

This is mainly dealing with common annotations on beans such as @lazy, @primary, and @dependson. The comments are clear, so I won’t bother you here.

Fifth, check that the Bean is registered in the IOC container

Trace the if (checkCandidate(beanName, candidate)) method at line 30 in doScan() :

protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
		// Whether beanName is included
		if (!this.registry.containsBeanDefinition(beanName)) {
			return true;
		}
		// If the bean already exists in the container
		// Get an existing bean in the container
		BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
		BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
		if(originatingDef ! =null) {
			existingDef = originatingDef;
		}
		// The new bean compares the old bean
		if (isCompatible(beanDefinition, existingDef)) {
			return false;
		}
		throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +
				"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +
				"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
	}
Copy the code

The IOC container is actually a map. If it contains a beanName, the IOC container is a map. This is done by calling map.containsKey(key).

Step 6. Apply the appropriate proxy pattern to the Bean

Tracking doScan definitionHolder in () = AnnotationConfigUtils. ApplyScopedProxyMode (scopeMetadata definitionHolder, this.registry); methods

static BeanDefinitionHolder applyScopedProxyMode( ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {

		// Get the value of the proxyMode attribute of the @Scope annotation in the annotation Bean definition class
		ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode();
		// if the proxyMode attribute of the @scope annotation is set to NO, the proxyMode is not applied
		if (scopedProxyMode.equals(ScopedProxyMode.NO)) {
			return definition;
		}
		// Get the proxyMode attribute value of the configured @scope annotation, if TARGET_CLASS
		// INTERFACES returns true, or false if INTERFACES
		boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS);
		// Create a proxy object for the corresponding schema for the registered Bean
		return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass);
	}
Copy the code

Here we use the proxyMode property of the @scope annotation we obtained in step 2, and then set the proxyMode for the bean.

Step 7: Register the Bean into the IOC container

Trace line 37 of doScan() registerBeanDefinition(definitionHolder, this.registry); methods

// Register parsed BeanDefinitionHold with the container
	public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {

		// Register bean definition under primary name.
		// Get the name of the resolved BeanDefinition
		String beanName = definitionHolder.getBeanName();
		// Register BeanDefinition with the IOC container9Line registry. RegisterBeanDefinition (beanName, definitionHolder getBeanDefinition ());// Register aliases for bean name, if any.
		// If the resolved BeanDefinition has an alias, register the alias with the container
		String[] aliases = definitionHolder.getAliases();
		if(aliases ! =null) {
			for(String alias : aliases) { registry.registerAlias(beanName, alias); }}}Copy the code

Look directly at line 9 and continue tracing:

// Register resolved BeanDefiniton with the IOC container
	@Override
	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {

		// Verify that beanName and beanDefinition are not null
		Assert.hasText(beanName, "Bean name must not be empty");
		Assert.notNull(beanDefinition, "BeanDefinition must not be null");

		// Verify the parse BeanDefiniton
		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;

		// Gets the BeanDefinition of the specified beanName from the container
		oldBeanDefinition = this.beanDefinitionMap.get(beanName);
		// If it already exists
		if(oldBeanDefinition ! =null) {
			// Throw an exception if it exists but cannot be overridden
			if(! isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
						"': There is already [" + oldBeanDefinition + "] bound.");
			}
			// Overwrite the ROLE of the beanDefinition that is greater than the overwritten beanDefinition
			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 + "]"); }}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 + "]"); }}// Allow overwriting, directly overwriting existing beanDefinitionMap.
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		else {
			// Check whether the beanDefinitionMap creation phase is enabled. If so, concurrency control for beanDefinitionMap is required
			if (hasBeanCreationStarted()) {
				// Cannot modify startup-time collection elements anymore (for stable iteration)
				// Thread synchronization is required during registration to ensure data consistency (because of put, add, remove operations).
64				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;
					// Remove beanName from manualSingletonNames
					if (this.manualSingletonNames.contains(beanName)) {
						Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
						updatedSingletons.remove(beanName);
						this.manualSingletonNames = updatedSingletons; }}}else {
				// Still in startup registration phase
				this.beanDefinitionMap.put(beanName, beanDefinition);
				this.beanDefinitionNames.add(beanName);
				this.manualSingletonNames.remove(beanName);
			}
			this.frozenBeanDefinitionNames = null;
		}

		// Check if any BeanDefinitions with the same name are already registered in the IOC container
88		if(oldBeanDefinition ! =null || containsSingleton(beanName)) {
			// Update beanDefinitionNames and manualSingletonNamesresetBeanDefinition(beanName); }}Copy the code

Here is the core code for registering beans with the IOC container. The long code is broken down into several steps:

  • Validity check of beanName and beanDefinition

  • According to beanName determine whether from the IOC container has been registered according to isAllowBeanDefinitionOverriding variable to determine whether to cover

  • Perform an override or throw an exception according to the override rule if one exists

  • If it doesn’t exist, Private final Map

    beanDefinitionMap = new ConcurrentHashMap<>(256);
    ,>

At this point, the process of registering beans into the IOC container is almost complete. In fact, the IOC registration is not a mystery. Basically, beanName and beans are stored in the Map collection

When we look back step 7 code BeanDefinitionReaderUtils class registerBeanDefinition () method, Can see registry. RegisterBeanDefinition (beanName, definitionHolder getBeanDefinition ()); Now that we’re done analyzing, all we need to do is register the bean alias as well.

// Register parsed BeanDefinitionHold with the container
	public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {

		// Register bean definition under primary name.
		// Get the name of the resolved BeanDefinition
		String beanName = definitionHolder.getBeanName();
		// Register BeanDefinition with the IOC container
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

		// Register aliases for bean name, if any.
		// If the resolved BeanDefinition has an alias, register the alias with the container
		String[] aliases = definitionHolder.getAliases();
		if(aliases ! =null) {
			for(String alias : aliases) { registry.registerAlias(beanName, alias); }}}Copy the code
Conclusion:Copy the code

The IoC container is actually DefaultListableBeanFactory, it there is a map types of beanDefinitionMap variables, to store registered bean

IoC container initialization process:

  • 1. Resource positioning

Scan the. Class file in the package path and convert the Resource to Resource

  • 2. Resource loading

Class metadata is captured through the ASM framework and encapsulated in the BeanDefinition

  • 3. Resource analysis

Gets the attribute value of the annotation on the bean. Such as @ the Scope

  • 4. Generate beans

Generate beanName, set Bean defaults (lazy loading, initialization methods, etc.), proxy mode

  • 5. Register beans

Put the BeanDefinition DefaultListableBeanFactory IoC container

A few final questions to ponder:

  • BeanDefinitionMap is ConcurrentHashMap and should be thread-safe, but why sync lock at line 64?

  • We already checked for the same name beans in the container, so why check again on line 88?

ConcurrentHashMap is used for beanDefinitionMap. Please note that ConcurrentHashMap can only ensure thread-safety of operations, but does not guarantee thread-safety of the whole service.

Why will this. BeanDefinitionMap. Put (beanDefinition beanName) also in the synchronized code block?

We already know that beanDefinitionNames are saved in the order in which they were registered, so you must also synchronize actions that you put in, otherwise the order may be wrong

Why do I have to do a copy-like operation (addAll) to add elements to the collection after locking?

If we call Spring methods that need to iterate beanDefinitionNames during business operations, these iterators are usually used. We know that if we add or remove sets during iteration, If you are not familiar with the fast failure mechanism, please search for it yourself. To avoid this, I chose to create a new collection and copy it