[spring IOC] the spring IOC container bean is parsed and encapsulated as BeanDefinition

Posted by hrosas on Sun, 06 Feb 2022 08:27:26 +0100

When we use context to obtain beans, in fact, the bottom layer is the DefaultListableBeanFactory class of operation. This class is mainly divided into two parts:

  1. Registered bean: parse class and encapsulate it as beanDefinition object;
  2. Instantiate an object from beanDefinition.

Let's first look at how to save data.

beanDefinition object

Properties in DefaultListableBeanFactory:

	// key: the name of the bean
	// value: BeanDefinition object
	private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

	//Save key: type and value:bean name by type
	private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<>(64);

	// Only the single example is saved here
	private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap<>(64);

	// Names of all bean s
	private volatile List<String> beanDefinitionNames = new ArrayList<>(256);

	// The name of the manually added bean
	// Why is it added manually? Because we can operate context and add our own BeanDefinition, which is added manually and will be added to this set.
	private volatile Set<String> manualSingletonNames = new LinkedHashSet<>(16);

Instance object

Always find the parent class from DefaultListableBeanFactory, and you will find DefaultSingletonBeanRegistry

	//Bean name bean instance
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	//The class of ObjectFactory is saved
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	//Early exposed bean s are used to solve circular dependencies
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

	// Name of the instance.
	private final Set<String> registeredSingletons = new LinkedHashSet<>(256);

If you know where to save the data, you can start to see where to parse the class and encapsulate it as the beandefinitions class. Because spring supports configuration files and annotations. So let's take a look at how to register beans.

ClassPathXmlApplicationContext resolves the configuration file registration bean

We usually use this when creating:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

The configuration file passed in to spring is registered in the container by parsing the configuration inside.

	public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {

		// Set the path of the configuration file
		if (refresh) {
			// Call the refresh method.

Refresh method at org springframework. context. support. Abstractapplicationcontext#refresh(), which has many methods. xml is parsed in the obtainFreshBeanFactory method

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		// Refresh the factory and return the factory to
		return getBeanFactory();


	protected final void refreshBeanFactory() throws BeansException {
		// If it already exists, destroy it and recreate it.
		if (hasBeanFactory()) {
		try {
			// The instance factory is to create the DefaultListableBeanFactory object
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			// Register the configuration in the configuration file into beanFactory.
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);


	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {

When XmlBeanDefinitionReader is instantiated, beanFactory has been put into. Now the path of the incoming configuration file.

Continue to explore the following methods:


	protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		if (delegate.isDefaultNamespace(root)) {
			NodeList nl = root.getChildNodes();
			for (int i = 0; i < nl.getLength(); i++) {
				Node node = nl.item(i);
				if (node instanceof Element) {
					Element ele = (Element) node;
					if (delegate.isDefaultNamespace(ele)) {
						parseDefaultElement(ele, delegate);
					else {
		else {

Resolve tags through namespaces. On the line, under different namespaces
First look at the following under this namespace:

	private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		// Import tag to import other configuration files.
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
		// Alias Tag 
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
		// bean tag
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			processBeanDefinition(ele, delegate);
		//beans tag
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse

importBeanDefinitionResource: used to parse the import tag. The import tag is used to import other configuration files; The internal logic is to get the location data and then use XmlBeanDefinitionReader to parse the path. In fact, it is the logic of analyzing configuration and asking price

processAliasRegistration: resolve the alias tag, resolve the alias, key: bean name, value is the alias, and put it into the alias map.

processBeanDefinition: resolves the bean tag. The data in the bean tag will directly correspond to the properties in beandenition. The property tag will be resolved to PropertyValue and added to bf's property collection. Constructors are similar logic.

doRegisterBeanDefinitions: when parsing the beans tag, it means recursion or calling the same logic. Now what is parsed is the content in the beans tag.

See the resolution of other namespaces:

Parse the corresponding tag.

	public BeanDefinition parse(Element element, ParserContext parserContext) {
		BeanDefinitionParser parser = findParserForElement(element, parserContext);
		return (parser != null ? parser.parse(element, parserContext) : null);

Here we start with different namespaces and different parsers

You can see the corresponding labels by name:


The code is subsidized. It directly says that it did those things. Some beans are registered. What do these beans do?
ConfigurationClassPostProcessor: used to resolve the configuration class (@ configuration)

Autowired annotation beanpostprocessor: @ Autowired dependency injected processor

CommonAnnotationBeanPostProcessor: @ resource depends on the injected processor

The latter two have also been injected. I haven't seen them yet.


Two bean s are also registered: these two good analyses are to create an agent and put the method of the agent into one thread for execution.



The name is spring's Cache, which is still a registered processor class related to it.
There are two ways of acting. I haven't learned about the aspects. The agent is registered with the class infrastructureasuggestorautoproxycreator. It is specially used to analyze the aspects inside spring. What is inside spring? Role() == BeanDefinition.ROLE_INFRASTRUCTURE


At first glance, it is related to aop. The registration processor is annotation awareaspectjauto proxycreator


Scan package processor:

	public BeanDefinition parse(Element element, ParserContext parserContext) {
		String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
		basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
		String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,

		// The processor that resolves the package name
		ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
		Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
		registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

		return null;

Note: the doScan method of ClassPathBeanDefinitionScanner has been registered with the scanned beans. The registerComponents method contains the functions of the above AnnotationConfigBeanDefinitionParser.

There are other parsers. Just analyze it yourself.

The registered bean of ClassPathXmlApplicationContext has been analyzed: except that the bean tag is a real registered bean, other tags are processors of registered functions.

Let's take a look at how the annotated container registers bean s. Think about it: ClassPathXmlApplicationContext is a registered processor by parsing labels, so annotations must be used to implement the registered processor in the AnnotationConfigApplicationContext.

How do ordinary bean s register? Guess, it should be the scanned annotation.

ClassPathXmlApplicationContext is a configuration file for. Parse the file.
AnnotationConfigApplicationContext: give a class with configuration annotations. Parsing annotations in classes.

AnnotationConfigApplicationContext parsing configuration class

The parameter of ClassPathXmlApplicationContext is the path of the configuration file; Multiple paths can be passed, or other configuration files can be introduced into the import tag in a general file price inquiry;

AnnotationConfigApplicationContext needs to be passed into the configuration class; It can also be transmitted to multiple; But usually only the configuration class of the host will be passed. After that, the annotation in the main configuration class: @ compoentscan; Scan bean s and configuration classes. Then analyze.

	public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
		// Call its own constructor
		// Register the main configuration class
		// Refresh method
	//The parameterless constructor creates two objects.
	public AnnotationConfigApplicationContext() {
		// Processor for reading annotations
		this.reader = new AnnotatedBeanDefinitionReader(this);
		// Processor used to scan bean s
		this.scanner = new ClassPathBeanDefinitionScanner(this);

When creating the AnnotatedBeanDefinitionReader object, several beans are registered, which are those registered in the AnnotationConfigBeanDefinitionParser function; Note: the bean ConfigurationClassPostProcessor is used to parse configuration classes.

When creating ClassPathBeanDefinitionScanner, only the scanning conditions are configured.

To learn more about how annotation type containers parse classes and encapsulate them as beanDefinition, first look at this article:
[spring IOC] customization of BeanFactory through BeanFactory postprocessor

Based on the above, take a look at this class: ConfigurationClassPostProcessor. No, it's registered by AnnotatedBeanDefinitionReader.

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
		PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware

It implements BeanDefinitionRegistryPostProcessor and PriorityOrdered. You know where to call this class.

Look directly at the postProcessBeanDefinitionRegistry method.

	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
		int registryId = System.identityHashCode(registry);
		if (this.registriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
		if (this.factoriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanFactory already called on this post-processor against " + registry);
		// Avoid duplicate parsing

	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		// Find all registered bean names
		String[] candidateNames = registry.getBeanDefinitionNames();

		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
			// If it is annotated with @ configuration annotation, add the collection
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));

		// Return immediately if no @Configuration classes were found
		if (configCandidates.isEmpty()) {

		// Sort by previously determined @Order value, if applicable
		configCandidates.sort((bd1, bd2) -> {
			int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
			int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
			return Integer.compare(i1, i2);

		// Detect any custom bean name generation strategy supplied through the enclosing application context
		SingletonBeanRegistry sbr = null;
		if (registry instanceof SingletonBeanRegistry) {
			sbr = (SingletonBeanRegistry) registry;
			if (!this.localBeanNameGeneratorSet) {
				BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
				if (generator != null) {
					this.componentScanBeanNameGenerator = generator;
					this.importBeanNameGenerator = generator;

		if (this.environment == null) {
			this.environment = new StandardEnvironment();

		// Create @ Configuration class parser
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			// Resolve the current candidate configuration class.

			// The rest of the code: first analyze parser parse(candidates);
			. . . . 

Skip some methods and look at this directly:

	protected final void parse(@Nullable String className, String beanName) throws IOException {
		Assert.notNull(className, "No bean class name for configuration class bean definition");
		MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
		processConfigurationClass(new ConfigurationClass(reader, beanName));

Each configuration class corresponds to a ConfigurationClass;

Real parsing configuration class:

	protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
			throws IOException {

		// The configuration class must be marked with the Component annotation.
		// This method resolves the internal class. If the internal class is marked with @ configuration annotation, it will resolve the internal class first.
		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
			processMemberClasses(configClass, sourceClass);

		// @The logic of PropertySource parsing this annotation is also very simple, that is, parse the property file and put the key value pair into the environment.
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.environment instanceof ConfigurableEnvironment) {
			else {
				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");

		// Parse @ ComponentScan annotation
		// According to the registration, scan the package, directly inject the bean and return the beanDefinition set.
		// Filter from. If it is a configuration class, the configuration class should also be resolved.
		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() &&
				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
			for (AnnotationAttributes componentScan : componentScans) {
				// The config class is annotated with @ComponentScan -> perform the scan immediately
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				// Check the set of scanned definitions for any further config classes and parse recursively if needed
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					// If the configuration class is scanned, enter the parsing logic.
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
						parse(bdCand.getBeanClassName(), holder.getBeanName());

		// @Import import configuration class. The target is the import tag in xml.
		// getImports(sourceClass) is the class set by the import annotation.
		processImports(configClass, sourceClass, getImports(sourceClass), true);

		// Process any @ImportResource annotations
		AnnotationAttributes importResource =
				AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
		if (importResource != null) {
			String[] resources = importResource.getStringArray("locations");
			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
			for (String resource : resources) {
				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
				configClass.addImportedResource(resolvedResource, readerClass);

		// Parse @ Bean annotation: find all methods and add them to the configClass attribute.
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));

		// Process default methods on interfaces
		processInterfaces(configClass, sourceClass);

		// Process superclass, if any
		if (sourceClass.getMetadata().hasSuperClass()) {
			String superclass = sourceClass.getMetadata().getSuperClassName();
			if (superclass != null && !superclass.startsWith("java") &&
					!this.knownSuperclasses.containsKey(superclass)) {
				this.knownSuperclasses.put(superclass, configClass);
				// Superclass found, return its annotation metadata and recurse
				return sourceClass.getSuperClass();

		// No superclass -> processing is complete
		return null;

Note that it is only resolved here and then added to the attribute of configClass. It is not encapsulated as bd

Resolve inner class

	private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
		// Get the inner class collection
		Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
		if (!memberClasses.isEmpty()) {
			List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
			for (SourceClass memberClass : memberClasses) {
				// If the inner class is annotated with the @ configuration annotation, it is added to the collection.
				if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
						!memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
			// Traverse the collection. Resolve internal configuration class:
			for (SourceClass candidate : candidates) {
				if (this.importStack.contains(configClass)) {
					this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
				else {
					try {
					// What comes in here is the class of the inner class
					finally {

Parse import annotation

The basis is as follows:
import is the imported configuration class;
If the class marked in the import annotation is of ImportSelector type, the selectImports method of this class returns the imported configuration class, not this class.

If the annotated class is ImportBeanDefinitionRegistrar, it will be passed into the registry. Register yourself.

If it is a normal type, then this class is the real configuration class.

	private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
			// Collection < sourceclass > importcandidates is the class marked in the import annotation

		if (importCandidates.isEmpty()) {

		if (checkForCircularImports && isChainedImportOnStack(configClass)) {
			this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
		else {
			try {
			// Traverse these classes.
				for (SourceClass candidate : importCandidates) {
					// If it is of ImportSelector type,
					if (candidate.isAssignable(ImportSelector.class)) {
						// Candidate class is an ImportSelector -> delegate to it to determine imports
						Class<?> candidateClass = candidate.loadClass();
						ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
								this.environment, this.resourceLoader, this.registry);
						if (selector instanceof DeferredImportSelector) {
							this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
						else {
						// See here? Get the return value of the selectImports method, call this method processImports again, and analyze it until you find the ordinary class or ImportBeanDefinitionRegistrar class.
							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
							processImports(configClass, currentSourceClass, importSourceClasses, false);
					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
						// Candidate class is an ImportBeanDefinitionRegistrar ->
						// The of ImportBeanDefinitionRegistrar type will be instantiated and then added to the property of configClass.
						Class<?> candidateClass = candidate.loadClass();
						ImportBeanDefinitionRegistrar registrar =
								ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
										this.environment, this.resourceLoader, this.registry);
						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
					else {
						// If it is an ordinary bean, it is a configuration class. Put it into the attribute of configClass and notify to resolve the configuration class again. Call the processConfigurationClass method.
								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to process import candidates for configuration class [" +
						configClass.getMetadata().getClassName() + "]", ex);
			finally {

The import annotation is very complex and needs to be thought over:
It can be understood as follows: ImportSelector is the of classes imported in batch. ImportBeanDefinitionRegistrar adds beanDefinition manually, and ordinary is the real configuration class.

Sort out and analyze which block has generated a new ConfigurationClass.

  1. Inner class
  2. Package scanning. If it is a configuration class, a new one will be generated.
  3. import annotation, ordinary, will generate a new one.

Note that generating a new is to further parse the class. All configurationclasses are cached in ConfigurationClassParser.

Let's take a look at the remaining methods of processConfigBeanDefinitions:

		do {
			// Here you get all the configuration classes.
			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());

			// Read the model and create bean definitions based on its content
			if (this.reader == null) {
				this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
			// Encapsulate the configuration class as bd

			if (registry.getBeanDefinitionCount() > candidateNames.length) {
				String[] newCandidateNames = registry.getBeanDefinitionNames();
				Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
				Set<String> alreadyParsedClasses = new HashSet<>();
				for (ConfigurationClass configurationClass : alreadyParsed) {
				for (String candidateName : newCandidateNames) {
					if (!oldCandidateNames.contains(candidateName)) {
						BeanDefinition bd = registry.getBeanDefinition(candidateName);
						if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
								!alreadyParsedClasses.contains(bd.getBeanClassName())) {
							candidates.add(new BeanDefinitionHolder(bd, candidateName));
				candidateNames = newCandidateNames;
		while (!candidates.isEmpty());

The reason for using the while loop. It is to judge whether a new configuration class is generated. If there is something, it needs to be parsed again.

Encapsulated as beanDefinition

	private void loadBeanDefinitionsForConfigurationClass(
			ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

		if (trackedConditionEvaluator.shouldSkip(configClass)) {
			String beanName = configClass.getBeanName();
			if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
		// It is a common class of import.
		// Several annotations are parsed: @ lazy, @ dependent, @ primary, etc
		if (configClass.isImported()) {
		// Bean method is mainly used to encapsulate the attributes in @ bean annotation into the attributes in bd.
		// This method is set as factory method
		for (BeanMethod beanMethod : configClass.getBeanMethods()) {

// The configuration file is introduced, and the xmlBeanDefinitionreader parses the configuration file
		//This is the importBeanDefinitionRegistrar type in import. Call method. Register manually.

At this point, the bean has been parsed..

Topics: Java Spring Container