Source code analysis of the simplest Spring IOC container

preface

Many articles analyze xml configuration, but now Spring Boot development is mostly based on annotations. This paper analyzes the source code of Spring IOC container from the perspective of annotation.

edition:

  • Spring Boot: 2.1.6.RELEASE
  • Spring FrameWork: 5.1.8.RELEASE
  • Java 8

Part of the article is from: https://www.javadoop.com/post/spring-ioc

BeanDefinition

The BeanDefinition interface defines a bean instance that contains properties, constructor parameters, and other specific information.

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {

	// There are only two types of ConfigurableBeanFactory: singleton and prototype.
	// request, session, etc. are Web-based extensions
	String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
	String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
	
	// unimportance
	int ROLE_APPLICATION = 0;
	int ROLE_SUPPORT = 1;
	int ROLE_INFRASTRUCTURE = 2;


	// Set the information of the parent Bean (inherit the configuration information of the parent Bean)
	void setParentName(@Nullable String parentName);
	
	@Nullable
	String getParentName();

	// Set the class name of the Bean and generate an instance through reflection
	void setBeanClassName(@Nullable String beanClassName);

	// Returns the class name of the current Bean
	String getBeanClassName();


	void setScope(@Nullable String scope);

	@Nullable
	String getScope();

	// Delay initialization
	void setLazyInit(boolean lazyInit);

	boolean isLazyInit();

	// Set all beans that this Bean depends on, not those marked with @ Autowire
	void setDependsOn(@Nullable String... dependsOn);

	@Nullable
	String[] getDependsOn();

	// Set whether this Bean can be injected into other beans, which is only valid for injection according to type,
   // If you inject by name, even if false is set here, it is OK
	void setAutowireCandidate(boolean autowireCandidate);

	boolean isAutowireCandidate();

	// For multiple implementations of the same interface, if you do not specify a name, Spring will give priority to bean s with primary set to true
	void setPrimary(boolean primary);

	boolean isPrimary();

	// If the Bean is generated by the factory method, specify the factory name; Otherwise, it is generated by reflection
	void setFactoryBeanName(@Nullable String factoryBeanName);

	@Nullable
	String getFactoryBeanName();

	// Specifies the name of the factory method in the factory class
	void setFactoryMethodName(@Nullable String factoryMethodName);

	@Nullable
	String getFactoryMethodName();

	// Returns the constructor parameters of the bean
	ConstructorArgumentValues getConstructorArgumentValues();

	default boolean hasConstructorArgumentValues() {
		return !getConstructorArgumentValues().isEmpty();
	}

	// The attribute value in the Bean and the returned instance will be changed during Bean factory post processing
	MutablePropertyValues getPropertyValues();

	default boolean hasPropertyValues() {
		return !getPropertyValues().isEmpty();
	}

	void setInitMethodName(@Nullable String initMethodName);

	@Nullable
	String getInitMethodName();

	void setDestroyMethodName(@Nullable String destroyMethodName);

	@Nullable
	String getDestroyMethodName();

	
	void setRole(int role);
	int getRole();
	void setDescription(@Nullable String description);
	@Nullable
	String getDescription();

	// Read-only attributes
	boolean isSingleton();
	boolean isPrototype();
	boolean isAbstract();
	@Nullable
	String getResourceDescription();
	@Nullable
	BeanDefinition getOriginatingBeanDefinition();
}

AnnotationConfigUtils#processCommonDefinitionAnnotations(...)

static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
	AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
	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 (metadata.isAnnotated(Primary.class.getName())) {
		abd.setPrimary(true);
	}
	AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
	if (dependsOn != null) {
		abd.setDependsOn(dependsOn.getStringArray("value"));
	}

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

You can see that the processCommonDefinitionAnnotations method will fill in the AnnotatedBeanDefinition according to the annotations, which are:

  • Lazy
  • Primary
  • DependsOn
  • Role
  • Description

Look up at the call and find that it will be registered as a bean definition in ConfigurationClassBeanDefinitionReader#registerBeanDefinitionForImportedConfigurationClass.

private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {
	AnnotationMetadata metadata = configClass.getMetadata();
	AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);

	ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);
	configBeanDef.setScope(scopeMetadata.getScopeName());
	String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
	// 1. Fill in configBeanDef with annotations
	AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);

	BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
	definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
	// 2. Register the bean definition in the registry
	this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
	configClass.setBeanName(configBeanName);

	if (logger.isTraceEnabled()) {
		logger.trace("Registered bean definition for imported class '" + configBeanName + "'");
	}
}

It will eventually be called by the invokebeanfactoryprocessors (beanfactory) method of AbstractApplicationContext#refresh.

@Override
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		// Prepare this context for refreshing.
		prepareRefresh();

		// Tell the subclass to refresh the internal bean factory.
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		// Prepare the bean factory for use in this context.
		prepareBeanFactory(beanFactory);

		try {
			// Allows post-processing of the bean factory in context subclasses.
			postProcessBeanFactory(beanFactory);

			// Invoke factory processors registered as beans in the context.
			invokeBeanFactoryPostProcessors(beanFactory);

			// Register bean processors that intercept bean creation.
			registerBeanPostProcessors(beanFactory);

			// Initialize message source for this context.
			initMessageSource();

			// Initialize event multicaster for this context.
			initApplicationEventMulticaster();

			// Initialize other special beans in specific context subclasses.
			onRefresh();

			// Check for listener beans and register them.
			registerListeners();

			// Instantiate all remaining (non-lazy-init) singletons.
			finishBeanFactoryInitialization(beanFactory);

			// Last step: publish corresponding event.
			finishRefresh();
		}

		...
	}
}

Introduction to BeanFactory

BeanFactory is a factory that produces beans. It is responsible for producing and managing various bean instances. As can be seen from the figure below, ApplicationContext is also a BeanFactory. If BeanFactory is the heart of Spring, then ApplicationContext is the complete body.

ApplicationContext is a general interface that provides configuration information when an application runs. ApplicationContext cannot be changed when the program is running, but the implementation class can re-enter the configuration information.

There are many implementation classes of ApplicationContext, such as AnnotationConfigApplicationContext, AnnotationConfigWebApplicationContext, ClassPathXmlApplicationContext, FileSystemXmlApplicationContext, XmlWebApplicationContext, etc. What we analyzed above is the AnnotationConfigApplicationContext, which provides configuration information in the form of annotation, so we don't need to write XML configuration files, which is very concise.

Web container startup process

This article is developed using Spring Boot, and its startup code is:

@SpringBootApplication
@EnableScheduling
@EnableAspectJAutoProxy
public class AppApplication {

    public static void main(String[] args) {
        SpringApplication.run(AppApplication.class, args);
    }

}

The core point is this sentence:

SpringApplication.run(AppApplication.class, args);

The code of SpringApplication will not be analyzed. It is clear that the purpose of looking at the source code this time is to analyze the container source code. The startup process of Spring Boot and other information are ignored, because the Spring code is really complex. After analyzing the run method above, we will eventually trace it to Spring application #run (...) method.

public ConfigurableApplicationContext run(String... args) {
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	configureHeadlessProperty();
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting();
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
		configureIgnoreBeanInfo(environment);
		Banner printedBanner = printBanner(environment);
		context = createApplicationContext();
		exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
				new Class[] { ConfigurableApplicationContext.class }, context);
		prepareContext(context, environment, listeners, applicationArguments, printedBanner);
		refreshContext(context);
		afterRefresh(context, applicationArguments);
		stopWatch.stop();
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
		}
		listeners.started(context);
		callRunners(context, applicationArguments);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, listeners);
		throw new IllegalStateException(ex);
	}

	try {
		listeners.running(context);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, null);
		throw new IllegalStateException(ex);
	}
	return context;
}

Related to context are the following three codes:

prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);

The refreshContext method refreshes the given context:

private void refreshContext(ConfigurableApplicationContext context) {
	refresh(context);
	if (this.registerShutdownHook) {
		try {
			context.registerShutdownHook();
		}
		catch (AccessControlException ex) {
			// Not allowed in some environments.
		}
	}
}
protected void refresh(ApplicationContext applicationContext) {
	Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
	((AbstractApplicationContext) applicationContext).refresh();
}

You will find that the AbstractApplicationContext#refresh method is finally called. Note reference from: https://www.javadoop.com/post/spring-ioc

@Override
public void refresh() throws BeansException, IllegalStateException {
   // A lock, or else refresh() is not over, and you start or destroy the container again. That's a mess
   synchronized (this.startupShutdownMonitor) {

      // Prepare the work, record the start time of the container, mark the "started" status, and handle the placeholder in the configuration file
      prepareRefresh();

      // This is a key step. After this step is completed, the configuration file will be parsed into Bean definitions and registered in BeanFactory,
      // Of course, the Bean mentioned here has not been initialized, but the configuration information has been extracted,
      // Registration only saves these information to the registration center (in the final analysis, the core is a beanname - > beandefinition map)
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Set the class loader of BeanFactory, add several beanpostprocessors, and manually register several special beans
      prepareBeanFactory(beanFactory);

      try {
         // [here you need to know the knowledge of beanfactoryprocessor. If a Bean implements this interface,
         // After the container is initialized, Spring will be responsible for calling the postProcessBeanFactory method inside.]

         // Here is the extension point provided to subclasses. When you get here, all beans have been loaded and registered, but they have not been initialized
         // For specific subclasses, you can add some special beanfactoryprocessor implementation classes or do something at this step
         postProcessBeanFactory(beanFactory);
         // Call the postProcessBeanFactory(factory) method of each implementation class of beanfactoryprocessor
         invokeBeanFactoryPostProcessors(beanFactory);

         // Register the implementation class of BeanPostProcessor. Pay attention to the difference between BeanPostProcessor and BeanFactoryPostProcessor
         // This interface has two methods: postProcessBeforeInitialization and postProcessAfterInitialization
         // The two methods are executed before and after Bean initialization. Note that the Bean has not yet been initialized
         registerBeanPostProcessors(beanFactory);

         // Initialize the MessageSource of the current ApplicationContext. Internationalization will not be expanded here, otherwise it will be endless
         initMessageSource();

         // Initialize the event broadcaster of the current ApplicationContext, which will not be expanded here
         initApplicationEventMulticaster();

         // You can know from the method name that a typical template method (hook method),
         // Specific subclasses can initialize some special beans here (before initializing singleton beans)
         onRefresh();

         // Register the event listener. The listener needs to implement the ApplicationListener interface. This is not our focus, too
         registerListeners();

         // Focus, focus, focus
         // Initialize all singleton beans
         //(except for lazy init)
         finishBeanFactoryInitialization(beanFactory);

         // Finally, broadcast the event and the ApplicationContext is initialized
         finishRefresh();
      }

      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 initialized singleton Beans to prevent some Beans from occupying resources all the time
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

The core process is the content in the try code block. We should understand the overall principle. This article cannot analyze it line by line and sentence by sentence. If you do that, it will completely become a dictionary

bean loading

bean loaded calling function: org springframework. beans. factory. support. AbstractBeanFactory#doGetBean

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
		@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

	// Extract the name of the corresponding bean
	final String beanName = transformedBeanName(name);
	Object bean;

	// 1. Important, important, important!
	// Create a singleton bean to avoid circular dependency and try to get it from the cache
	Object sharedInstance = getSingleton(beanName);
	if (sharedInstance != null && args == null) {
		if (logger.isTraceEnabled()) {
			if (isSingletonCurrentlyInCreation(beanName)) {
				logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
						"' that is not fully initialized yet - a consequence of a circular reference");
			}
			else {
				logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
			}
		}
		bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
	}

	else {
		// Circular dependency exists
		if (isPrototypeCurrentlyInCreation(beanName)) {
			// Prototype mode throws exceptions directly (circular dependency can only be solved in singleton mode)
			throw new BeanCurrentlyInCreationException(beanName);
		}

		// Check if bean definition exists in this factory.
		BeanFactory parentBeanFactory = getParentBeanFactory();
		if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
			// Not found -> check parent.
			String nameToLookup = originalBeanName(name);
			if (parentBeanFactory instanceof AbstractBeanFactory) {
				return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
						nameToLookup, requiredType, args, typeCheckOnly);
			}
			else if (args != null) {
				// Delegation to parent with explicit args.
				return (T) parentBeanFactory.getBean(nameToLookup, args);
			}
			else if (requiredType != null) {
				// No args -> delegate to standard getBean method.
				return parentBeanFactory.getBean(nameToLookup, requiredType);
			}
			else {
				return (T) parentBeanFactory.getBean(nameToLookup);
			}
		}

		// If you don't just do type checking, you need to create bean s and make records
		if (!typeCheckOnly) {
			markBeanAsCreated(beanName);
		}

		try {
			// Get the RootBeanDefinition. If the specified beanName is a child bean, the parent class attribute needs to be merged
			final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
			checkMergedBeanDefinition(mbd, beanName, args);

			// If there are dependencies, you need to recursively instantiate the dependent bean s
			String[] dependsOn = mbd.getDependsOn();
			if (dependsOn != null) {
				for (String dep : dependsOn) {
					if (isDependent(beanName, dep)) {
						throw new BeanCreationException(mbd.getResourceDescription(), beanName,
								"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
					}
					registerDependentBean(dep, beanName);
					try {
						getBean(dep);
					}
					catch (NoSuchBeanDefinitionException ex) {
						throw new BeanCreationException(mbd.getResourceDescription(), beanName,
								"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
					}
				}
			}

			// Create bean instance
			
			// Creation of Singleton pattern
			if (mbd.isSingleton()) {
				sharedInstance = getSingleton(beanName, () -> {
					try {
						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.
						destroySingleton(beanName);
						throw ex;
					}
				});
				bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
			}

			// Prototype schema creation
			else if (mbd.isPrototype()) {
				Object prototypeInstance = null;
				try {
					beforePrototypeCreation(beanName);
					prototypeInstance = createBean(beanName, mbd, args);
				}
				finally {
					afterPrototypeCreation(beanName);
				}
				bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
			}

			else {
				String scopeName = mbd.getScope();
				final Scope scope = this.scopes.get(scopeName);
				if (scope == null) {
					throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
				}
				try {
					Object scopedInstance = scope.get(beanName, () -> {
						beforePrototypeCreation(beanName);
						try {
							return createBean(beanName, mbd, args);
						}
						finally {
							afterPrototypeCreation(beanName);
						}
					});
					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 whether the requiredType is the actual type of the bean. If it is not, it will be converted. If it is not successful, an exception will be thrown
	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.isTraceEnabled()) {
				logger.trace("Failed to convert bean '" + name + "' to required type '" +
						ClassUtils.getQualifiedName(requiredType) + "'", ex);
			}
			throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
		}
	}
	return (T) bean;
}

You can see that the loading of bean s is quite complex. The loading steps are as follows:

  1. Transform corresponding beanName
  2. Try loading a singleton from the cache
  3. bean instantiation
  4. Dependency checking of prototype patterns
  5. Detect parentBeanFactory
  6. Convert configuration file to RootBeanDefinition
  7. Looking for dependencies
  8. Create bean s for different scope s
  9. Type conversion

FactoryBean

BeanFactory mentioned earlier, and here comes another FactoryBean... It is said that Spring provides the implementation of more than 70 factorybeans, which shows its position in the Spring framework. They hide the details of instantiating complex beans and bring convenience to the upper application.

public interface FactoryBean<T> {

	// Return the bean instance created by FactoryBean. If isSingleton() returns true, the instance will be placed in the singleton cache pool of Spring container
	@Nullable
	T getObject() throws Exception;

	// Returns the bean type created by FactoryBean
	@Nullable
	Class<?> getObjectType();

	default boolean isSingleton() {
		return true;
	}
}

Cyclic dependence

Circular dependency is a circular reference. Two or more bean s hold each other. So how does Spring solve circular dependency?

There are three cases of circular dependency in Spring:

  1. Constructor loop dependency
  2. setter cyclic dependency
  3. Dependency handling of prototype scope

The constructor loop dependency cannot be solved, because a bean must first pass through the constructor when it is created, but the constructor is interdependent, which is equivalent to multi-threaded deadlock in Java.

The dependency caused by setter injection is completed by exposing the beans that have just completed the constructor injection but have not completed other steps (such as setter injection) in advance by the Spring container, and can only solve the bean circular dependency of the singleton scope. By exposing a singleton factory method in advance, other beans can reference the bean. The code is as follows:

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		synchronized (this.singletonObjects) {
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
				if (singletonFactory != null) {
					singletonObject = singletonFactory.getObject();
					this.earlySingletonObjects.put(beanName, singletonObject);
					this.singletonFactories.remove(beanName);
				}
			}
		}
	}
	return singletonObject;
}

The definition of earlySingletonObjects is:

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

For prototype scoped beans, the Spring container cannot complete dependency injection because the Spring container does not cache prototype scoped beans.

bean lifecycle

During the interview, the core of Spring is here, but just remember the general process.

official account

coding notes and notes, and later articles will be synchronized to the official account (Coding Insight).

Code and mind mapping in GitHub project Welcome to star!

Added by cgm225 on Mon, 31 Jan 2022 21:55:26 +0200