Spring: register beanDefinition with Spring IOC annotation method

Keywords: Spring Junit Java Lombok

Article directory

1. beauties

2. overview

When Spring container is started, the main process and related core logic of annotation bean loading are combed.

3. cases

3.1 case 1

bean

package com.spring.boot.annotation;

import lombok.Data;

@Data
public class TestBean {
    private String name;
}

Initialize string bean

package com.spring.boot.annotation;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
class NameConfig {

    @Bean
    String name() { return "foo"; }
}

To configure

package com.spring.boot.annotation;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(NameConfig.class)
//@Import(AspectJAutoProxyRegistrar.class)
//@ImportResource("org/springframework/aop/aspectj/AfterAdviceBindingTests.xml")
class AutowiredConfig {

    @Autowired
    private String name;

    @Bean
    TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(name);
        return testBean;
    }
}

Test class

package com.spring.boot.annotation;

import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

import static org.junit.Assert.*;
import static org.springframework.util.StringUtils.uncapitalize;

public class NameConfigTest {

    @Test
    public void scanAndRefresh() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.scan("org.springframework.context.annotation6");
        context.refresh();

//        context.getBean(uncapitalize(ConfigForScanning.class.getSimpleName()));
//        context.getBean("testBean"); // contributed by ConfigForScanning
//        context.getBean(uncapitalize(ComponentForScanning.class.getSimpleName()));
//        context.getBean(uncapitalize(Jsr330NamedForScanning.class.getSimpleName()));
        Map<String, Object> beans = context.getBeansWithAnnotation(Configuration.class);
        assertEquals(1, beans.size());
    }

    @Test
    public void registerAndRefresh() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(AutowiredConfig.class);
        //context.register(Config.class, NameConfig.class);
        context.refresh();

        context.getBean("testBean");
        context.getBean("name");
        Map<String, Object> beans = context.getBeansWithAnnotation(Configuration.class);
        assertEquals(2, beans.size());
    }

}

4. Container initialization

Let's start with a few very important objects in the Spring initialization container

  1. BeanDefinition object: each bean in the container will have a corresponding BeanDefinition instance. This instance is responsible for saving all necessary information of the bean object, including the class type of the bean object, whether it is an abstract class, construction methods and parameters, other properties, etc.
  2. BeanDefinitionRegistry object: abstracts the bean registration logic. Register BeanDefinition, remove BeanDefinition, getBeanDefinition and other methods to register and manage BeanDefinition.
  3. BeanFactory object: the management logic of the bean is abstracted. It mainly includes getBean, containBean, getType, getAliases and other methods to manage beans.
  4. DefaultListableBeanFactory object: as a general beanFactory implementation, it also implements the BeanDefinitionRegistry interface, so it undertakes the bean registration management, which is the beanFactory of the commonly used registration management beans.

5.AnnotationConfigApplicationContext

public AnnotationConfigApplicationContext() {
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}
	public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) {
		super(beanFactory);
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}

	//Register the annotation bean directly to the container, and pass the configuration class involved to the constructor to automatically register the bean in the corresponding configuration class to the container
	public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
		this();
		register(annotatedClasses);
		refresh();
	}
}

It can be seen from the source code that the null parameter construction method will initialize sacner and reader first to prepare for bean resolution and registration, while the container does not contain any bean information. From the comments

/**
     * Create a new AnnotationConfigApplicationContext that needs to be populated
     * through {@link #register} calls and then manually {@linkplain #refresh refreshed}.
 */
 

It can be seen that if you call the null parameter construction, you need to manually call its register() method to register the configuration class, and call the refresh() method to refresh the container, triggering the loading, parsing and registration process of the container for the annotation Bean.

Where is BeanFactory initialized?

From the annotationconfidapplicationcontext parent class, you can find the initialization of beanFactory.

GenericApplicationContext#new
public GenericApplicationContext() {
	this.beanFactory = new DefaultListableBeanFactory();
}

In the process of new DefaultListableBeanFactory(), all the properties of the parent class will be initialized and various configurations will be loaded, such as beanClassLoader,InstantiationStrategy, etc.

In the process of initializing AnnotatedBeanDefinitionReader, spring inner post processor will be initialized, including BeanPostProcessor and beanfactory postprocessor (such as

ConfigurationClassPostProcessor,AutowiredAnnotationBeanPostProcessor). 
AnnotatedBeanDefinitionReader#new

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
		this.registry = registry;
		this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}
	

AnnotationConfigUtils#registerAnnotationConfigProcessors

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
			BeanDefinitionRegistry registry, @Nullable Object source) {

		DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
		if (beanFactory != null) {
			if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
				beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
			}
			if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
				beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
			}
		}

		Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

		if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
		}
		...
		return beanDefs;
}

When initializing ClassPathBeanDefinitionScanner, the filter will be registered, that is, what annotation needs to be scanned, such as @ Component.

protected void registerDefaultFilters() {
		//Add @ Component annotation class to the filter rule to be included, and notice @ Repository in Spring
		//@Both Service and @ Controller are components, because these annotations add @ Component annotation
		this.includeFilters.add(new AnnotationTypeFilter(Component.class));
		//Gets the class loader of the current class
		ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
		try {
			//Add the @ ManagedBean annotation of JavaEE6 to the filter rule to be included
			this.includeFilters.add(new AnnotationTypeFilter(
					((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
			logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
		}
		catch (ClassNotFoundException ex) {
			// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
		}
		try {
			//Add @ Named annotation to the filter rule to include
			this.includeFilters.add(new AnnotationTypeFilter(
					((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
			logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
		}
		catch (ClassNotFoundException ex) {
			// JSR-330 API not available - simply skip.
		}
	}

The initialization of the container is complete. Start to register bean Definition

5.1 registerBean

Spring provides two ways to register bean s: one is to load through scanning path, the other is to register through class file. First, let's look at class file registration.

5.1.1 class file registration

@Test
public void registerAndRefresh() {
	AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
	context.register(AutowiredConfig.class);
	context.refresh();

	context.getBean("testBean");
	context.getBean("name");
	Map<String, Object> beans = context.getBeansWithAnnotation(Configuration.class);
	assertEquals(2, beans.size());
}

The main process is as follows:

5.1.2 AnnotationBeanDefinitionReader#doRegisterBean

<T> void doRegisterBean(Class<T> beanClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name,
			@Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {

		//According to the specified annotation Bean definition class, create the encapsulated data structure of annotation Bean in Spring container
		AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
		//Used to parse the class annotated with @ condition annotation, skipping if the condition is not met
		if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
			return;
		}

		//Set the callback to create the bean instance
		abd.setInstanceSupplier(instanceSupplier);
		//Parsing the scope of annotation Bean definition
		ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
		//Set scope for annotation Bean definition
		abd.setScope(scopeMetadata.getScopeName());
		//Generate Bean names for annotation beans
		String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));

		//Handling general annotations in annotation Bean definitions
		AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);

		//If an additional qualifier annotation is used when registering the annotation Bean definition with the container, the qualifier annotation is resolved.
		//It is mainly about the configured constraints of autowiring auto dependency injection assembly, namely @ Qualifier annotation. Spring auto dependency injection assembly is assembled by type by default,
		// By name if @ Qualifier is used
		if (qualifiers != null) {
			for (Class<? extends Annotation> qualifier : qualifiers) {
				//If @ Primary annotation is configured, set the Bean as the preferred choice for autowiring auto dependency injection assembly
				if (Primary.class == qualifier) {
					abd.setPrimary(true);
				}
				//If @ Lazy annotation is configured, the Bean is set to be non delayed initialization. If it is not configured, the Bean is pre instantiated
				else if (Lazy.class == qualifier) {
					abd.setLazyInit(true);
				}
				else {
					//If annotations other than @ Primary and @ Lazy are used, add a
					//Autowiring automatically depends on the injection assembly qualifier. The Bean is entering autowiring
					//When automatic dependency injection assembly, assemble the Bean specified by the name qualifier
					abd.addQualifier(new AutowireCandidateQualifier(qualifier));
				}
			}
		}
		for (BeanDefinitionCustomizer customizer : definitionCustomizers) {
			customizer.customize(abd);
		}

		//Create a Bean definition object with a specified Bean name to encapsulate the annotation Bean definition class data
		BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
		//Create the corresponding proxy object according to the scope configured in the annotation Bean definition class
		definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
		//Register annotation Bean class definition object with IoC container
		BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
	}

It can be seen that it is to resolve annotatedClass to BeanDefinition, encapsulate bean properties such as @ Lazy, and then call beanFactory registration:
DefaultListableBeanFactory#registerBeanDefinition

/**
	 * Register resolved BeanDefiniton with IOC container
	 * @param beanName the name of the bean instance to register
	 * @param beanDefinition definition of the bean instance to register
	 * @throws BeanDefinitionStoreException
	 */
	@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");

		//Verify the resolved BeanDefiniton. If beanDefinition is an AbstractBeanDefinition instance, verify
		if (beanDefinition instanceof AbstractBeanDefinition) {
			try {
				//Validation cannot combine static factory methods with method overrides (static factory methods must create instances). The validation here is for the property methodOverrides of AbstractBeanDefinition
				((AbstractBeanDefinition) beanDefinition).validate();
			}
			catch (BeanDefinitionValidationException ex) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Validation of bean definition failed", ex);
			}
		}

		// 2. Priority attempts to load BeanDefinition from cache
		BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
		if (existingDefinition != null) {
			// beanName already exists and cannot be overwritten, throw an exception
			if (!isAllowBeanDefinitionOverriding()) {
				throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
			}
			// Use the new BeanDefinition to overwrite the loaded BeanDefinition. if else, there is only log printing and no real code. Delete for convenience of reading
			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 + "]");
				}
			}
			//Overwrite beanDefinition is different from the overwritten beanDefinition. Print the debug log
			else if (!beanDefinition.equals(existingDefinition)) {
				if (logger.isDebugEnabled()) {
					logger.debug("Overriding bean definition for bean '" + beanName +
							"' with a different definition: replacing [" + existingDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			//Others, print debug logs
			else {
				if (logger.isTraceEnabled()) {
					logger.trace("Overriding bean definition for bean '" + beanName +
							"' with an equivalent definition: replacing [" + existingDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			//Allow to overwrite, directly overwrite the original BeanDefinition to beanDefinitionMap.
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		// 3. If there is no corresponding BeanDefinition in the cache, register directly
		else {
			// If the bean definition has been marked as created (to solve the circular dependency of a single bean)
			if (hasBeanCreationStarted()) {
				// Cannot modify startup-time collection elements anymore (for stable iteration)
				//Thread synchronization is required during registration to ensure data consistency
				synchronized (this.beanDefinitionMap) {
					// Save beanDefinition to beanDefinitionMap
					this.beanDefinitionMap.put(beanName, beanDefinition);
					// Create a list < string > and add the cached beanDefinitionNames and the newly resolved beannames to the collection to expand the beanDefinitionNames again
					List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
					//Add pre expansion
					updatedDefinitions.addAll(this.beanDefinitionNames);
					//Add beanName to updatedDefinitions
					updatedDefinitions.add(beanName);
					// Assign updatedDefinitions to beanDefinitionNames
					this.beanDefinitionNames = updatedDefinitions;
					// If the manualSingletonNames contains the newly registered beanName
					removeManualSingletonName(beanName);
				}
			}
			else {
				// Still in startup registration phase
				// Maintain beanDefinition information to cache
				// beanDefinitionMap-->(key->beanName,value->beanDefinition)
				this.beanDefinitionMap.put(beanName, beanDefinition);
				// Beandefinitionnames -- > maintains the beanName collection
				this.beanDefinitionNames.add(beanName);
				// manualSingletonNames caches the manually registered singleton beans, so you need to call the remove method to prevent the bean name from repeating
				// For example: xmlBeanFactory.registerSingleton("myDog", new Dog());
				// You can register the singleton bean with the manual singleton names
				removeManualSingletonName(beanName);
			}
			this.frozenBeanDefinitionNames = null;
		}

		//Check if a BeanDefinition with the same name is registered in the IOC container
		// 4. Reset BeanDefinition,
		// The currently registered bean definition already exists in the bean definition map cache,
		// Or its instance already exists in the cache of the singleton bean
		if (existingDefinition != null || containsSingleton(beanName)) {
			//Reset the cache of all registered beandefinitions
			resetBeanDefinition(beanName);
		}
	}

The registration of BeanDefinition is divided into the following steps:

  1. Fetch the bean definition of the current bean from the cache
  2. If it exists, overwrite the original with the current BeanDefinition
  3. If not, determine whether the current bean is created and start
  4. If the creation is not started, the current BeanDefinition and beanName are put into the cache
  5. If you have started to create it, put the current BeanDefinition and beanName into the cache. If the current bean is a manual singleton bean, move the current beanName out of the manual singleton Bean Name, that is to say, it becomes a normal bean. The manual singleton bean here refers to the following types of beans:

5.2 scanBean mode

New class

package com.spring.boot.annotation;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ConfigForScanning {
    @Bean
    public TestBean testBean() {
        return new TestBean();
    }
}

package com.spring.boot.annotation;

import org.springframework.stereotype.Component;

@Component
public class ComponentForScanning {
}

package com.spring.boot.annotation;

import javax.inject.Named;

@Named
public class Jsr330NamedForScanning {

}

Test class

 @Test
    public void scanAndRefresh() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.scan("com.spring.boot.annotation");
        context.refresh();

        context.getBean(uncapitalize(ConfigForScanning.class.getSimpleName()));
        context.getBean("testBean"); // contributed by ConfigForScanning
        context.getBean(uncapitalize(ComponentForScanning.class.getSimpleName()));
        context.getBean(uncapitalize(Jsr330NamedForScanning.class.getSimpleName()));
        Map<String, Object> beans = context.getBeansWithAnnotation(Configuration.class);
        assertEquals(1, beans.size());
    }

Let's look at the context.scan() method first. The main process is as follows:

5.2.1 ClassPathBeanDefinitionScanner#doScan

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		for (String basePackage : basePackages) {
			//Scan the given class path to get the qualified Bean definition
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				//Get the value of the @ Scope annotation in the Bean definition class, that is, get the Scope of the Bean
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				//Generate bean name
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				//If the scanned Bean is not a Spring annotation Bean, set the default value for the Bean,
				//Set automatic dependency injection assembly properties of Bean
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				//If the scanned Bean is a Spring annotation Bean, its general Spring annotation will be processed
				if (candidate instanceof AnnotatedBeanDefinition) {
					//Handling common annotations in annotation beans
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				//Check whether the specified Bean needs to be registered in the container or conflicts in the container according to the Bean name
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					//According to the scope configured in the annotation, apply the corresponding proxy mode for the Bean
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}
	

In fact, this is similar to registerBean, but there is only one more step to get the matching bean from the scan path and encapsulate it into beanDefinition, and then it is the process of registerBean

ClassPathScanningCandidateComponentProvider#scanCandidateComponents

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
		Set<BeanDefinition> candidates = new LinkedHashSet<>();
		try {
			//Resolve the given package path, this.resourcePattern = "* * / *. class",
			//ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX="classpath:"
			//The resolveBasePackage method converts ".. in the package name to" / "in the file system
			String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
					resolveBasePackage(basePackage) + '/' + this.resourcePattern;
			//Resolve the given package path to a Spring resource object
			Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
			boolean traceEnabled = logger.isTraceEnabled();
			boolean debugEnabled = logger.isDebugEnabled();
			for (Resource resource : resources) {
				if (traceEnabled) {
					logger.trace("Scanning " + resource);
				}
				if (resource.isReadable()) {
					try {
						//To obtain the metadata reader for the specified resource, the meta information reader reads the resource meta information through assembly (ASM)
						MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
						//If the scanned class conforms to the filter rules configured by the container
						if (isCandidateComponent(metadataReader)) {
							//Read the Bean definition meta information in resource bytecode through assembly (ASM)
							ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
							//Set Bean definition from resource
							sbd.setResource(resource);
							//Set configuration resource object for metadata element
							sbd.setSource(resource);
							//Check whether the Bean is an instantiable object
							if (isCandidateComponent(sbd)) {
								if (debugEnabled) {
									logger.debug("Identified candidate component class: " + resource);
								}
								candidates.add(sbd);
							}
							else {
								if (debugEnabled) {
									logger.debug("Ignored because not a concrete top-level class: " + resource);
								}
							}
						}
						else {
							if (traceEnabled) {
								logger.trace("Ignored because not matching any filter: " + resource);
							}
						}
					}
					catch (Throwable ex) {
						throw new BeanDefinitionStoreException(
								"Failed to read candidate component class: " + resource, ex);
					}
				}
				else {
					if (traceEnabled) {
						logger.trace("Ignored because not readable: " + resource);
					}
				}
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
		}
		return candidates;
	}


1058 original articles published, 437 praised, 1.28 million visitors+
His message board follow

Posted by LoStEdeN on Mon, 03 Feb 2020 04:20:53 -0800