I was asked about the principle of Springboot automatic assembly during the interview. I'm sorry, hold it!!! No loss in collection

Keywords: Java Spring Spring Boot Interview source code

springboot version 2.5.5

Debug Roadmap

Many words are tears. Let's look at the picture.

Let's start with run

After using Springboot for so many years, what has this run() method done?

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

ascription

The run() method belongs to the SpringApplication.class object, so you need to instantiate the SpringApplication.class object before calling the run() method:

	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

What did you do when instantiating the SpringApplication.class object?
Here, we mainly need to pay attention to two methods: ① getSpringFactoriesInstances() and ② deduceMainApplicationClass()

/**
 * The constructor actually called when instantiating
 */
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		// spring. Com will be used here
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
		// Set initializer
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		// Set listener
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

① The getSpringFactoriesInstances() method mainly loads the spring.factories file in the whole application, and puts the contents of the file into the cache object for subsequent access.

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		// The initial load failed to get data from the cache, so it is null
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}
		result = new HashMap<>();
		try {
			// FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories", is this configuration class familiar
			// Load configuration class
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				// Load resources here
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}
			// Replace all lists with unmodifiable lists containing unique elements
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			// Here, put the obtained resources into the cache
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

② The deduceMainApplicationClass() method returns the class information of the startup class:

summary

Instantiating the SpringApplication.class object accomplishes two things:

1. Load the spring.factories file in the whole application and put the contents of the file into the cache object for subsequent access.
2. Class information of startup class is returned.

run

With the SpringApplication.class object instance object, you can then call the run() method.

In the run() method, we mainly focus on two methods: prepareContext() and refreshContext()

public ConfigurableApplicationContext run(String... args) {
// Omit some codes
	try {
		prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
		refreshContext(context);
	}
// Omit some codes

The prepareContext() method prepares the context environment and loads the startup class by calling the load() method to prepare for obtaining the annotations on the startup class;

private void load(Class<?> source) {
		if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
			// Any GroovyLoaders added in beans{} DSL can contribute beans here
			GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
			((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans());
		}
		// Here we will judge that the startup class is not a groovy closure or an anonymous class
		if (isEligible(source)) {
			// Register and read the annotation information of the startup class
			// Note that the startup type is registered as AnnotatedBeanDefinition type here, which will be used later in parse() parsing.
			this.annotatedReader.register(source);
		}
	}

The refreshContext() method finally calls the refresh() of the AbstractApplicationContext.class class class. I'm sure all the friends who have seen the spring source code are familiar with the refresh() method.

The main battlefield of automatic assembly operation is ① invokebeanfactoryprocessors() method, ① calling ② invokebeanfinitionregistrypostprocessors() method, ② calling ③ postProcessBeanDefinitionRegistry() method of ConfigurationClassPostProcessor.class, ③ calling ④ processConfigBeanDefinitions() method;

④ In processConfigBeanDefinitions() method:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();
		// Here, the loop matching is added to the startup class and added to the configCandidates collection above.
		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			}
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}
		
		// ...
		
		// Resolve each class marked with @ Configuration annotation, and @ SpringBootApplication on the startup class contains @ Configuration annotation
		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");
			// Start parsing
			parser.parse(candidates);
			parser.validate();
	}
		// ============Split line=================
/**
 * For ease of reading, the parse() method is accessed here
 */
public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try {
				// As mentioned earlier in the load() method in the prepareContext() method, this will enter the judgment analysis
				if (bd instanceof AnnotatedBeanDefinition) {
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}	
		// ...
		}
		// be careful!! Here, after parse() parsing is completed, it will return here to execute the process() method;
		this.deferredImportSelectorHandler.process();
	}

After entering the judgment, the parse() method will then call ① processConfigurationClass() method, ① call ② doProcessConfigurationClass() method;

② In doProcessConfigurationClass(), further parsing of annotations is started, including @ PropertySource, @ ComponentScan, @ Import (let's see this), @ ImportResource, @ Bean. Before parsing, the collectImports() method will be called through the getImports() method to count the type information marked by @ Import;

	protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {
		// ...
		// Process any @Import annotations
		processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
		// ...
// ============Split line=================
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
		Set<SourceClass> imports = new LinkedHashSet<>();
		Set<SourceClass> visited = new LinkedHashSet<>();
		collectImports(sourceClass, imports, visited);
		return imports;
	}
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
			throws IOException {

		if (visited.add(sourceClass)) {
			for (SourceClass annotation : sourceClass.getAnnotations()) {
				String annName = annotation.getMetadata().getClassName();
				if (!annName.equals(Import.class.getName())) {
					// Notice here that the self - invoking recursive query
					collectImports(annotation, imports, visited);
				}
			}
			imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
		}
	}
}
  • Display of query results of getImports() method:

When parse() method parse the @Import annotation, the little buddy here can see the parse() method above, I have code annotation, then start calling process() method, calling processGroupImports() method, then calling grouping.getImports(), and calling process process of DeferredImportSelector.Group interface. Here we look at the implementation of the implementation of AutoConfigurationImportSelector.class implementation of the process() method (here we need to pay attention to, will soon come back to use), 4 called getAutoConfigurationEntry(), called getCandidateConfigurations() method;

Here's the point: after the above series of method calls, we finally came to this method. I believe you have seen ⑥ getCandidateConfigurations() in many blogs, but we didn't make it clear how to call this method. We just said that this class will get the class name collection of the class to be configured, etc;

In the ⑥ method, it is shown that an EnableAutoConfiguration.class annotation object is returned through the ⑦ getspringfactoriesloaderfactory class() method, and then through the call ⑧ loadFactoryNames(), ⑧ calls ⑨ loadSpringFactories();

⑧ ⑨ does the method look familiar? Yes, we called these two methods when initializing the SpringApplication object. When calling ⑨, we put the contents of the spring.factories file into the cache object.

@Override
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		// Getspringfactoryesloaderfactoryclass() this method returns an EnableAutoConfiguration.class annotation object
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
				+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
}

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
}
// ===========================Split line===========================
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}
	// ...

At this time, there are EnableAutoConfiguration objects in the cache object, size=131:

These 131 are the auto assembly configuration items in the spring.factories file:

Of course, many classes that we don't need are also assembled. Don't worry here. Then look down. After the assembly is completed, we return to ⑤ getAutoConfigurationEntry() method, and return a configuration class information collection of list < string >, and then what do we do?

As can be seen from the above figure, after filtering and removing 131 configuration information, it finally becomes 13 to be used. After getting the final configuration information, according to the configuration information, start automatic loading (what are you waiting for, sprinkle flowers quickly!). Here, the automatic assembly of Springboot is basically over.

Again, notes

@Springboot application is correct. This annotation is familiar to everyone. It is found on the springboot project startup class:

@SpringBootApplication contains two annotations:

  • @EnableAutoConfiguration (key): enable the automatic configuration mechanism of SpringBoot;
  • @ComponentScan: scan bean s annotated by @ Component (@Service,@Controller). The annotation will scan all classes under the package where the class is located by default;
  • @Spring boot configuration: allows you to register additional bean s or import other configuration classes in the context;

Among the three annotations, the core @ EnableAutoConfiguration of automatic assembly is this annotation:

@The EnableAutoConfiguration annotation imports the AutoConfigurationImportSelector.class class through the @ Import annotation provided by Spring (@ Import annotation can Import the configuration class or Bean into the current class). The role of this class is also mentioned above (obtain the class name collection of the class to be configured in the spring.factories file).

Thank you for your: likes, collections and comments!

Posted by EdN on Wed, 06 Oct 2021 12:53:48 -0700