Spring source code learning Chapter 1 - loading configuration files

Posted by angelena on Sat, 22 Jan 2022 23:00:27 +0100

preface

IoC Container is the foundation of Spring. Beans we use everyday are created and maintained by containers. Package org springframework. Beans and org springframework. Context is the fundamental implementation of the container.

BeanFactory is the foundation of the container. It provides the functions of configuration framework and foundation.

ApplicationContext is a sub interface of BeanFactory, which can be said to be a complete superset of the container. It adds some functions on the basis of BeanFactory:

  • Easier integration with Spring’s AOP features. (more convenient for AOP integration)
  • Message resource handling (for use in internationalization). Message resource processing (for internationalization)
  • Event publication. Event release
  • Application-layer specific contexts such as the WebApplicationContext for use in web applications. Specific application context, such as webapplicationcontext for network applications

The container obtains instructions about which objects to instantiate, configure, and assemble by reading the configuration metadata.

Configuration metadata exists in three ways:

  • XML based configuration: XML configuration file is the earliest configuration method, and it may also be the first configuration for everyone to learn Spring
  • Annotation based configuration: annotation based configuration, spring 2 The support provided after 5 can simplify XML configuration.
  • Java based configuration: Java code based configuration, spring 3 Starting with 0, you can no longer use XML configuration.

The Spring Configuration includes at least one Bean definition, and the container must manage these definitions. The XML based Configuration metadata configures these beans as < beans / > elements in the top-level < beans / > elements. Java Configuration usually uses the @ Bean annotated method in the @ Configuration class.

Here we first discuss how Spring locates the configuration file and Bean configuration, and then see the specific loading process.

1, XML configuration file loading

The most basic configuration structure of XML configuration file

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">  
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>

XmlBeanFactory load configuration

XmlBeanFactory has been marked as Deprecated, but it does not affect our understanding of configuration file loading.

@Deprecated
public class XmlBeanFactory extends DefaultListableBeanFactory {

	private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

	public XmlBeanFactory(Resource resource) throws BeansException {
		this(resource, null);
	}

	public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
		super(parentBeanFactory);
		this.reader.loadBeanDefinitions(resource);
	}

Resource

The full class name of resource is org springframework. core. io. Resource, which has some different implementations, such as ClassPathResource, FileSystemResource, FileUrlResource, InputStreamResource, PathResource, ServletContextResource, and so on.

Through these implementations, different access paths can be used to obtain configuration resources.

1. ClassPathResource

It can also be seen from the class name that this is loading the configuration from the class path

//Load the configuration file from the absolute path of class path
ClassPathResource resource = new ClassPathResource("config/services.xml");
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(resource);
TestA testa = xmlBeanFactory.getBean("testa", TestA.class);
Assertions.assertNotNull(testa);

ClassPathResource has a construction method of loading from relative path, which is more practical. Many unit tests in Spring source code use this method to organize various configurations.

for instance:
The full class name of testa is beans dependency. TestA. If we want to test this Bean, the configuration file path can be test \ resources \ beans \ dependency \ beta_ Bean_ config. XML (Maven or Gradle).
If you use absolute path, you need to use "beans / dependency / beta_bean_config. XML", but if you use relative classpath, it is relatively simple.

ClassPathResource resource = new ClassPathResource("testa_bean_config.xml", TestA.class);
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(resource);
TestA testa = xmlBeanFactory.getBean("testa", TestA.class);
Assertions.assertNotNull(testa);

This helps the organization associate the configurations of different modules, so as not to be confused when there are too many configurations.

2. PathResource

FileSystemResource is loaded from the file system, which is also easy to understand, but it is estimated that it is not used much in practice. Here is another PathResource. Since Java provides NIO API, file related operations are much easier.

PathResource resource = new PathResource(Paths.get("e:", "test", "testa_bean_config.xml"));
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(resource);
TestA testa = xmlBeanFactory.getBean("testa", TestA.class);
Assertions.assertNotNull(testa);

Other Resource loads are similar, so I won't list them one by one. In short, Spring points to different configuration files through different resources.

XmlBeanDefinitionReader

XmlBeanFactory has been abandoned. You can directly use DefaultListableBeanFactory and XmlBeanDefinitionReader.
In fact, XmlBeanFactory is a subclass of DefaultListableBeanFactory. XmlBeanFactory itself uses XmlBeanDefinitionReader. You can use XmlBeanDefinitionReader to load the configuration to the specified beanFactory as follows.

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
xmlBeanDefinitionReader.loadBeanDefinitions(new ClassPathResource("testa_bean_config.xml", TestA.class));

TestA testa = beanFactory.getBean("testa", TestA.class);
assertNotNull(testa);

The XmlBeanDefinitionReader constructor receives BeanDefinitionRegistry, which specifies the registration and acquisition behavior of BeanDefinition, which is Spring's abstraction of Bean configuration.

Next, let's look at how the XmlBeanDefinitionReader loads the configuration:

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
	Assert.notNull(encodedResource, "EncodedResource must not be null");
	if (logger.isTraceEnabled()) {
		logger.trace("Loading XML bean definitions from " + encodedResource);
	}

	Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

	if (!currentResources.add(encodedResource)) {
		throw new BeanDefinitionStoreException(
				"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
	}

	try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
		InputSource inputSource = new InputSource(inputStream);
		if (encodedResource.getEncoding() != null) {
			inputSource.setEncoding(encodedResource.getEncoding());
		}
		return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
	}

It can be seen that InputSource is constructed after InputStream is obtained from Resource, which is provided by rt.jar in Java to parse xml. After that, get the Document, and then traverse all < bean / > elements to get the BeanDefinition.

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
		throws BeanDefinitionStoreException {
	try {
		Document doc = doLoadDocument(inputSource, resource);
		int count = registerBeanDefinitions(doc, resource);
		if (logger.isDebugEnabled()) {
			logger.debug("Loaded " + count + " bean definitions from " + resource);
		}
		return count;
	}

ClassPathXmlApplicationContext

ApplicationContext is more powerful and convenient. It also loads XML configuration files internally through XmlBeanDefinitionReader.

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("testa_bean_config.xml", TestA.class);
TestA testa = applicationContext.getBean("testa", TestA.class);
assertNotNull(testa);

Summary XML configuration file loading

Now, for the time being, consider the loading of a single file. The basic process is

  • Load XML configuration file as Resource
  • XmlBeanDefinitionReader parses XML document;
  • Register the < bean / > element as BeanDefinition

2, Annotation based configuration loading

Annotation based configuration can configure beans through @ Autowired, @ Qualifier, @ Resource, @ Inject and other annotations. Moreover, annotated beans can be mixed with XML. First, you need to start annotation configuration:

@Inject is JSR-330 standard annotations, but it needs to rely on javax inject

@Autowired is suitable for fields, constructors, and multi parameter methods, allowing narrowing at the parameter level through qualifier comments. In contrast, @ Resource only supports fields and bean property setter methods with only one parameter.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>

Let's take a look at how context: annotation config works

<context:annotation-config/>

Since it is still configured in xml, the process of reading the entire configuration file to get the document is the same. Then, the sub elements of the document will be traversed and each sub element will be processed:

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 {
					delegate.parseCustomElement(ele);
				}
			}
		}
	}
	else {
		delegate.parseCustomElement(root);
	}
}

DefaultNamespace refers to http://www.springframework.org/schema/beans , the namespace of the root element < beans / > is the default, but the namespace of the context: annotation config element is http://www.springframework.org/schema/context , so it will execute delegate Parsecustomelement handles the configuration element context: annotation config.

public BeanDefinition parseCustomElement(Element ele) {
	return parseCustomElement(ele, null);
}

@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
	String namespaceUri = getNamespaceURI(ele);
	if (namespaceUri == null) {
		return null;
	}
	NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
	if (handler == null) {
		error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
		return null;
	}
	return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

You can see the processing of non default namespace. You need to find the corresponding NamespaceHandler according to its own namespace, and then use it to process the corresponding sub element < context: annotation config / >. Note that this feature can be used to extend Spring.

that http://www.springframework.org/schema/context What kind of NamespaceHandler can be found?

Get < context: annotation config / > namespacehandler

As can be seen from the above code, the NamespaceHandler comes from the readercontext getNamespaceHandlerResolver(). Resolve (namespaceUri). It can be seen from the construction process of XmlReaderContext that Spring uses DefaultNamespaceHandlerResolver to handle namespaceUri by default.

public XmlReaderContext createReaderContext(Resource resource) {
	return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
			this.sourceExtractor, this, getNamespaceHandlerResolver());
}
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
	if (this.namespaceHandlerResolver == null) {
		this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
	}
	return this.namespaceHandlerResolver;
}

protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
	ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
	return new DefaultNamespaceHandlerResolver(cl);
}

DefaultNamespaceHandlerResolver is from meta-inf / spring.com by default The handlers configuration file reads the correspondence between namespaceUri and NamespaceHandler. And meta-inf / spring There are many handlers:

  • Meta-inf / spring. Under the spin bean module handlers
    http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
    http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
    http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
    
    
  • Meta-inf / spring.inf under spring context module handlers
    http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
    http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
    http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
    http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
    http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
    
    
  • Meta-inf / spring. Under spring AOP module handlers
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

When you learn SpringBoot later, you will also see different spring Handlers file, each spring Handlers are read as Properties files.

Spring. From spring context You can know from handlers that ContextNamespaceHandler is used to handle < context: annotation config / > elements.

ContextNamespaceHandler

After finding the class corresponding to NamespaceHandler, the object will be created through reflection and the init method will be called.

NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);

The init method of ContextNamespaceHandler is also very simple. We can see the figure of annotation config:

registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());

Now go back to the parsing of < context: annotation config / >, that is, namespacehandler The parse method first obtains the corresponding BeanDefinitionParser according to the localName of the element.

The localName of < context: annotation config / > is annotation config, and the above contextnamespacehandler Init method, you can find the corresponding BeanDefinitionParser, that is, AnnotationConfigBeanDefinitionParser.

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

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
	String localName = parserContext.getDelegate().getLocalName(element);
	BeanDefinitionParser parser = this.parsers.get(localName);
	if (parser == null) {
		parserContext.getReaderContext().fatal(
				"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
	}
	return parser;
}

Now you can see that < context: annotation config / > the main parsing and processing logic is in the annotation configbeandefinitionparser

< context: annotation config / > AnnotationConfigBeanDefinitionParser

The annotation configbeandefinitionparser itself does not return a BeanDefinition, and its return value is null. However, it registers the following beandefinitions:

  • ConfigurationClassPostProcessor
  • AutowiredAnnotationBeanPostProcessor
  • CommonAnnotationBeanPostProcessor
  • PersistenceAnnotationBeanPostProcessor
  • EventListenerMethodProcessor
  • DefaultEventListenerFactory

These registered beandefinitions will be preferentially initialized as singleton beans later. Then, when creating annotated beans later, BeanPostProcessor will handle the dependency of annotation configuration. The specific loading process is not discussed here. So far, each annotation can be located and processed.

Annotation configuration summary

  • Read configuration file XML as Resource
  • Parse the XML document and process the child elements in turn
  • Encountered < context: annotation config / >, found the context namespace processor ContextNamespaceHandler
  • Find the parser corresponding to annotation config annotation configbeandefinitionparser
  • AnnotationConfigBeanDefinitionParser registers the Spring infrastructure. Several postprocessors correspond to BeanDefinition
  • Resolve the < bean / > element and register it as BeanDefinition
  • Create several corresponding BeanPostProcessor instance beans, and these subsequent instances will process user-defined Bean annotations

XML configuration auto scan

If only < context: annotation config / > is used, although @ Autowired, @ Resource and other annotations can be used to reduce the configuration content of XML, the < bean / > element cannot be missing.

Spring also provides the automatic scanning function to register beans, which can be configured in two ways:

  • For Java based Configuration, add @ ComponentScan("org.example") in the @ Configuration configuration class
  • XML configuration, < context: component scan base package = "org.example" / >

After automatic scanning is configured, beans can be registered with @ Component, @ Service and other annotations.

Let's talk about XML configuration first, and then learn about @ ComponentScan in Java code based configuration.

<context:component-scan/>

Base package is a required attribute that allows multiple packages to be configured

<context:component-scan base-package="org.example"/>

As in < context: annotation config / >, you can find that the parser corresponding to component scan is ComponentScanBeanDefinitionParser

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,
			ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

	// Actually scan for bean definitions and register them.
	ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
	Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
	registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

	return null;
}

The ClassPathBeanDefinitionScanner is constructed in the ComponentScanBeanDefinitionParser. The main work of obtaining the BeanDefinition of @ Component and other annotations is completed by the ClassPathBeanDefinitionScanner.

From the construction process of ClassPathBeanDefinitionScanner, < context: component scan / > there are some additional attributes, such as use default filters, resource pattern, name generator, scope resolver, scoped proxy, etc; In addition, there are several child elements, including filter and exclude filter. At present, we only focus on how to scan the Bean and register it.

ClassPathBeanDefinitionScanner .doScan method:

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) {
		Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
		for (BeanDefinition candidate : candidates) {
			ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
			candidate.setScope(scopeMetadata.getScopeName());
			String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
			if (candidate instanceof AbstractBeanDefinition) {
				postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
			}
			if (candidate instanceof AnnotatedBeanDefinition) {
				AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
			}
			if (checkCandidate(beanName, candidate)) {
				BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
				definitionHolder =
						AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
				beanDefinitions.add(definitionHolder);
				registerBeanDefinition(definitionHolder, this.registry);
			}
		}
	}
	return beanDefinitions;
}

After going deep into findCandidateComponents, you can see that there are many ways to scan files or obtain the corresponding URI under class path, and finally build different resources. For example, when I ran unit test, I finally constructed FileSystemResource, and each class is a Resource.

Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
Set<Resource> result = new LinkedHashSet<>(matchingFiles.size());
for (File file : matchingFiles) {
	result.add(new FileSystemResource(file));
}
return result;

Others include VfsResource, UrlResource, etc. This is somewhat similar to reading an XML configuration file as a Resource.

After reading the Resource, it is processed to construct a MetadataReader (the XML configuration file corresponds to an XmlBeanDefinitionReader). MetadataReader encapsulates various information of the class obtained through Resource through ClassVisitor, such as class name, superclass, interface, attribute and so on.

Then build ScannedGenericBeanDefinition, which is a concrete implementation of BeanDefinition.

MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
		if (isCandidateComponent(metadataReader)) {
			ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
			sbd.setSource(resource);
			if (isCandidateComponent(sbd)) {
				if (debugEnabled) {
					logger.debug("Identified candidate component class: " + resource);
				}
				candidates.add(sbd);
			}

At this point, you can roughly understand how the automatic scanning of XML configuration finds the Bean configuration.

XML configuration auto scan summary

  • Read configuration file XML as Resource
  • Parse the XML document and process the child elements in turn
  • Encountered < context: component scan / >, found the context namespace processor ContextNamespaceHandler
  • Find the parser corresponding to component scan ComponentScanBeanDefinitionParser
  • ComponentScanBeanDefinitionParser constructs ClassPathBeanDefinitionScanner, and scans the Resource under the specified package name as Resource
  • Then read the information related to the class, and register the qualified as ScannedGenericBeanDefinition
  • In the same way as annotation config, the BeanDefinition of some postprocessors will also be registered
  • Create several corresponding PostProcessor instance beans, and these subsequent instances will process user-defined Bean annotations

3, Java based configuration

Through Java code Configuration, Bean can be completely separated from XML Configuration. The core of Spring Java code Configuration is the @ Configuration annotated class and @ Bean annotated method@ The Configuration annotated class itself will be registered as a bean definition, and all @ Bean annotated methods under it will also be registered as bean definition.

@Configuration
public class AppConfig {
    @Bean
    public TestA testA() {
        return new TestA();
    }
}

AnnotationConfigApplicationContext can be used to maintain configured beans. The usage is as follows:

 ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
 TestA testa = ctx.getBean(TestA.class);
 assertNotNull(testa);

It can accept not only @ Configuration annotated classes, but also @ Component, @ Service and other annotated classes:

ApplicationContext ctx = new AnnotationConfigApplicationContext(TestA.class, TestB.class);
TestA testa = ctx.getBean(TestA.class);
assertNotNull(testa);

Next, let's learn how the Java code configuration obtains the BeanDefinition.

The default construction method of AnnotationConfigApplicationContext is as follows:

public AnnotationConfigApplicationContext() {
	StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
	this.reader = new AnnotatedBeanDefinitionReader(this);
	createAnnotatedBeanDefReader.end();
	this.scanner = new ClassPathBeanDefinitionScanner(this);
}

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
	this();
	register(componentClasses);
	refresh();
}

As you can see, there are two attributes AnnotatedBeanDefinitionReader and ClassPathBeanDefinitionScanner.

Let's take a look at AnnotatedBeanDefinitionReader first.

AnnotatedBeanDefinitionReader

AnnotatedBeanDefinitionReader is created in the AnnotationConfigApplicationContext constructor

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
	Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
	Assert.notNull(environment, "Environment must not be null");
	this.registry = registry;
	this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
	AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}

It internally registers the BeanDefinition of AnnotationConfigProcessors, including:

  • ConfigurationClassPostProcessor
  • AutowiredAnnotationBeanPostProcessor
  • CommonAnnotationBeanPostProcessor
  • EventListenerMethodProcessor
  • DefaultEventListenerFactory

After these Processors are registered, AnnotationConfigApplicationContext calls register to register the user's Configuration class as AnnotatedGenericBeanDefinition. Later, ConfigurationClassPostProcessor will process the @ Bean annotation method under the @ Configuration configuration class before formally initializing the user Bean.

ConfigurationClassPostProcessor

ConfigurationClassPostProcessor implements the BeanDefinitionRegistryPostProcessor interface and will give priority to creating the corresponding singleton Bean.

The postProcessBeanDefinitionRegistry method of BeanDefinitionRegistryPostProcessor will process the existing BeanDefinition once before the BeanDefinition configured by the user formally creates the Bean.

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);
	}
	this.registriesPostProcessed.add(registryId);

	processConfigBeanDefinitions(registry);
}

processConfigBeanDefinitions starts to parse the Configuration information of @ Configuration after processing the user Configuration Class. Parsing uses ConfigurationClassParser to get ConfigurationClass. Then again

// Parse each @Configuration class
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 {
	StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
	parser.parse(candidates);
	parser.validate();

	Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
	configClasses.removeAll(alreadyParsed);

	// 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());
	}
	this.reader.loadBeanDefinitions(configClasses);
	alreadyParsed.addAll(configClasses);
	...
		candidateNames = newCandidateNames;
	}
}
while (!candidates.isEmpty());

Look at how ConfigurationClassParser handles ConfigurationClassParser. Trace the parse method of ConfigurationClassParser to find the processing of various annotations on the configuration class, such as the @ Bean annotation we want to see.

// ConfigurationClassParser#doProcessConfigurationClass
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
	// Recursively process any member (nested) classes first
	processMemberClasses(configClass, sourceClass, filter);
}

... There are also many annotation processing, such as@ComponentScans,@ComponentScan wait

// Process individual @Bean methods
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
	configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

By calling the retrieveBeanMethodMetadata method, the method information extracted from all configured @ beans is added to the ConfigurationClass attribute. The subsequent instance of BeanDefinitionRegistryPostProcessor ConfigurationClassPostProcessor will construct the ConfigurationClass processed by ConfigurationClassBeanDefinitionReader and register BeanDefinition:

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
	TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
	for (ConfigurationClass configClass : configurationModel) {
		loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
	}
}

/**
 * Read a particular {@link ConfigurationClass}, registering bean definitions
 * for the class itself and all of its {@link Bean} methods.
 */
private void loadBeanDefinitionsForConfigurationClass(
		ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

	if (trackedConditionEvaluator.shouldSkip(configClass)) {
		String beanName = configClass.getBeanName();
		if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
			this.registry.removeBeanDefinition(beanName);
		}
		this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
		return;
	}

	if (configClass.isImported()) {
		registerBeanDefinitionForImportedConfigurationClass(configClass);
	}
	// The @ Bean registration method will be processed here
	for (BeanMethod beanMethod : configClass.getBeanMethods()) {
		loadBeanDefinitionsForBeanMethod(beanMethod);
	}

	loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
	loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

As you can see above, loadBeanDefinitionsForBeanMethod will process the method information annotated by @ Bean in turn and register it as ConfigurationClassBeanDefinition.

Java code configuration summary

  1. AnnotatedBeanDefinitionReader registers beandefinitions of several postprocessors, including ConfigurationClassPostProcessor
  2. ConfigurationClassPostProcessor will first instantiate and process the user configuration class through the postProcessBeanDefinitionRegistry method
    • ConfigurationClassParser handles various annotations, including methods for annotation of @ Bean, encapsulates the corresponding information into MethodMetadata and stores it on ConfigurationClass
    • Construct ConfigurationClassBeanDefinitionReader and process ConfigurationClass
    • Construct the ConfigurationClassBeanDefinition according to the configuration information corresponding to MethodMetadata
  3. Later, some beanpostprocessors will be instantiated first
  4. Finally, when the ConfigurationClassBeanDefinition is instantiated as a Bean, the BeanPostProcessor will continue to process some annotations such as @ Autoware, @ Resource, and so on

Java code configuration auto scan

scan method of AnnotationConfigApplicationContext

When AnnotationConfigApplicationContext is initialized, two properties are constructed, one is AnnotatedBeanDefinitionReader, and the other is ClassPathBeanDefinitionScanner. We introduced AnnotatedBeanDefinitionReader from to above. Now let's take a look at ClassPathBeanDefinitionScanner:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("beans.dependency");
ctx.refresh();

TestA testa = ctx.getBean(TestA.class);
assertNotNull(testa);

The scan method is very simple. It just calls the scan of ClassPathBeanDefinitionScanner. So the main work is in ClassPathBeanDefinitionScanner.

The ClassPathBeanDefinitionScanner has the logic to register BeanDefinition, as mentioned earlier in the < context: component scan / > automatic scanning of XML configuration. I won't repeat it.

@ComponentScan

After using ComponentScan, the configuration becomes very simple. The registered configuration can rely on @ Service, @ Component and other annotations.

@Configuration
@ComponentScan("beans.dependency")
public class AppConfig {
}

Get Bean invariant

ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TestA testa = ctx.getBean(TestA.class);
assertNotNull(testa);

The logic of registering beans is the same as the Java based configuration above, and the starting process is the same:

  1. AnnotatedBeanDefinitionReader registers beandefinitions of several postprocessors, including ConfigurationClassPostProcessor
  2. ConfigurationClassPostProcessor will be instantiated first and process the user configuration class through the postProcessBeanDefinitionRegistry method; The ConfigurationClassParser handles various annotations. Here, it mainly deals with @ ComponentScans
  3. Later, some beanpostprocessors will be instantiated first
  4. Finally, when the ConfigurationClassBeanDefinition is instantiated as a Bean, the BeanPostProcessor will continue to process some annotations such as @ Autoware, @ Resource and so on

So how does @ ComponentScans handle it? Find the ConfigurationClassParser and the corresponding processing logic:

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 (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
				parse(bdCand.getBeanClassName(), holder.getBeanName());
			}
		}
	}
}

Among them, componentScanParser is an instance of ComponentScanAnnotationParser, that is, the main logic is in ComponentScanAnnotationParser In parse:

ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
		componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
... Omit some scanner Treatment of
Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
	String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
			ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
	Collections.addAll(basePackages, tokenized);
}
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
	basePackages.add(ClassUtils.getPackageName(clazz));
}
if (basePackages.isEmpty()) {
	basePackages.add(ClassUtils.getPackageName(declaringClass));
}

scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
	@Override
	protected boolean matchClassName(String className) {
		return declaringClass.equals(className);
	}
});
return scanner.doScan(StringUtils.toStringArray(basePackages));

You can see that the ClassPathBeanDefinitionScanner still performs scanning registration, which has been described in < context: component scan / >.

4, Summary

  • XML configuration files generally load the corresponding configuration file as a route first
  • Spring's abstraction of Bean configuration is BeanDefinition
  • Spring will register the corresponding BeanDefinitionRegistryPostProcessor and BeanPostProcessor in the corresponding context
  • BeanDefinitionRegistryPostProcessor will give priority to instantiation and process the user-defined BeanDefinition after obtaining the user-defined BeanDefinition; Here, the user may customize some annotations, such as @ Bean, and construct some corresponding beandefinitions
  • BeanPostProcessor will be instantiated and added to the context before user-defined BeanDefinition
  • When the user-defined BeanDefinition is instantiated, the instantiated BeanPostProcessor will perform an additional processing
  • No matter which way you configure scanning, the ClassPathBeanDefinitionScanner will eventually register BeanDefinition

Topics: Java Spring source code