Writing in the front
In fact, when writing this article, the mood is quite complicated, I thought I had understood the principle of SpringBoot automatic configuration.
I want to write this part in detail, during which I consulted a large number of blogs and articles, and also wrote less than 5,000 words.
Then I want to know about run(); The method did what matter concretely, contacted this big man’s article.
After reading his article, IT became clear to me that I only understood the tip of the iceberg about SpringBoot autoconfiguration…….
After struggling for a long time, I decided to refer to the big guy’s article and run(); The method part is perfect.
The more backward writing during the period of perfection, the more confused, the more aware of their own limitations.
The article was finally finished. But I don’t think of it as my work, I just stitched it up.
Seventy or eighty percent of the article is still written by this big guy, and I know I can’t write an article of this level and depth.
So if you find this post helpful and you want to say thank you, jump to the original author’s blog.
Original author blog park home page
The original address
I think good writers, good articles should be recognized. Not someone like me doing sutures
Principle of SpringBoot automatic configuration
The following quote is from Zhihu
SpringBoot its appearance is light and simple, but it is like a huge monster inside, the monster has hundreds of feet to entangle themselves together, the love of source code readers around the dizzy. But SpringBoot is the big brother in the Java programming world, and you have to accept that. Even if there are thousands of horses running in your heart, it is the best in the world. If you’re an academic programmer and you’re skeptical of life, you have to accept the rule that the most popular in the market is not necessarily the best designed, and there are too many other irrational factors involved.
Automatic assembly principle introduced
From our previous use of SpringBoot, I’m sure you all have two questions.
Why does SpringBoot work when we don’t write anything about configuration?
Why can SpringBoot read and take effect custom configuration items written in configuration files?
There are three things we need to know about SpringBoot autoassembly:
- The spring.factories file in the meta-INF directory
- The main program of SpringApplication. Run (FirstSpringbootApplication. Class, args); methods
- The @SpringBootApplication annotation in the main program
Run () in the main program; methods
public static void main(String[] args) {
SpringApplication.run(FirstSpringbootApplication.class, args);
}
Copy the code
From the outside, there's not a lot of useful information, so let's click on it and see if it uses any auto-configuration stuff.
Click on it to find a chain of calls to the run() method, so let’s go further inside.
public static ConfigurableApplicationContext run(Class
primarySource, String... args) {
return run(newClass<? >[] { primarySource }, args);Copy the code
Instantiation of the SpringApplication class
We see that it first creates an instance of SpringApplication and then calls the Run () method of SpringApplication
public static ConfigurableApplicationContext run(Class
[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
Copy the code
Let’s focus on the process of creating an instance of SpringApplication. Clicking on SpringApplication shows two constructors, the second of which we’ll focus on
public SpringApplication(Class
... primarySources) {
this(null, primarySources);
}
// Omit unnecessary comments
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class
... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// Save the primary configuration class (this is an array, indicating that there can be multiple primary configuration classes)
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// Infer the application type, and then initialize the corresponding environment based on the type. Most commonly used are servlet environments
this.webApplicationType = WebApplicationType.deduceFromClasspath();
/ / initialize the project path under the meta-inf/spring. ApplicationContextInitializer configured in the factories
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// Initializes all configured ApplicationListeners in the project path
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// Find the main configuration class with the main method from multiple configuration classes
this.mainApplicationClass = deduceMainApplicationClass();
}
Copy the code
We continue to point into the getSpringFactoriesInstances (); methods
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, newClass<? > [] {}); }Get the factory instance of the specified Spring from meta-INF /spring.factories using the specified classloader
private <T> Collection<T> getSpringFactoriesInstances(Class
type, Class
[] parameterTypes, Object... args)
{
ClassLoader classLoader = getClassLoader();
// Read the value with key type.getName() from the meta-INF/spring.Factories resource file using the specified classLoader
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// Create a Spring factory instance
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
/ / for Spring factory instance sort (org. Springframework. Core. The annotation. Order annotations specified Order)
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
Copy the code
We can see from the above code have a SpringFactoriesLoader loadFactoryNames (); Method, this method is very important.
This method is the same entry method provided in spring-core to get the specified class (key) from meta-INF/spring.Factories.
Through the debug, we can know that it takes is the key for org. Springframework. Context. ApplicationContextInitializer class.
We found the class in the meta-INF/Spring.Factories file in the project path
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
Copy the code
ApplicationContextInitializer is Spring framework classes, the main purpose of this class is in ConfigurableApplicationContext call the refresh () method before, Call the Initialize method of this class. Through ConfigurableApplicationContext instance container Environment Environment, so as to realize the configuration file modification, etc.
Let’s take a look at this row
ApplicationListener ApplicationContextInitializer above the loading process and the class loading process is the same. The ApplicationListener is a spring event listener. The ApplicationEvent class and the ApplicationListener interface are used to monitor the entire lifecycle of the Spring container. You can also customize listening events.
This concludes the construction process of the SpringApplication class. Looking through the instantiation process of the SpringApplication class, we can see that, with proper use of the class, we can do some preparatory work and customize requirements before the Spring container is created.
Understand the finishedSpringApplication
Class instantiation, so we’re going to get down to business and see what the run method does.
run(); methods
First take a look at the run (); Method source code
public ConfigurableApplicationContext run(String... args) {
// Create a timer
StopWatch stopWatch = new StopWatch();
stopWatch.start();
/ / ConfigurableApplicationContext Spring context
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// Get the listener from meta-INF /spring.factories
// Get and start the listener
SpringApplicationRunListeners listeners = getRunListeners(args);
/ / callback all SpringApplicationRunListeners starting () method
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// Construct the application context
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// Process beans that need to be ignored
configureIgnoreBeanInfo(environment);
/ / print the banner
Banner printedBanner = printBanner(environment);
// Initialize the application context
context = createApplicationContext();
/ / instantiate SpringBootExceptionReporter. Class, used to support the report about the startup errors
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[]{ConfigurableApplicationContext.class}, context);
// The preparation phase before refreshing the application context
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// Refresh the application context
refreshContext(context);
// Extended interface after refreshing the application context
afterRefresh(context, applicationArguments);
// Stop the timer
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// Publish the container start completion event
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;
}
Copy the code
You can almost see that it does the following things
- Gets and starts the listener
- Construct the application context
- Initialize the application context
- Preparation phase before refreshing the application context
- Refreshing the application Context
- The extended interface after the application context is refreshed
Let's analyze them one by one
Gets and starts the listener
The event mechanism is an important part of Spring. It allows you to listen for events that are happening in the Spring container, as well as custom events. Spring’s events provide support for Bean and Bean messaging. When an object finishes processing a certain task, it notifies another object to perform certain processing. In common scenarios, notifications, messages, and emails are sent after certain operations.
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<? >[] types =newClass<? >[] { SpringApplication.class, String[].class };return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
Copy the code
We’re from getRunListeners (); Method and saw a familiar role in getSpringFactoriesInstances, recently we have seen this method.
Back to the run (); Method, the debug it and see SpringApplicationRunListeners listeners = getRunListeners (args); Which listener is obtained
EventPublishingRunListener listener is the Spring container start the listener.
Construct the application context
What does the application context include? Including computer environment, Java environment, running environment of Spring, Spring project configuration (in SpringBoot is the familiar application. The properties/yaml) and so on.
Let’s first look at prepareEnvironment(); Methods.
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// Configure the environment system environment based on user configurations
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
/ / start the corresponding listener, one of the important listener ConfigFileApplicationListener is the listener loading project configuration file.
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
Copy the code
Create and configure the environment for the application type, configure the system environment according to the user’s configuration, start the listener, and load the system configuration file.
- Let’s see
getOrCreateEnvironment();
What did
private ConfigurableEnvironment getOrCreateEnvironment(a) {
if (this.environment ! =null) {
return this.environment;
}
/ / if the type is the SERVLET StandardServletEnvironment is instantiated
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
/ / if the application type for the SERVLET instantiates StandardReactiveWebEnvironment ();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return newStandardEnvironment(); }}Copy the code
It can be seen that different system environment instances are initialized according to different application types.
There’s a way to infer the type of application when you instantiate the SpringApplication that you’re interested in, but I won’t go into it for too long.
configureEnvironment();
methods
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
/ / will be the main function of the args encapsulated into SimpleCommandLinePropertySource adding environment.
configurePropertySources(environment, args);
// Activate the corresponding configuration file
configureProfiles(environment, args);
}
Copy the code
I wanted to debug this method, but it was a waste of space.
listeners.environmentPrepared();
methods
With all the way down into the method to multicastEvent SimpleApplicationEventMulticaster class () method.
Let’s make a breakpoint and debug
See getApplicationListeners (event, type) as a result, found an important listener ConfigFileApplicationListener.
Take a look at the comments for this class
/ * * * {@link EnvironmentPostProcessor} that configures the context environment by loading
* properties from well known file locations. By default properties will be loaded from
* 'application.properties' and/or 'application.yml' files in the following locations:
* <ul>
* <li>file:./config/</li>
* <li>file:./config/{@literal *}/</li>
* <li>file:./</li>
* <li>classpath:config/</li>
* <li>classpath:</li>
* </ul>
* The list is ordered by precedence (properties defined in locations higher in the list
* override those defined in lower locations).
* <p>
* Alternative search locations and names can be specified using
* {@link #setSearchLocations(String)} and {@link #setSearchNames(String)}.
* <p>
* Additional files will also be loaded based on active profiles. For example if a 'web'
* profile is active 'application-web.properties' and 'application-web.yml' will be
* considered.
* <p>
* The 'spring.config.name' property can be used to specify an alternative name to load
* and the 'spring.config.location' property can be used to specify alternative search
* locations or specific files.
* <p>
*/
Copy the code
This listener is taken from comments by default
-
The TAB shows several locations to load the configuration file and add it to the context
- Servlet-based Web applications
- Reactive Web applications
- Non-web applications
environment
Variable. It can also be specified by configuration.
Initialize the application context
In the SpringBoot project, there are three application types
Corresponding to three types of applications, SpringBoot project there are three kinds of corresponding application context, we take the web project as an example, its context for AnnotationConfigServletWebServerApplicationContext namely.
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
protected ConfigurableApplicationContext createApplicationContext(a) { Class<? > contextClass =this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default: contextClass = Class.forName(DEFAULT_CONTEXT_CLASS); }}catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass", ex); }}return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
Copy the code
Let's have a look at AnnotationConfigServletWebServerApplicationContext design.
The application context can be understood as a high-level expression of the IoC container, and it does enrich some high-level functions on the basis of the IoC container.
The application context holds a relationship to the IoC container. One of his attributes is the beanFactory IoC container (DefaultListableBeanFactory). So there’s a relationship between holding and extension.
- See GenericApplicationContext next class
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
private finalDefaultListableBeanFactory beanFactory; .public GenericApplicationContext(a) {
this.beanFactory = newDefaultListableBeanFactory(); }... }Copy the code
The beanFactory is defined in the interfaces being implemented by the AnnotationConfigServletWebServerApplicationContext GenericApplicationContext. Above createApplicationContext () method of BeanUtils. InstantiateClass (contextClass) this method, Not only initializes the AnnotationConfigServletWebServerApplicationContext class, that is, our context context, also triggered GenericApplicationContext constructor of a class, Thus the IoC container was created. Carefully to see if he’s constructor, found a familiar class DefaultListableBeanFactory, yes, DefaultListableBeanFactory IoC container is true colors. In the back of the refresh () method in the analysis, DefaultListableBeanFactory is ubiquitous presence.
Debug skips the createApplicationContext() method.
As shown in the figure above, context is the familiar context (or container, whatever you like), and beanFactory is the real face of what we call the IoC container. Understanding the differences between context and container can help us understand the source code.
Preparation phase before refreshing the application context
Let's start with the prepareContext() method.
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
// Set the container's environment
context.setEnvironment(environment);
// Perform container post-processing
postProcessApplicationContext(context);
/ / perform in a container ApplicationContextInitializer including spring. Factories and custom in three ways
applyInitializers(context);
// Send events prepared by the container to each listener
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Encapsulate the args argument in main as a singleton Bean and register it in the container
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
// Encapsulate printedBanner as a singleton and register it in the container
if(printedBanner ! =null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// Load our startup class and inject it into the container
load(context, sources.toArray(new Object[0]));
// Publish container loaded events
listeners.contextLoaded(context);
}
Copy the code
Set
Load (context, sources.toarray (new Object[0])); For other methods, please refer to the comments.
Follow up the load() method with the source code
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug(
"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
/ / create BeanDefinitionLoader
BeanDefinitionLoader loader = createBeanDefinitionLoader(
getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator ! =null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader ! =null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment ! =null) {
loader.setEnvironment(this.environment);
}
loader.load();
}
Copy the code
getBeanDefinitionRegistry()
private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
if (context instanceof BeanDefinitionRegistry) {
return (BeanDefinitionRegistry) context;
}
if (context instanceof AbstractApplicationContext) {
return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory();
}
throw new IllegalStateException("Could not locate BeanDefinitionRegistry");
}
Copy the code
Here we convert the context we created earlier to BeanDefinitionRegistry,
BeanDefinitionRegistry defines the important method registerBeanDefinition(), This method will BeanDefinition registered into beanDefinitionMap DefaultListableBeanFactory vessel
createBeanDefinitionLoader()
Continue to see createBeanDefinitionLoader () method, finally entered the BeanDefinitionLoader class constructor, as follows
protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[]sources){
return new BeanDefinitionLoader(registry, sources);
}
Copy the code
BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
Assert.notNull(registry, "Registry must not be null");
Assert.notEmpty(sources, "Sources must not be empty");
this.sources = sources;
// Annotated beans define readers such as @configuration @bean @Component @Controller @service etc
this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
// The Bean in XML form defines the reader
this.xmlReader = new XmlBeanDefinitionReader(registry);
if (isGroovyPresent()) {
this.groovyReader = new GroovyBeanDefinitionReader(registry);
}
// Class path scanner
this.scanner = new ClassPathBeanDefinitionScanner(registry);
// The scanner adds an exclusion filter
this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}
Copy the code
Remember the three properties above, and see the comments for details.
The initialization of the IoC container is divided into three steps. The above three attributes play an important role in locating the Resource of the BeanDefinition and registering the BeanDefinition.
loader.load()
private int load(Object source) {
Assert.notNull(source, "Source must not be null");
// Load from Class
if (source instanceofClass<? >) {returnload((Class<? >) source); }// Load from Resource
if (source instanceof Resource) {
return load((Resource) source);
}
// Load from Package
if (source instanceof Package) {
return load((Package) source);
}
// Load from CharSequence.
if (source instanceof CharSequence) {
return load((CharSequence) source);
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}
Copy the code
Currently our main Class is loaded as Class, following up with the load() method.
private int load(Class
source) {
if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
load(loader);
}
if (isEligible(source)) {
// Register the beanDefinitionMap of the startup class
this.annotatedReader.register(source);
return 1;
}
return 0;
}
Copy the code
this.annotatedReader.register(source); Follow up the register () method, and ultimately into the AnnotatedBeanDefinitionReader class doRegisterBean () method.
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
@Nullable BeanDefinitionCustomizer[] customizers) {
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
abd.setInstanceSupplier(supplier);
// Get the scope attribute of the class
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd); abd.setScope(scopeMetadata.getScopeName()); String beanName = (name ! =null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
if(qualifiers ! =null) {
for (Class<? extends Annotation> qualifier : qualifiers) {
if (Primary.class == qualifier) {
abd.setPrimary(true);
}
else if (Lazy.class == qualifier) {
abd.setLazyInit(true);
}
else {
abd.addQualifier(newAutowireCandidateQualifier(qualifier)); }}}if(customizers ! =null) {
for (BeanDefinitionCustomizer customizer : customizers) {
customizer.customize(abd);
}
}
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
// Register the beanDefinitionMap in the IoC container
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
Copy the code
In this method will be the main class encapsulated into AnnotatedGenericBeanDefinition
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry); Method to register beanDefinitionMap with beanDefinitionMap
public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Primary name is an id
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Then register the alias
String[] aliases = definitionHolder.getAliases();
if(aliases ! =null) {
for(String alias : aliases) { registry.registerAlias(beanName, alias); }}}Copy the code
Follow up with the registerBeanDefinition() method.
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
// Last check
// Check the Overrides of the bean
((AbstractBeanDefinition) beanDefinition).validate();
} catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex); }}// Determine if there is a bean with a duplicate name, and then see if override is allowed
// Synchronized is used to implement mutually exclusive access, now ConcurrentHashMap
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if(existingDefinition ! =null) {
// If the class doesn't allow Overriding to throw exceptions directly
if(! isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + existingDefinition + "] bound.");
} else if (existingDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (logger.isWarnEnabled()) {
logger.warn("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.isInfoEnabled()) {
logger.info("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]"); }}else {
if (logger.isDebugEnabled()) {
logger.debug("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]"); }}// Register beanDefinitionMap
this.beanDefinitionMap.put(beanName, beanDefinition);
} else {
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons; }}}else {
// Still in startup registration phase
If you are still in the initial registration phase, register beanDefinitionMap
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if(existingDefinition ! =null|| containsSingleton(beanName)) { resetBeanDefinition(beanName); }}Copy the code
Finally came to registerBeanDefinition DefaultListableBeanFactory class () method, DefaultListableBeanFactory familiar class also? I’m sure you’re familiar with this class. Specific product DefaultListableBeanFactory is the IoC container.
If you look closely at the method registerBeanDefinition(), it first checks to see if it already exists and throws an exception if it does and cannot be overridden. If it doesn’t exist, register it directly in beanDefinitionMap.
Debug skips the prepareContext() method and can see that the BeanDefinition of the launch class is already registered
OK, that’s the end of the fourth step of the startup process, but there’s no need to go into detail here, because the registration process for the startup class BeanDefinition is the same as the registration process for our custom BeanDefinition. This first introduces this process, after familiar with this process is easy to understand. The most important method, the refresh() method, comes soon
Refreshing the application Context
The initialization of the IoC container described above can be roughly divided into three steps
- BeanDefinition’s Resource location
- BeanDefinition load
- Register the BeanDefinition with the IoC container
After describing what the prepareContext() method does in the prepare refresh phase. We now summarize the IoC container initialization process primarily from the Refresh () method.
From the run (); Methods, the refreshContext () method with all the way down, eventually came to refresh AbstractApplicationContext class () method.
public void refresh(a) throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Refresh the context
prepareRefresh();
// This is where refreshBeanFactory() is started in the subclass.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context
prepareBeanFactory(beanFactory);
try {
// post-processing
postProcessBeanFactory(beanFactory);
// Call the BeanFactory postprocessor
invokeBeanFactoryPostProcessors(beanFactory);
// Register the Bean's post-handler, called during Bean creation
registerBeanPostProcessors(beanFactory);
// Initialize the context information source
initMessageSource();
// Initialize the event mechanism in the context
initApplicationEventMulticaster();
// Initialize other special beans
onRefresh();
// Check listening beans and register them in the container
registerListeners();
// Instantiate all (non-lazy-init) singletons
finishBeanFactoryInitialization(beanFactory);
// Finally publish the container event
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.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// 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...resetCommonCaches(); }}}Copy the code
In fact, this method has very detailed English annotations, it is easy to understand.
The refresh() method does so much, one by one, that it may be time for the next article.
We mainly analyze the IoC container initialization steps and IoC dependency injection process.
Around the above process, we mainly introduce the important methods, see the comments for the rest.
obtainFreshBeanFactory();
protected ConfigurableListableBeanFactory obtainFreshBeanFactory(a) {
/ / refresh the BeanFactory
refreshBeanFactory();
/ / get the beanFactory
return getBeanFactory();
}
Copy the code
For the obtainFreshBeanFactory() method, we just get the beanFactory we created earlier.
Follow up refreshBeanFactory GenericApplicationContext class (); Method to find out what he didn’t do
protected final void refreshBeanFactory(a) throws IllegalStateException {
if (!this.refreshed.compareAndSet(false.true)) {
throw new IllegalStateException(
"GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
}
this.beanFactory.setSerializationId(getId());
}
Copy the code
AbstractApplicationContext classes have two subclasses implement refreshBeanFactory (), but the third step before initialization context, instantiated GenericApplicationContext class, So did not enter the refreshBeanFactory AbstractRefreshableApplicationContext () method. this.refreshed.compareAndSet(false, true); Here, this line of code says: GenericApplicationContext allows only refresh again. This line of code is important, not important in Spring, but this line of code itself. Check the checkfield field for the this. field field. Private Final AtomicBoolean checkfield = new AtomicBoolean(); Java J.U.C and the package is an important atomic class AtomicBoolean. Through the class’s compareAndSet(); Method allows a piece of code to be implemented absolutely once.
prepareBeanFactory(beanFactory);
Prepare the BeanFactory. This method is not important. Read the comment.
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// Configure classloaders: Default to use the classloader of the current context
beanFactory.setBeanClassLoader(getClassLoader());
// Configure the EL expression: it is used when the Bean is initialized and the properties are populated
beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
// add the PropertyEditor PropertyEditor
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
// Add the Bean's back handler
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
// Ignore assembling the classes specified below
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
/ / the following class registered to the beanFactory resolvableDependencies attributes (DefaultListableBeanFactory)
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);
// Register the early post-processor as an Application listener to detect the internal bean
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
// If the current BeanFactory contains a loadTimeWeaver Bean, there is a classloading period woven into AspectJ,
/ / is the BeanFactory to class loading in the current period BeanPostProcessor implementation class LoadTimeWeaverAwareProcessor to deal with,
// The class loading phase is woven into AspectJ.
if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
// Set a temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
// Register the current environment variable as a singleton bean
if(! beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) { beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment()); }// register the current systemProperties as a singleton Bean
if(! beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) { beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties()); }// register the current systemEnvironment as a singleton Bean
if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
}
}
Copy the code
postProcessBeanFactory(beanFactory);
The postProcessBeanFactory() method adds a series of Bean afterhandlers to the context. The time for the post-processor to work is after all beanDenifition loads are complete and before the bean is instantiated. Simply put, the Bean’s backend handler can modify the BeanDefinition property information.
About this method so first, interested friends can search the method. There is not enough space to introduce this method.
- invokeBeanFactoryPostProcessors(beanFactory); (key)
Said above, the IoC container of the initialization process includes three steps, the invokeBeanFactoryPostProcessors () method to complete the IoC container initialization process of three steps.
1, the first step: Resource location
In SpringBoot, we know that its package scan starts with the package that the main class is in, and prepareContext() will parse the main class into a BeanDefinition, And then the refresh () method of invokeBeanFactoryPostProcessors analytical main class () method of BeanDefinition access basePackage paths. This completes the positioning process. Secondly SpringBoot starter is through the SPI of extension mechanism to realize the automatic assembling, SpringBoot automatic assembly is also in invokeBeanFactoryPostProcessors () method. There is also a situation, there are a lot of in SpringBoot @ EnableXXX annotations, careful in seeing should know its underlying is the @ Import annotations, in invokeBeanFactoryPostProcessors () method is realized with the annotations to specify the configuration of the class loading.
There are three common implementations in SpringBoot: the first is for the main class package, the second is for the automatic assembly of the SPI extension mechanism implementation (such as various starter), and the third is for the classes specified by the @import annotation. (Not to mention the unconventional)
Step 2: Load the BeanDefinition
The first step describes the location of the three resources, followed by the loading of the BeanDefinition. The so-called load is the basePackage obtained from the above location, SpringBoot will concatenate this path into: The classpath * : org/springframework/boot/demo / * * / *. Class form like this, Then a class called PathMatchingResourcePatternResolver will all under the path. The class files are loaded in, and then traverse the judgment whether have @ Component annotation, if any, is our BeanDefinition to load. So that’s the general process.
3. Third process: Register BeanDefinition
This is done by calling the implementation of the BeanDefinitionRegister interface mentioned above. This registration process registers the BeanDefinitions resolved during loading with the IoC container. From the above analysis, we can see that beanDefinitions are injected into a ConcurrentHashMap in the IoC container. The IoC container holds these BeanDefinitions data through this HashMap. Such as the beanDefinitionMap DefaultListableBeanFactory properties.
OK, that’s it. Let’s look at the code and see how it works.
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); . }/ / PostProcessorRegistrationDelegate class
public static void invokeBeanFactoryPostProcessors( ConfigurableListableBeanFactory beanFactory, List
beanFactoryPostProcessors)
{... invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); . }/ / PostProcessorRegistrationDelegate class
private static void invokeBeanDefinitionRegistryPostProcessors( Collection
postProcessors, BeanDefinitionRegistry registry) {
for(BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) { postProcessor.postProcessBeanDefinitionRegistry(registry); }}/ / ConfigurationClassPostProcessor class
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {... processConfigBeanDefinitions(registry); }/ / ConfigurationClassPostProcessor class
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {...do{ parser.parse(candidates); parser.validate(); . }... }Copy the code
Follow the call stack all the way to the Parse () method of the ConfigurationClassParser class.
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
/ / if it is SpringBoot project in, bd is the main class in front of the encapsulation of AnnotatedGenericBeanDefinition (AnnotatedBeanDefinition interface implementation class)
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else{ parse(bd.getBeanClassName(), holder.getBeanName()); }}catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); }}// Load the default configuration --> (this is the automatic assembly entry for springboot projects)
this.deferredImportSelectorHandler.process();
}
Copy the code
Look at the comment above, in prepareContext(); Method, we introduced our Lord how class is encapsulated into AnnotatedGenericBeanDefinition step by step, and register of beanDefinitionMap into the IoC container.
Continue along parse((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanname ()); Method follow along
1 / / ConfigurationClassParser class
2 protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
3 processConfigurationClass(new ConfigurationClass(metadata, beanName));
4 }
5 / / ConfigurationClassParser class
6 protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
7.8 // Recursively process the configuration class and its superclass hierarchy.
9 // Process the configuration classes and their parent hierarchies recursively.
10 SourceClass sourceClass = asSourceClass(configClass);
11 do {
12 // Recursively process the Bean, if it has a parent class, recursively up to the top parent class
13 sourceClass = doProcessConfigurationClass(configClass, sourceClass);
14 }
15 while(sourceClass ! =null);
16
17 this.configurationClasses.put(configClass, configClass);
18 }
19 / / ConfigurationClassParser class
20 protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
21 throws IOException {
22
23 // Recursively process any member (nested) classes first
24 // The inner class is handled recursively first. (The main class of a SpringBoot project usually has no inner class.)
25 processMemberClasses(configClass, sourceClass);
26
27 // Process any @PropertySource annotations
28 // Attribute configuration handling for the @propertysource annotation
29 for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
30 sourceClass.getMetadata(), PropertySources.class,
31 org.springframework.context.annotation.PropertySource.class)) {
32 if (this.environment instanceof ConfigurableEnvironment) {
33 processPropertySource(propertySource);
34 } else {
35 logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
36 "]. Reason: Environment must implement ConfigurableEnvironment");
37 }
38 }
39
40 // Process any @ComponentScan annotations
41 // Scan the beans in the project according to the @ComponentScan annotation (available on the SpringBoot boot class)
42 Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
43 sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
44 if(! componentScans.isEmpty() &&45 !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
46 for (AnnotationAttributes componentScan : componentScans) {
47 // The config class is annotated with @ComponentScan -> perform the scan immediately
48 // Perform the scan immediately (why the SpringBoot project scans from the package where the main class is, that's the key)
49 Set<BeanDefinitionHolder> scannedBeanDefinitions =
50 this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
51 // Check the set of scanned definitions for any further config classes and parse recursively if needed
52 for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
53 BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
54 if (bdCand == null) {
55 bdCand = holder.getBeanDefinition();
56 }
57 // Check if there is a ConfigurationClass (configuration/ Component annotations), and if so, recursively find the configuration class associated with that class.
58 // Related Configuration classes, such as the beans defined by @bean in @Configuration. Or keep the @import annotation on classes that have the @Component annotation.
59 if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
60 parse(bdCand.getBeanClassName(), holder.getBeanName());
61 }
62 }
63 }
64 }
65
66 // Process any @Import annotations
67 // Recursively handle @import annotations (the various @enable *** annotations commonly used in SpringBoot projects are basically encapsulated @import)
68 processImports(configClass, sourceClass, getImports(sourceClass), true);
69
70 // Process any @ImportResource annotations
71 AnnotationAttributes importResource =
72 AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
73 if(importResource ! =null) {
74 String[] resources = importResource.getStringArray("locations");
75 Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
76 for (String resource : resources) {
77 String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
78 configClass.addImportedResource(resolvedResource, readerClass);
79 }
80 }
81
82 // Process individual @Bean methods
83 Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
84 for (MethodMetadata methodMetadata : beanMethods) {
85 configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
86 }
87
88 // Process default methods on interfaces
89 processInterfaces(configClass, sourceClass);
90
91 // Process superclass, if any
92 if (sourceClass.getMetadata().hasSuperClass()) {
93 String superclass = sourceClass.getMetadata().getSuperClassName();
94 if(superclass ! =null && !superclass.startsWith("java") &&
95 !this.knownSuperclasses.containsKey(superclass)) {
96 this.knownSuperclasses.put(superclass, configClass);
97 // Superclass found, return its annotation metadata and recurse
98 return sourceClass.getSuperClass();
99 }
100 }
101
102 // No superclass -> processing is complete
103 return null;
104 }
Copy the code
See doProcessConfigurationClass () method. (SpringBoot package scan entry method, emphasis oh)
Let’s look at what’s going on in this method first, and then read the source code analysis later.
Parse (bdcand.getBeanclassName (), holder.getBeanname ()) on line 60 of the above code; The call is recursive, because when Spring scans for classes to load, it further determines whether each class meets the @Component/@Configuration annotation. If so, it recursively calls the parse() method to find its associated class. Likewise line 68 processImports(configClass, sourceClass, getImports(sourceClass), true); Classes found through the @import annotation also recursively look up their related classes. The two recursions will be very messy when debugging, and it is even more difficult to understand in words. Therefore, we only focus on the parsing of the main class and the scanning process of the class.
The above code for line 29 (AnnotationAttributes propertySource: AnnotationConfigUtils. AttributesForRepeatable (… Get the @propertysource annotation on the main class, parse the annotation and store the values in the properties configuration file specified by the annotation into Spring’s Environment. The Environment interface provides methods to read values in the configuration file. The parameter is the key value defined in the Properties file.
Line 42 Set componentScans = AnnotationConfigUtils. AttributesForRepeatable (sourceClass. For getMetadata (), ComponentScans.class, ComponentScan.class); Parsing the @ComponentScan annotation on the main class, well, the code after line 42 will parse that annotation and do a package scan.
68 line processImports(configClass, sourceClass, getImports(sourceClass), true); Parse the @import annotation on the main class and load the configuration class specified by the annotation.
In Spring, many annotations are wrapped layer by layer. For example, @enablexxx is a secondary wrapper for the @import annotation. @SpringBootApplication annotation =@ComponentScan+@EnableAutoConfiguration+ @import +@Configuration+@Component. @Controller, @Service and so on are secondary encapsulation of @Component.
Let’s see what lines 42-64 did
Looking down from line 42 above, Come to 49 Set scannedBeanDefinitions = this.com ponentScanParser parse (componentScan, sourceClass.getMetadata().getClassName());
Enter the method
/ / ComponentScanAnnotationParser class
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader); .// According to declaringClass (in the case of a SpringBoot project, the full pathname of the main class)
if(basePackages.isEmpty()) { basePackages.add(ClassUtils.getPackageName(declaringClass)); }...// Scan classes against basePackages
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
Copy the code
I found two important lines of code
To verify the comments in the code, debug, look at declaringClass, which is indeed the full pathname of our main class as shown in the figure below
Skip this line and continue to debug basePackages. There is only one set in basePackages, which is the path of the main class.
TIPS: Why use a collection when there is only one, because we can also specify the scan path with the @ComponentScan annotation.Copy the code
This is the first of three steps in IoC container initialization. The Resource location is complete and the package containing the main class is successfully located.
Then look down return scanner. DoScan (StringUtils. ToStringArray (basePackages)); How Spring does class scanning. Enter the doScan() method.
/ / ComponentScanAnnotationParser class
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) {
// Scans the beans to be loaded from the specified package
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);
// Register the Bean in the IoC container (beanDefinitionMap)
registerBeanDefinition(definitionHolder, this.registry); }}}return beanDefinitions;
}
Copy the code
Set candidates = findCandidateComponents(basePackage); Scan classes from basePackage and parse them into BeanDefinitions. Once you have all qualified classes registerBeanDefinition(definitionHolder, this.registry) on line 24; Register this class in the IoC container. In other words, this method completes the second and third steps of the IoC container initialization process, loading the BeanDefinition, and registering the BeanDefinition.
findCandidateComponents(basePackage);
Trace call stack
/ / ClassPathScanningCandidateComponentProvider class
public Set<BeanDefinition> findCandidateComponents(String basePackage) {...else {
returnscanCandidateComponents(basePackage); }}/ / ClassPathScanningCandidateComponentProvider class
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
/ / stitching scanning path, such as: the classpath * : org/springframework/boot/demo / * * / *. Class
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// Scan all classes from the packageSearchPath
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// // determines whether the class is annotated by the @Component annotation and not a class that needs to be excluded
if (isCandidateComponent(metadataReader)) {
/ / this class encapsulation into ScannedGenericBeanDefinition (BeanDefinition interface implementation class) class
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
} else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: "+ resource); }}}else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: "+ resource); }}}catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: "+ resource, ex); }}else {
if (traceEnabled) {
logger.trace("Ignored because not readable: "+ resource); }}}}catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
Copy the code
In line 13 basePackage spliced into the classpath * : org/springframework/boot/demo / / * * *. The class, in the 16 getResources (packageSearchPath); Method to scan all classes in that path. Then iterate over the Resources and determine at line 27 whether the class is marked by the @Component annotation and not a class to be excluded. In 29 will scan to class, the parsed into ScannedGenericBeanDefinition, this class is a BeanDefinition interface implementation class. OK, the loading of the BeanDefinition of the IoC container ends here.
Going back to the doScan() method, debug looks at the result (the screenshot shows the class I located that needs to be handed over to the Spring container for management).
registerBeanDefinition(definitionHolder, this.registry);
Look at the registerBeanDefinition() method. Isn’t it a bit familiar, introduced in front prepareContext () method, we introduced the main class of BeanDefinition is how of registered into DefaultListableBeanFactory beanDefinitionMap step by step. Let’s just skip the 1W here. Once the BeanDefinition registration is complete, the IoC container initialization process is complete. At this point, the configuration information for the entire Bean has been established in the IoC container DefaultListableFactory used, and these BeanDefinitions are available for use by the container. They are all retrieved and used in the BeanbefinitionMap. The container’s role is to process and maintain this information. This information is the basis for container resume dependency inversion.
protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
}
Copy the code
OK, this completes the three steps of the IoC container initialization process. Of course this is just the orientation, loading, and registration of BeanDefinition for SpringBoot’s package scanning orientation. As mentioned earlier, there are also two ways @import and the starter auto-assembly of the SPI extension implementation.
@import Parsing of annotations
Most of the @enableXXX annotations are secondary encapsulation of @import (for decoupling purposes, for example, when the @import class changes, our business system does not need to change any code).
Well, We want to go back to the previous ConfigurationClassParser doProcessConfigurationClass method of a class of line 68 processImports (configClass, sourceClass. getImports(sourceClass), true); , the jump is relatively large. As explained above, we only analyze the main class, because we have recursion.
processImports(configClass, sourceClass, getImports(sourceClass), true); The configClass and sourceClass parameters are corresponding to the main class.
TIPS: While analyzing this section, I annotated @enablecaching on the main class.Copy the code
GetImports (sourceClass);
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
Set<SourceClass> imports = new LinkedHashSet<>();
Set<SourceClass> visited = new LinkedHashSet<>();
collectImports(sourceClass, imports, visited);
return imports;
}
Copy the code
debug
The class specified by the @import annotation in the @Enablecaching annotation. The other two are classes specified by the @import annotation in the @SpringBootApplication on the main class. You can peel back the @SpringBootApplication annotations one layer at a time.
Debug the processImports() method by yourself.
Due to the word limit of the Nuggets, I will save the rest of the article for the next post:Ultra-detailed Analysis of SpringBoot automatic configuration principle (next)