IOC details of Spring (based on annotation)

Posted by paulnaj on Sat, 30 May 2020 09:37:49 +0200


IOC(Inversion of Control): the so-called inversion of control is that the application itself is not responsible for the creation and maintenance of dependent objects, and the creation and maintenance of dependent objects are in the charge of external containers. In this way, the application of control is transferred to the external container, and the transfer of control is the inversion of control.

Initialization process

The initialization of Spring IOC container can be simply divided into three processes:

  • The first process is Resource location. This Resource refers to the Resource location of BeanDefinition. This process is the process of finding data for containers, just like water in buckets needs to find water first.
  • The second process is the loading process of BeanDefinition. This loading process represents the user-defined Bean as the data structure inside the Ioc container, and the data structure inside the container is Bean definition.
  • The third process is to register these beandefinitions with the IOC container. This process is to save the previous beandefinitions to the HashMap.


In the version after spring 2.0, spring also introduces annotation based configuration. Annotation is a new feature introduced in JDK 1.5, which is used to simplify Bean configuration and can replace XML configuration files. Spring IOC container has the following two processing strategies for class level annotation and class internal annotation:

  • Class level annotations: for example, @ Component, @ Repository, @ Controller, @ Service and JavaEE6's @ ManagedBean and @ Named annotations are class level annotations added to the class. Spring container scans and reads annotation Bean definition classes according to the filtering rules of annotations and registers them in Spring IOC container.
  • Class internal annotations: such as @ Autowire, @ Value, @ Resource, EJB and WebService related annotations, are all class internal annotations added to fields or methods within the class. The SpringIOC container parses the internal annotations of the Bean through the Bean post annotation processor. Next, we will analyze the source code related to Spring processing annotation according to the two processing strategies.

Main categories

Core container

The creation of Spring Bean is a typical factory mode. This series of Bean factories, i.e. IOC containers, provide many conveniences and basic services for developers to manage the dependencies between objects. In Spring, there are many implementations of IOC containers for users to choose and use. Their interrelations are as follows:

BeanFactory, as the top-level interface class, defines the basic functional specifications of IOC containers. BeanFactory has three important subclasses: ListableBeanFactory, hierarchal BeanFactory and AutowireCapableBeanFactory. But from the class diagram, we can find that the final default implementation class is DefaultListableBeanFactory, which implements all interfaces. At the same time, DefaultListableBeanFactory is also a registrar, which registers Bean information with the factory according to the encapsulated data structure.

data structure

The Spring IOC container manages all kinds of bean objects and their relationships. Bean objects are described by bean definition in the Spring implementation. The inheritance system is as follows:

Resource scanner

Annotation based method needs to load all classes under the specified path. In Spring, it is mainly implemented in ClassPathBeanDefinitionScanner class. The class diagram is as follows:


Next, we will understand the implementation principle of Spring annotation according to the source code.

There are two containers for managing annotation Bean definitions in Spring: AnnotationConfigApplicationContext and AnnotationConfigWebApplicationContext. These two classes are specialized containers for Spring annotation configuration, which directly rely on annotations as IOC containers for container configuration information. AnnotationConfigWebApplicationContext is the Web version of AnnotationConfigApplicationContext. There is almost no difference in their usage and processing of annotations. Now let's take annotation config application context as an example to analyze from the construction method as the entry:

public AnnotationConfigApplicationContext(String... basePackages) {

The constructor automatically scans all classes under the given package and its subpackages, and automatically recognizes all spring beans to register them in the container.


Let's take a look at how Spring scans the beans in the specified path and registers them in the container.

public void scan(String... basePackages) {
	Assert.notEmpty(basePackages, "At least one base package must be specified");

Jump to classpathb eanDefinitionScanner.scanner () method, call the classpath scanner entry method.

public int scan(String... basePackages) {
	//Get the number of registered beans in the container
	int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
	//Start scanner to scan given package

	// Register annotation config processors, if necessary.
	//Register annotation configuration annotation config processor
	if (this.includeAnnotationConfig) {
	//Returns the number of registered beans
	return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);

Scan the given package and subpackage, and register the scanned BeanDefinition to the container after initial property setting. The source code is as follows:

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
	Assert.notEmpty(basePackages, "At least one base package must be specified");
	//Create a collection to hold the encapsulation class scanned to Bean definition
	Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
	//Traverse scan all given packages
	for (String basePackage : basePackages) {
		//Scan the given class path to get the qualified BeanDefinition
		Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
		//Traverse the scanned Bean
		for (BeanDefinition candidate : candidates) {
			//Get the scope of a Bean
			ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
			//Set scope for Bean
			//Generate name for Bean
			String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
			//If the scanned Bean is not a Spring annotation Bean, set the default property for the Bean
			//Set whether the Bean is lazy to load, automatic dependency injection assembly properties, etc
			if (candidate instanceof AbstractBeanDefinition) {
				postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
			//If it's Spring's annotation Bean, handle the general annotation
			if (candidate instanceof AnnotatedBeanDefinition) {
				AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
			//Check whether the specified Bean has been registered in the container according to the Bean name. If so, judge whether the two Bean definitions are compatible. If not, throw an exception
			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);
				//Register the scanned Bean with the container
				registerBeanDefinition(definitionHolder, this.registry);
	return beanDefinitions;

Scan all the resource data under the package of the given path, read the metadata and encapsulate it into the BeanDefinition collection to return.

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
	if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
		return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
	else {
		return scanCandidateComponents(basePackage);
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
	Set<BeanDefinition> candidates = new LinkedHashSet<>();
	try {
		//Convert paths to resource search paths in a format
		String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
				resolveBasePackage(basePackage) + '/' + this.resourcePattern;
		//Get related resource information from resource search path
		Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
		boolean traceEnabled = logger.isTraceEnabled();
		boolean debugEnabled = logger.isDebugEnabled();
		for (Resource resource : resources) {
			if (traceEnabled) {
				logger.trace("Scanning " + resource);
			//Is the resource readable
			if (resource.isReadable()) {
				try {
					//To obtain the metadata reader for the specified resource, the metadata reader reads the meta information of the resource through ASM
					MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
					//Determine whether the class read by the meta information reader conforms to the annotation filtering rules defined by the container
					if (isCandidateComponent(metadataReader)) {
						//Get a general scan BeanDefinition through metadata reader
						ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
						if (isCandidateComponent(sbd)) {
							if (debugEnabled) {
								logger.debug("Identified candidate component class: " + resource);
						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;

Determine whether the class read by the meta information reader conforms to the annotation filtering rules defined by the container

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
	//If the annotation of the read class is in the exclusion rule, return false
	for (TypeFilter tf : this.excludeFilters) {
		if (tf.match(metadataReader, getMetadataReaderFactory())) {
			return false;
	//Returns true if the annotation of the read class is in the included annotation filtering rule
	for (TypeFilter tf : this.includeFilters) {
		if (tf.match(metadataReader, getMetadataReaderFactory())) {
			return isConditionMatch(metadataReader);
	//Returns false if the annotation of the read class is neither in the exclusion rule nor the inclusion rule
	return false;

At this point, the scan is completed, scanning all packages under the specified path and encapsulating them into the BeanDefinition collection.


First, let's look at the way to get the Bean scope, because the scope has a great impact on Spring's creation of Bean proxy objects. AnnotationScopeMetadataResolver uses the resolveScopeMetadata() method to parse the scope meta information of the annotation Bean definition class, that is, to determine whether the registered Bean is a native type or a singleton type. The source code is as follows:

public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
	ScopeMetadata metadata = new ScopeMetadata();
	if (definition instanceof AnnotatedBeanDefinition) {
		AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
		//Find the value whose attribute is Scope from the attribute of the annotation Bean definition class, that is, the value of @ Scope annotation
		//annDef.getMetadata() store all annotations and annotation values in the Bean in the map
		AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
				annDef.getMetadata(), this.scopeAnnotationType);
		//Set the value of the acquired scope annotation to the object to be returned
		if (attributes != null) {
			//Get the value of ProxyMode property and reuse it when creating proxy object
			ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
			//Set the property value of proxyMode
			if (proxyMode == ScopedProxyMode.DEFAULT) {
				proxyMode = this.defaultProxyMode;
			//Set proxyMode for returned metadata
	return metadata;

processCommonDefinitionAnnotations() of AnnotationConfigUtils class processes the Spring annotation Bean general annotation before registering the Bean with the container. The source code is as follows:

static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
	//Set the value of the Lazy annotation property
	AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
	if (lazy != null) {
	else if (abd.getMetadata() != metadata) {
		lazy = attributesFor(abd.getMetadata(), Lazy.class);
		if (lazy != null) {

	//Determine whether there is @ Primary annotation. If there is, set it as the preferred object of dependency injection assembly
	if (metadata.isAnnotated(Primary.class.getName())) {
	//If there is @ DependsOn annotation, set the Bean name to depend on
	//The container ensures that the dependent beans are instantiated before the bean is instantiated
	AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
	if (dependsOn != null) {

	AnnotationAttributes role = attributesFor(metadata, Role.class);
	if (role != null) {
	AnnotationAttributes description = attributesFor(metadata, Description.class);
	if (description != null) {

The applyscope dproxymode() method of AnnotationConfigUtils class applies the corresponding proxy mode for Bean definition according to the value of the @ Scope annotation configured in the annotation Bean definition class, which is mainly used in Spring aspect oriented programming (AOP).

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

	//Get the ProxyMode property value of the Scope annotation in the annotation Bean definition class
	ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode();
	//If the value is no, the proxy mode is not applied
	if (scopedProxyMode.equals(ScopedProxyMode.NO)) {
		return definition;
	//If the attribute value is TARGET_CLASS, returns true, otherwise, INTERFACES
	boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS);
	//Create proxy objects of corresponding patterns for registered beans
	return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass);

After completing a series of property settings for BeanDefinition, the next step is to officially register BeanDefinition into the container. Through bean D efinitionReaderUtils.registerBeanDefinition () method, the source code is as follows:

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

	//Gets the name of the resolved BeanDefinition
	String beanName = definitionHolder.getBeanName();
	//Register BeanDefinition with Spring IOC container
	registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

	// If the resolved BeanDefinition has an alias, register the alias with the Spring IOC container
	String[] aliases = definitionHolder.getAliases();
	if (aliases != null) {
		for (String alias : aliases) {
			registry.registerAlias(beanName, alias);

Jump to defaul tListableBeanFactory.registerBeanDefinition () as for the method, from the beginning of combing the core containers, it was said that DefaultListableBeanFactory is not only a container but also a registrar. The registered source code is as follows:

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 BeanDefinition
	if (beanDefinition instanceof AbstractBeanDefinition) {
		try {
			((AbstractBeanDefinition) beanDefinition).validate();
		catch (BeanDefinitionValidationException ex) {
			throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
					"Validation of bean definition failed", ex);

	BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
	if (existingDefinition != null) {
		//Whether to allow to overwrite the previous beanDefinition of the same beanName
		if (!isAllowBeanDefinitionOverriding()) {
			throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
		else if (existingDefinition.getRole() < beanDefinition.getRole()) {
			if (logger.isInfoEnabled()) {"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 + "]");
		this.beanDefinitionMap.put(beanName, beanDefinition);
	else {
		if (hasBeanCreationStarted()) {
			//Thread synchronization is required during registration to ensure data consistency
			synchronized (this.beanDefinitionMap) {
				this.beanDefinitionMap.put(beanName, beanDefinition);
				List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
				this.beanDefinitionNames = updatedDefinitions;
				//Maintain instance name set
		else {
			// Still in the start registration phase
			this.beanDefinitionMap.put(beanName, beanDefinition);
		this.frozenBeanDefinitionNames = null;

	//Check if a BeanDefinition with the same name has been registered
	if (existingDefinition != null || containsSingleton(beanName)) {
		//Reset the cache of all registered beandefinitions

Take a look at two important properties of DefaultListableBeanFactory:

//BeanDefinition for storing registration information
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
//Store all registered beannames
private volatile List<String> beanDefinitionNames = new ArrayList<>(256);

At this point, the whole process of IOC container registration is completed, and the BeanDefinition of all packages under the specified path is registered in the container.

Refresh container

After registering the specified path scan, Abstra is called ctApplicationContext.refresh Method to complete the container refresh process.

public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		// Prepare this context for refreshing.
		// Call the method that the container is ready to refresh, get the current time of the container, and set the synchronization ID for the container

		// Tell the subclass to refresh the internal bean factory.
		// Get bean information from resource path and encapsulate it into beanDefinition map of beanFactory
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		// Prepare the bean factory for use in this context.
		// The preparation of beanFactory. Configure the container features of beanFactory, such as classloader, event handler, etc

		try {
			// Allows post-processing of the bean factory in context subclasses.
			//The postprocessing work to be done after the preparation of beanFactory is completed is reserved for subclass extension

			// Invoke factory processors registered as beans in the context.
			// Execute the post processor of BeanFactory, which is executed after the BeanFactory standard initialization
			// Call the postProcessBeanFactory method of all registered beanfactorypostprocessors

			// Register bean processors that intercept bean creation.
			// Register the Post event processor for BeanFactory. BeanPostProcessor is the postprocessor of Bean, which is used to listen for the events triggered by the container

			// Initialize message source for this context.
			//Initialize MessageSource information source, i.e. internationalization processing, message binding, message parsing

			// Initialize event multicaster for this context.
			//Initialize the container event broadcaster and put it into the applicationEventMulticaster bean

			// Initialize other special beans in specific context subclasses.
			//Leave subclasses to initialize other bean s

			// Check for listener beans and register them.
			//Find ApplicationListener in all registered bean s and register event listener for event broadcaster

			// Instantiate all remaining (non-lazy-init) singletons.
			//Initialize all remaining non lazy load single instance bean s

			// Last step: publish corresponding event.
			//Complete the refresh process, initialize the container's life cycle event handler, and publish the container's life cycle events

		catch (BeansException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Exception encountered during context initialization - " +
						"cancelling refresh attempt: " + ex);

			// Destroy already created singletons to avoid dangling resources.
			// Destroy the created Bean

			// Reset 'active' flag.
			// Cancel the refresh operation and reset the synchronization identity of the container

			// Propagate exception to caller.
			throw ex;

		finally {
			// Reset common introspection caches in Spring's core, since we
			// might not ever need metadata for singleton beans anymore...
			// Reset public cache

Today we mainly analyze the IOC container registration process, so we only analyze the obtainFreshBeanFactory method. The source code is as follows:

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
	//Get the bean information from the resource path again and refresh the bean factory
	//The delegation design pattern is used here. The parent class defines the abstract refreshBeanFactory() method, which specifically calls the refreshBeanFactory() method of the subclass container
	//Returns the beanfactory property of the current entity
	return getBeanFactory();

Jump to abstractrefreshab leApplicationContext.refreshBeanFactory () method. This implementation actually refreshes the underlying BeanFactory of this context, closes the previous BeanFactory, if any, and initializes a new BeanFactory for the next phase of the context lifecycle.

protected final void refreshBeanFactory() throws BeansException {
	// Determine whether there is a BeanFactory. If there is a container, destroy the Bean in the container and close the container
	if (hasBeanFactory()) {
	try {
		//Create DefaultListableBeanFactory, IOC container
		DefaultListableBeanFactory beanFactory = createBeanFactory();
		//Set serialization id
		//Customize beanFactory and set related properties, such as setting startup parameters, enabling automatic assembly of annotations, etc
		//Call the method to load the Bean definition. This class only defines the abstract method, which is implemented through the subclass container
		synchronized (this.beanFactoryMonitor) {
			this.beanFactory = beanFactory;
	catch (IOException ex) {
		throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);

Next, I'll focus on how to load BeanDefinition on and register it into the container. AnnotationConfigW ebApplicationContext.loadBeanDefinitions () the method source code is as follows:

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
	//Set annotation Bean definition reader for container
	AnnotatedBeanDefinitionReader reader = getAnnotatedBeanDefinitionReader(beanFactory);
	//Define scanner for container set classpath Bean
	ClassPathBeanDefinitionScanner scanner = getClassPathBeanDefinitionScanner(beanFactory);

	//Get the Bean name generator of the container
	BeanNameGenerator beanNameGenerator = getBeanNameGenerator();
	//Set the Bean name generator for annotation Bean definition reader and classpath scanner
	if (beanNameGenerator != null) {
		beanFactory.registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator);

	//Get scope meta information resolver of container
	ScopeMetadataResolver scopeMetadataResolver = getScopeMetadataResolver();
	//Set scope meta information resolver for annotation Bean definition reader and classpath scanner
	if (scopeMetadataResolver != null) {

	if (!this.componentClasses.isEmpty()) {
		if (logger.isDebugEnabled()) {
			logger.debug("Registering component classes: [" +
					StringUtils.collectionToCommaDelimitedString(this.componentClasses) + "]");

	if (!this.basePackages.isEmpty()) {
		if (logger.isDebugEnabled()) {
			logger.debug("Scanning base packages: [" +
					StringUtils.collectionToCommaDelimitedString(this.basePackages) + "]");

	//Get the Bean definition resource path of the container definition
	String[] configLocations = getConfigLocations();
	//If the defined Bean definition resource path is not empty
	if (configLocations != null) {
		for (String configLocation : configLocations) {
			try {
				//Use the classloader of the current container to load the bytecode file of the location path
				Class<?> clazz = ClassUtils.forName(configLocation, getClassLoader());
				if (logger.isTraceEnabled()) {
					logger.trace("Registering [" + configLocation + "]");
			catch (ClassNotFoundException ex) {
				if (logger.isTraceEnabled()) {
					logger.trace("Could not load class for config location [" + configLocation +
							"] - trying package scan. " + ex);
				//If the container classloader fails to load the Bean definition resource that defines the path, enable the container classpath scanner to scan the classes in the given path package and its subpackages
				int count = scanner.scan(configLocation);
				if (count == 0 && logger.isDebugEnabled()) {
					logger.debug("No component classes found for specified class/package [" + configLocation + "]");

We can see that classpathb is called as well eanDefinitionScanner.sacnner () method to scan and load into the container. If it is consistent with the above scanning process, the analysis will not be repeated. Next, get the resource path of BeanDefinition in the container and load it into the container. With the above registration process has been, and does not repeat the analysis.

So far, we have completed the whole process of registering BeanDefinition under the resource path to the IOC container, and really completed all the work of IOC container initialization. Now the configuration information of the whole Bean has been established in the IOC container. The Bean definition information can be used and retrieved. The function of the IOC container is to process and maintain the registered Bean definition information. These registered Bean definition information is the basis of IOC container inversion of control. With these registered data, the container can perform dependency injection.


Inject all beandefinitions under the specified path resources to the Spring IOC container through annotation, compared with the way of XML configuration file. The process of loading resources into BeanDefinition is different, and the process of registering BeanDefinition into the container is consistent.

Topics: Spring Attribute xml JDK