Note: The source code analysis corresponds to SpringBoot version 2.1.0.release

1 introduction

ConditionalOnXXX ConditionalOnXXX ConditionalOnXXX

ConditionalOnXxx (ConditionalOnXxx) ConditionalOnXxx (ConditionalOnXxx)

  1. All SpringBoot@ConditionalOnXxxThe conditions of the classOnXxxConditionIt’s all inherited fromSpringBootConditionThe base class, andSpringBootConditionAnd to achieve theConditionInterface.
  2. SpringBootConditionThe base class is mainly used to print the logs of some condition evaluation reports. The condition evaluation information all comes from its subclass annotation condition classOnXxxCondition, so it also abstracts a template methodgetMatchOutcomeIt is left to subclasses to implement to evaluate whether their conditional annotations meet the conditions.
  3. There is also an important point that we have not covered in the previous article, and that is related to filtering auto-configuration class logicAutoConfigurationImportFilterInterface, this article we are going to fill this hole.

In front of us is closely related with SpringBoot automatic configuration is analyzed conditions of built-in annotations @ ConditionalOnXxx, now we start to lu SpringBoot automatic configuration of the relevant source code.

2 @ SpringBootApplication annotation

Before we start, let’s think about how SpringBoot can automatically configure a large number of Starter classes by executing a simple run method with the @SpringBootApplication annotation. SpringBoot automatic configuration with @springBootApplication annotation, let’s take a look at the source of this annotation:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration 
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
/ /... Omit non-critical code
}
Copy the code

@SpringBootApplication has a lot of annotations, and we can see that one of them is @enableAutoConfiguration, so, Certainly SpringBoot automatic configuration is definitely is closely related with @ EnableAutoConfiguration (including @ ComponentScan annotation also has a class AutoConfigurationExcludeFil excludeFilters attribute Ter, this class also has something to do with auto-configuration, but it’s not our focus. Now let’s open the @enableAutoConfiguration annotation source:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<? >[] exclude()default {};
	String[] excludeName() default {};
}
Copy the code

See @ @ AutoConfigurationPackage EnableAutoConfiguration annotations and marked and @ Import two annotations (AutoConfigurationImportSelector. Class), as the name implies, The @autoConfigurationPackage annotation must be associated with automatically configured packages, The AutoConfigurationImportSelector is associated with automatic configuration choice import SpringBoot (ImportSelector in Spring is used to import the configuration of the class, The decision to import a configuration class is usually based on @conditionalonxxxx).

Therefore, it can be seen that AutoConfigurationImportSelector class is our focal point of this because SpringBoot automatic configuration must have a configuration class, While this configuration class import need accomplished by AutoConfigurationImportSelector this elder brothers.

Finished next we focus to see AutoConfigurationImportSelector this class, we will have a simple analysis of @ AutoConfigurationPackage this annotation logic.

How to find SpringBoot automatic configuration implementation logic entry method?

Certainly SpringBoot automatic configuration of logic must be associated with AutoConfigurationImportSelector this class, so how do we go to find SpringBoot logical entry method to realize automatic configuration?

Looking for realization of automatic configuration SpringBoot logic before entry method, we first look at AutoConfigurationImportSelector related class diagram, well have a whole understanding. See below:

Can see AutoConfigurationImportSelector key is to realize the DeferredImportSelector interfaces and various Aware, And then the DeferredImportSelector interface inherits the ImportSelector interface.

Naturally, we will pay attention to implementation method of selectImports AutoConfigurationImportSelector autotype DeferredImportSelector interface, because selectImports method associated with imported automatic configuration class, This method is often the entry method for program execution. SelectImports (selectImports) : selectImports (selectImports) : selectImports (selectImports) : selectImports (selectImports) : selectImports (selectImports) : selectImports

At this point the plot development seems to be not very logical, how do we find the automatic configuration logic of the entry method?

The simplest method is in AutoConfigurationImportSelector breakpoint on each of the methods of a class, then see which methods to perform to debug. But instead of doing this, let’s recall that when we customize a Starter we do it in the Spring. factories configuration file

EnableAutoConfiguration=XxxAutoConfiguration
Copy the code

Can therefore be concluded that SpringBoot automatic configuration principle must be followed the spring. The factories in the configuration file loaded automatically configure class, then combining AutoConfigurationImportSelector annotation methods, We found the getAutoConfigurationEntry method. So we create a breakpoint in this method, call the stack frame to see where the upper-level entry method is, and then we start with the upper-level entry method related to auto-configuration.

With automatic configuration by figure 1. We can see that are logically related entry method in DeferredImportSelectorGrouping getImports method of a class, So let’s start from DeferredImportSelectorGrouping class getImports method to analyze SpringBoot automatic configuration of the source code.

4 Analyze the principle of SpringBoot automatic configuration

Since found ConfigurationClassParser. GetImports () method is an automatic configuration related entry method, so let’s to really analyze SpringBoot automatic configuration of the source.

Take a look at the getImports method code:

// ConfigurationClassParser.java

public Iterable<Group.Entry> getImports() {
    / / traverse DeferredImportSelectorHolder object collection deferredImports, deferredImports collection with various ImportSelector, Of course, here is AutoConfigurationImportSelector
    for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
    	// [1], use the AutoConfigurationGroup process method to handle the auto-configurationgroup logic, and determine which configuration classes to import.
    	this.group.process(deferredImport.getConfigurationClass().getMetadata(),
    			deferredImport.getImportSelector());
    }
    // [2], and then select which configuration classes to import
    return this.group.selectImports();
}
Copy the code

The code in label [1] is the most important part of our analysis. Most of the logic related to automatic configuration is all here. The main logic of automatic configuration will be analyzed in depth in 4.1. So this. Group. The process (deferredImport getConfigurationClass (.) for getMetadata (), deferredImport. GetImportSelector ()); Mainly do is in this. Group AutoConfigurationGroup object process method, namely the incoming AutoConfigurationImportSelector object to choose some qualified automatic configuration, Filtering out some of the auto-configuration classes that don’t meet the criteria, that’s all there is to it.

Note:

  1. AutoConfigurationGroupIs:AutoConfigurationImportSelectorThe inner class, mainly used to handle auto-configuration related logic, ownsprocessandselectImportsMethod, and then haveentriesandautoConfigurationEntriesCollection properties, which store the qualified auto-configuration classes that are processed, respectively, and that’s all we need to know;
  2. AutoConfigurationImportSelector: Undertake most of the logic of automatic configuration, and be responsible for selecting some qualified automatic configuration classes;
  3. metadata: marked on the SpringBoot boot class@SpringBootApplicationAnnotation metadata

The this.group.selectimports method in standard [2] is mainly used to selectively import the auto-configuration class after the previous process method, which will be further analyzed in 4.2 Selectively importing auto-configuration classes.

4.1 Analyzing the logic of automatic configuration

Here we continue to explore how the this.group.process method in the section [1] of analyzing the principle of SpringBoot automatic configuration handles the logic related to automatic configuration.

// AutoConfigurationImportSelector$AutoConfigurationGroup.java

// This is used to deal with auto-configuration classes, such as filtering out auto-configuration classes that do not match the criteria
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
	Assert.state(
			deferredImportSelector instanceof AutoConfigurationImportSelector,
			() -> String.format("Only %s implementations are supported, got %s",
					AutoConfigurationImportSelector.class.getSimpleName(),
					deferredImportSelector.getClass().getName()));
	/ / 【 1 】, call getAutoConfigurationEntry method for automatic configuration in autoConfigurationEntry object class
	AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
			.getAutoConfigurationEntry(getAutoConfigurationMetadata(),
					annotationMetadata);
	// [2], and put the autoConfigurationEntry object encapsulated in the autoConfigurationEntries collection
	this.autoConfigurationEntries.add(autoConfigurationEntry); 
	// [3], traverses the auto-configuration class just obtained
	for (String importClassName : autoConfigurationEntry.getConfigurations()) {
		AnnotationMetadata is inserted into the entries collection as a value
		this.entries.putIfAbsent(importClassName, annotationMetadata); }}Copy the code

Code above in the way we look at standard [1] getAutoConfigurationEntry, this method is mainly used to retrieve automatically configure class, bear the automatic configuration of main logic. Directly on the code:

// AutoConfigurationImportSelector.java

// Get the auto-configuration classes that meet the conditions to avoid memory waste by loading unnecessary auto-configuration classes
protected AutoConfigurationEntry getAutoConfigurationEntry( AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
	/ / get if there is a spring configuration. The boot. Enableautoconfiguration properties, return true by default
	if(! isEnabled(annotationMetadata)) {return EMPTY_ENTRY;
	}
	// The Configuration class annotated by @Congiguration is the annotated data of the introspectedClass.
	/ / such as: @ SpringBootApplication (exclude = FreeMarkerAutoConfiguration. Class)
	/ / will get to exclude = FreeMarkerAutoConfiguration. The class and excludeName = "" annotation data
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	// [1] get all the auto-configuration classes configured in the Spring. factories file
	List<String> configurations = getCandidateConfigurations(annotationMetadata,
			attributes);
	// Use LinkedHashSet to remove duplicate configuration classes
	configurations = removeDuplicates(configurations);
	// Get the auto-configuration class to exclude, such as the configuration class for the annotation attribute exclude
	/ / such as: @ SpringBootApplication (exclude = FreeMarkerAutoConfiguration. Class)
	/ / will get to exclude = FreeMarkerAutoConfiguration. The class of annotation data
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
	// Check the configuration classes to be excluded, as some are not auto-configuration classes, so throw an exception
	checkExcludedClasses(configurations, exclusions);
	// [2] Remove the configuration class to be excluded
	configurations.removeAll(exclusions);
	// [3] Because there are so many auto-configuration classes from the spring.factories file, if some unnecessary auto-configuration classes are loaded into memory, it will cause memory waste
	// Note that there will be calls AutoConfigurationImportFilter match method to determine compliance with @ ConditionalOnBean, @ ConditionalOnClass or @ ConditionalOnWebApplication, We'll focus on that later
	configurations = filter(configurations, autoConfigurationMetadata);
	/ / [4] to obtain the qualified automatic configuration after class, trigger AutoConfigurationImportEvent events at this time,
	/ / the purpose is to tell ConditionEvaluationReport condition evaluation report, to record the eligible automatic configuration object class
	// When will the event be triggered? -- > in a container called when invokeBeanFactoryPostProcessors rear trigger when the processor
	fireAutoConfigurationImportEvents(configurations, exclusions);
	// [5] Encapsulates qualified and excluded auto-configuration classes into an AutoConfigurationEntry object and returns it
	return new AutoConfigurationEntry(configurations, exclusions); 
}
Copy the code

The main thing that the AutoConfigurationEntry method does is to get the auto-configuration classes that meet the criteria and avoid wasting memory by loading unnecessary auto-configuration classes. Here’s a summary of what the AutoConfigurationEntry method does:

[1] Load the EnableAutoConfiguration class from the Spring. factories configuration file (note that it is in the cache at this point) and the resulting autoconfiguration class is shown in Figure 3. Here we just need to know what the method does, and there will be another article detailing the principle of Spring.Factories;

[2] If @enableAutoConfiguration is marked with an autoconfiguration class to exclude, exclude this autoconfiguration class.

[3] After the automatic configuration classes to exclude are excluded, the filter method is called for further filtering, and some automatic configuration classes that do not meet the conditions are excluded again. More on this later.

After heavy filtering [4], and the extra trigger AutoConfigurationImportEvent event, tell ConditionEvaluationReport condition assessment object, to record the eligible automatic configuration classes; (the detailed analysis in this section 6 AutoConfigurationImportListener.)

[5] Finally, the automatic configuration classes that meet the conditions are returned.

AutoConfigurationEntry methods are summarized after the main logic, we again to scrutinize the AutoConfigurationImportSelector filter method:

// AutoConfigurationImportSelector.java

private List<String> filter(List
       
         configurations, AutoConfigurationMetadata autoConfigurationMetadata)
        {
	long startTime = System.nanoTime();
	// Roll out an array of strings from the auto-configuration classes obtained from spring.factories
	String[] candidates = StringUtils.toStringArray(configurations);
	// Define the skip array, whether to skip. Note the order of the SKIP array and the candidates array
	boolean[] skip = new boolean[candidates.length];
	boolean skipped = false;
	/ / getAutoConfigurationImportFilters method: to obtain a OnBeanCondition OnClassCondition and OnWebApplicationCondition
	// Then iterate through the three conditional classes to filter the large number of configuration classes loaded from Spring.factories
	for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
		/ / call aware methods, will beanClassLoader, such as the beanFactory injected into the filter object,
		/ / the filter object namely OnBeanCondition here, OnClassCondition or OnWebApplicationCondition
		invokeAwareMethods(filter);
		// Check the various filters for each candidate
		/ / @ ConditionalOnClass, @ ConditionalOnBean and @ ConditionalOnWebApplication annotations) matches,
		// Note that the candidates array corresponds to the match array
		/ * * * * * * * * * * * * * * * * * * * * * * ", focus on "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
		boolean[] match = filter.match(candidates, autoConfigurationMetadata);
		// Walk through the match array and note that the match order corresponds to the automatic configuration class of the candidates
		for (int i = 0; i < match.length; i++) {
			// If there is a mismatch
			if(! match[i]) {// Mismatches will be recorded in the SKIP array, marking skip[I] as true and corresponding to the candidates' array
				skip[i] = true;
				// Empty the corresponding auto-configuration class because of a mismatch
				candidates[i] = null;
				To be skipped or deflected
				skipped = true; }}}/ / here said if all automatic configuration class after OnBeanCondition, OnClassCondition and OnWebApplicationCondition filtered, all match, all the same to return
	if(! skipped) {return configurations;
	}
	// Create the result set to hold the matching autoconfiguration class
	List<String> result = new ArrayList<>(candidates.length); 
	for (int i = 0; i < candidates.length; i++) {
		// If skip[I] is false, it is an automatic configuration class that meets the conditions and is added to the result set
		if (!skip[i]) { 
			result.add(candidates[i]);
		}
	}
	// Prints logs
	if (logger.isTraceEnabled()) {
		int numberFiltered = configurations.size() - result.size();
		logger.trace("Filtered " + numberFiltered + " auto configuration class in "
				+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)
				+ " ms");
	}
	// Finally return the auto-configuration class that meets the criteria
	return new ArrayList<>(result);
}
Copy the code

AutoConfigurationImportSelector filter method mainly do is call interface AutoConfigurationImportFilter match method to determine each the conditions of the automatic configuration class comments (if any) @ Conditi OnalOnClass, @ ConditionalOnBean or @ ConditionalOnWebApplication whether meet the conditions, if met, it returns true, specification matching, if not satisfied, return false description does not match.

We now know what did the AutoConfigurationImportSelector filter method is mainly to go, now don’t have to study too deep, As for AutoConfigurationImportFilter interface match method will analyze it in detail in this section 5 AutoConfigurationImportFilter, fill our notes left in the source analysis the condition of the previous hole.

Note: we adhere to the principle of main line first, other branches of the code here do not delve into, so as not to lose the main line ha.

4.2 Importing automatic configuration Classes selectively

Here we continue to explore how the this.group.selectImports method in the previous section [2] selectively imports the automatic configuration class. Look directly at the code:

// AutoConfigurationImportSelector$AutoConfigurationGroup.java

public Iterable<Entry> selectImports(a) {
	if (this.autoConfigurationEntries.isEmpty()) {
		return Collections.emptyList();
	} 
	// Here is a set of all the auto-configuration classes to exclude
	Set<String> allExclusions = this.autoConfigurationEntries.stream()
			.map(AutoConfigurationEntry::getExclusions)
			.flatMap(Collection::stream).collect(Collectors.toSet());
	// Here is a set of all the auto-configuration classes that meet the criteria after filtering
	Set<String> processedConfigurations = this.autoConfigurationEntries.stream() 
			.map(AutoConfigurationEntry::getConfigurations)
			.flatMap(Collection::stream)
			.collect(Collectors.toCollection(LinkedHashSet::new));
	// Remove the auto-configuration class to exclude
	processedConfigurations.removeAll(allExclusions); 
	// Sort the autoconfiguration classes annotated with @order,
	return sortAutoConfigurations(processedConfigurations,
			getAutoConfigurationMetadata())
					.stream()
					.map((importClassName) -> new Entry(
							this.entries.get(importClassName), importClassName))
					.collect(Collectors.toList());
}
Copy the code

As you can see, selectImports method mainly for after eliminated the exclude and be filtered AutoConfigurationImportFilter interface meet the conditions of the automatic configuration class rule out further exclude the automatic configuration of classes, and then sort. The logic is simple and I won’t go into detail.

I’ve already excluded you once, so why exclude you again?

5 AutoConfigurationImportFilter

Continue to delve into the front section 4.1 AutoConfigurationImportSelector here. The filter method of automatic filter configuration class of Boolean [] match = filter. The match (candidates, autoConfigurationMetadata); This code.

So we continue to analyze AutoConfigurationImportFilter interface, analyzes the match method, is also a source analysis of the previous @ ConditionalOnXxx left in the hole to fill.

AutoConfigurationImportFilter interface is only one match method is used to filter is not in conformity with the conditions of the automatic configuration classes.

@FunctionalInterface
public interface AutoConfigurationImportFilter {
    boolean[] match(String[] autoConfigurationClasses,
    		AutoConfigurationMetadata autoConfigurationMetadata);
}
Copy the code

Also, based on the analysis of AutoConfigurationImportFilter interface before the match method, we first look at the class diagram:

As you can see, AutoConfigurationImportFilter interface have a concrete implementation class FilteringSpringBootCondition, FilteringSpringBootCondition have three specific subclass: OnClassCondition OnBeanCondtition and OnWebApplicationCondition.

So what’s the relationship between these classes?

FilteringSpringBootCondition implements the interface AutoConfigurationImportFilter match method, Then in FilteringSpringBootCondition match method call getOutcomes this abstract template matching method returns the automatic configuration class or not. At the same time, The most important thing is the three subclasses OnClassCondition FilteringSpringBootCondition, OnBeanCondtition and OnWebApplicationCondition will copy the template matching method realizes own judgment logic.

Ok, AutoConfigurationImportFilter interface overall relationship has been clear, now we enter the specific implementation class FilteringSpringBootCondition match method to see how is the filter automatically configure class according to conditions.

// FilteringSpringBootCondition.java

@Override
public boolean[] match(String[] autoConfigurationClasses,
		AutoConfigurationMetadata autoConfigurationMetadata) {
	// Create an evaluation report
	ConditionEvaluationReport report = ConditionEvaluationReport
			.find(this.beanFactory);
	// Note that getOutcomes is the template method that passes in all the auto-configuration classes loaded in the Spring.factories file
	/ / subclass (here refers to OnClassCondition, OnBeanCondition and OnWebApplicationCondition class) to filter
	// Note that the outcomes array stores mismatched results, corresponding to the autoConfigurationClasses array
	/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ", focus on "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
	ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses,
			autoConfigurationMetadata);
	boolean[] match = new boolean[outcomes.length];
	// iterate over outcomes, where null equals a match and non-null equals a mismatch
	for (int i = 0; i < outcomes.length; i++) {
		ConditionOutcome outcome = outcomes[i];
		match[i] = (outcome == null || outcome.isMatch());
		if(! match[i] && outcomes[i] ! =null) {
			// If one of the classes does not match, the parent class SpringBootCondition's logOutcome method is called to print the log
			logOutcome(autoConfigurationClasses[i], outcomes[i]);
			// Record the mismatches in the report
			if(report ! =null) {
				report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]); }}}return match;
}
Copy the code

FilteringSpringBootCondition methods do match or getOutcomes call abstract template method according to the condition to filter configuration class automatically, and facsimile getOutcomes template method has three children, here no longer one by one analysis, Only the getOutcomes method of the OnClassCondition copy is selected for analysis.

5.1 OnClassCondition

The code for the getOutcomes method is copied directly on OnClassCondition:

// OnClassCondition.java

protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
		AutoConfigurationMetadata autoConfigurationMetadata) {
	// Split the work and perform half in a background thread. Using a single
	// additional thread seems to offer the best performance. More threads make
	// things worse
	// If the thread is larger than two, the performance will be worse
	int split = autoConfigurationClasses.length / 2;
	// [1] Start a new thread to scan for half of the loaded autoconfiguration classes
	OutcomesResolver firstHalfResolver = createOutcomesResolver(
			autoConfigurationClasses, 0, split, autoConfigurationMetadata);
	// [2] Here we use the main thread to scan for half of the loaded autoconfiguration classes
	OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(
			autoConfigurationClasses, split, autoConfigurationClasses.length,
			autoConfigurationMetadata, getBeanClassLoader());
	// [3] automatically configure whether the class matches the condition
	ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
	// [4] The other half of the auto-configuration class is resolved with the newly opened thread fetching
	// Note that to prevent the main thread from terminating too quickly, resolveOutcomes calls thread.join()
	// Let the main thread wait for the new thread to finish, since the results of the two threads will be merged later
	ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
	// Create a ConditionOutcome array to store the filter results of the auto-configuration class
	ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
	// Copy the filtering results of the first two threads into the outcomes array, respectively
	System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
	System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
	// Returns the filter result of the auto-configuration class
	return outcomes;
}
Copy the code

As you can see, the getOutcomes method of OnClassCondition resolves whether the auto-configured class matches the condition specified by @conditionAlonClass in the auto-configured class and returns a match if it does. If it does not exist, a mismatch is returned.

First configuration resolver and secondHalfResolver are created by first thalfresolver and secondHalfResolver respectively. Each of these two resolvers corresponds to a thread to see if the loaded autoconfiguration class meets the condition. Finally, the matching results of the parsing auto-configuration classes of the two threads are merged and returned.

What about the parsing process to determine whether an auto-configuration class meets the criteria? Now let’s look at the points [1], [2], [3] and [4] indicated in the code comments above.

5.1.1 createOutcomesResolver

# # # # # # # # # # # # # # # # # # # # # # # # # # # ; Method:

// OnClassCondition.java

private OutcomesResolver createOutcomesResolver(String[] autoConfigurationClasses,
		int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
	// Create a StandardOutcomesResolver object
	OutcomesResolver outcomesResolver = new StandardOutcomesResolver(
			autoConfigurationClasses, start, end, autoConfigurationMetadata,
			getBeanClassLoader());
	try {
		// New a ThreadedOutcomesResolver object and pass in an outcomesResolver object of type StandardOutcomesResolver as the constructor argument
		return new ThreadedOutcomesResolver(outcomesResolver);
	}
	// Return the StandardOutcomesResolver object if the thread opened above throws an AccessControlException
	catch (AccessControlException ex) {
		returnoutcomesResolver; }}Copy the code

You can see that the createOutcomesResolver method creates a ThreadedOutcomesResolver resolution object that encapsulates the StandardOutcomesResolver class. So let’s take a look at ThreadedOutcomesResolver what’s the purpose of this thread resolution class that encapsulates StandardOutcomesResolver? We continue to follow the code:

// OnClassCondtion.java

private ThreadedOutcomesResolver(OutcomesResolver outcomesResolver) {
	// start a new thread using the resolveOutcomes method of StandardOutcomesResolver
	// Parse the auto-configuration classes to see if they match
	this.thread = new Thread(
			() -> this.outcomes = outcomesResolver.resolveOutcomes());
	// Start the thread
	this.thread.start();
}
Copy the code

As you can see, when constructing the ThreadedOutcomesResolver object, you start a thread that actually calls the resolveOutcomes method of the StandardOutcomesResolver object that was just passed in to resolve the auto-configuration class. How to parse it? Later we on the analysis of [3] code secondHalfResolver. ResolveOutcomes (); I’ll dig deeper.

5.1.2 new StandardOutcomesResolver

OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(… ; The StandardOutcomesResolver object is created to parse the auto-configuration class and is used by a new thread to parse the auto-configuration class.

5.1.3 StandardOutcomesResolver resolveOutcomes method

Here in front of the corresponding code section 5.1 of [3] ConditionOutcome [] secondHalf = secondHalfResolver. ResolveOutcomes (); .

Here StandardOutcomesResolver. ResolveOutcomes method for parsing automatic matching all logical configuration, that we should focus on the analysis of the method, The resolveOutcomes method ultimately assigns the results of the parsed auto-configuration class to the secondHalf array. So how does it resolve whether an auto-configuration class matches a condition?

// OnClassCondition$StandardOutcomesResolver.java

public ConditionOutcome[] resolveOutcomes() {
	// Call the getOutcomes method to resolve
	return getOutcomes(this.autoConfigurationClasses, this.start, this.end,
			this.autoConfigurationMetadata);
}

private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
		int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) { / / as long as autoConfigurationMetadata no store related configuration class automatically, then the outcome the default is null, then match
	ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
	// Walk through each auto-configuration class
	for (int i = start; i < end; i++) {
		String autoConfigurationClass = autoConfigurationClasses[i];
		/ / TODO for autoConfigurationMetadata have a doubt: Why some kinds of automatic configuration conditions annotations that can be loaded into the autoConfigurationMetadata, and some can't, Such as their definition of an automatic configuration class HelloWorldEnableAutoConfiguration it hadn't been put into the autoConfigurationMetadata
		if(autoConfigurationClass ! =null) {
			ConditionalOnClass = @conditionalonClass // ConditionalOnClass = @conditionalonClass
			/ / for chestnuts, see the following KafkaStreamsAnnotationDrivenConfiguration this automatic configuration class
			/ * * *@ConditionalOnClass(StreamsBuilder. Class) * class KafkaStreamsAnnotationDrivenConfiguration} {* / / omit irrelevant code * * /
			/ / then remove are StreamsBuilder fully qualified name of a class of candidates = org. Apache. Kafka. Streams. StreamsBuilder
			String candidates = autoConfigurationMetadata
					.get(autoConfigurationClass, "ConditionalOnClass"); ConditionalOnClass is ConditionalOnClass. ConditionalOnClass is ConditionalOnClass
			// If the auto-configuration class is ConditionalOnClass and has a value, getOutcome is called to determine whether it is in the classpath
			if(candidates ! =null) {
				ConditionalOnClass () returns null if the class is in the classpath. ConditionalOnClass () returns null if the class is in the classpath
				/******************* [mainline, focus] ******************/outcomes[i - start] = getOutcome(candidates); }}}return outcomes;
}
Copy the code

Can see StandardOutcomesResolver. ResolveOutcomes method call getOutcomes method again, Mainly from autoConfigurationMetadata annotations on the object gets to automatically configure class @ ConditionalOnClass designated as the fully qualified class name, and then passed as a parameter to getOutcome method is used for loading in the class to go to the class path, ConditionalOnClass (@conditionalonClass) ConditionalOnClass (ConditionalOnClass)

But remember, this is just past the @conditionalonClass annotation, and if the auto-configuration class has another annotation like @conditionalonBean, then the @conditionalonbean annotation will not match. This is a little bit of a digression, but let’s go back to the logic of OnClassCondtion, and go on to the getOutcome method and see how it determines whether @conditionalonclass is full or not.

// OnClassCondition$StandardOutcomesResolver.java

// The outcome returned records mismatches. If the outcome is not null, it indicates a mismatch. If the value is null, it indicates a match
private ConditionOutcome getOutcome(String candidates) {
	// Candidates for the form of "org. Springframework. Boot. Autoconfigure. Aop. AopAutoConfiguration. ConditionalOnClass = org. Aspectj. Lang. Annot Ation. The Aspect, org. Aspectj. Lang. Reflect. Advice,. Org. Aspectj weaver. AnnotatedElement"
	try {// If the @conditionalonClass value is only one, the getOutcome method is called to determine the match
		if(! candidates.contains(",")) {
			ClassNameFilter.MISSING: ClassNameFilter.MISSING: ClassNameFilter.MISSING: ClassNameFilter
			/****************** [mainline, focus] ********************/
			return getOutcome(candidates, ClassNameFilter.MISSING, 
					this.beanClassLoader);
		}
		ConditionalOnClass = @conditionalonClass = @conditionalonClass = @conditionalonClass = @conditionalonClass
		for (String candidate : StringUtils
				.commaDelimitedListToStringArray(candidates)) {
			ConditionOutcome outcome = getOutcome(candidate,
					ClassNameFilter.MISSING, this.beanClassLoader);
			// If there is only one mismatch, the result will be returned
			if(outcome ! =null) { 
				returnoutcome; }}}catch (Exception ex) {
		// We'll get another chance later
	}
	return null;
}
Copy the code

As you can see, the getOutcome method calls the overloaded getOutcome method to further determine whether the class specified by @conditionalonClass exists in the classpath.

// OnClassCondition$StandardOutcomesResolver.java

private ConditionOutcome getOutcome(String className, ClassNameFilter classNameFilter, ClassLoader classLoader) {
	// Call the matches method of classNameFilter to check whether the class specified by @conditionalonClass exists in the classpath
	/****************** [mainline, focus] ********************/
	if (classNameFilter.matches(className, classLoader)) { // Call classNameFilter to check whether className exists in the class path. ClassNameFilter can be divided into PRESENT and MISSING. At present, we only see the calls where ClassNameFilter is MISSING, so if the default is true, the mismatch information will be recorded. If you pass ClassNameFilter PRESENT, you probably have to write an else branch
		return ConditionOutcome.noMatch(ConditionMessage
				.forCondition(ConditionalOnClass.class)
				.didNotFind("required class").items(Style.QUOTE, className));
	}
	return null;
}
Copy the code

We peel layer by layer, finally peel to the bottom, this really need enough patience, no way, the source can only bit by bit gnaw, hey hey. The matches method of ClassNameFilter is called to check whether @conditionalonClass exists in the classpath. If it does not, it returns a mismatch.

We continue to follow the source of ClassNameFilter:

// FilteringSpringBootCondition.java

protected enum ClassNameFilter {
	// If the specified class exists on the classpath, return true
	PRESENT {

		@Override
		public boolean matches(String className, ClassLoader classLoader) {
			returnisPresent(className, classLoader); }},// If the specified class does not exist in the classpath, return true
	MISSING {

		@Override
		public boolean matches(String className, ClassLoader classLoader) {
			return! isPresent(className, classLoader);Return true if className does not exist in the classpath}};// This is another abstract method, implemented by the PRESENT and MISSING enumeration classes, respectively
	public abstract boolean matches(String className, ClassLoader classLoader);
	// Check whether the specified class exists in the classpath
	public static boolean isPresent(String className, ClassLoader classLoader) {
		if (classLoader == null) {
			classLoader = ClassUtils.getDefaultClassLoader();
		}
		// Use the class loader to load the corresponding class. If no exception is thrown, the class exists in the classpath. In this case, return true
		try {
			forName(className, classLoader); 
			return true;
		}// If it does not exist in the classpath, the exception will be caught and false will be returned.
		catch (Throwable ex) {
			return false; }}// Use the class loader to load the specified class
	private staticClass<? > forName(String className, ClassLoader classLoader)throws ClassNotFoundException {
		if(classLoader ! =null) {
			return classLoader.loadClass(className);
		}
		returnClass.forName(className); }}Copy the code

Can see ClassNameFilter turned out to be FilteringSpringBootCondition an internal enumeration class, the realization of the specified class exists in the logic of the classpath, this class is very simple, here no longer expatiatory.

5.1.4 ensuring ThreadedOutcomesResolver resolveOutcomes method

Here corresponds to the front of section 5.1 of [4] code ConditionOutcome [] firstHalf. = firstHalfResolver resolveOutcomes ().

Previous analysis 5.1.3 StandardOutcomesResolver. ResolveOutcomes methods have been explored in bottom, deeper into details, Now we need to jump out to continue to see the front of [4] code ConditionOutcome [] firstHalf. = firstHalfResolver resolveOutcomes () method.

Here is to use the new open a thread to call StandardOutcomesResolver. ResolveOutcomes method resolution half automatic configuration class matches, because is a new thread, there is such a situation may arise: After the main thread has parsed its own half of the auto-configuration class, it continues running for so long that it does not wait for a new child thread to open.

Therefore, in order for the main thread to finish parsing, we need to have the main thread continue to wait for the child thread being parsed until the child thread finishes. So we continue to follow up the code area. See ThreadedOutcomesResolver resolveOutcomes method is how to realize the main thread to wait for the child thread:

// OnClassCondition$ThreadedOutcomesResolver.java

public ConditionOutcome[] resolveOutcomes() {
	try {
		// Call the child thread's Join method and let the main thread wait
		this.thread.join();
	}
	catch (InterruptedException ex) {
		Thread.currentThread().interrupt();
	}
	// If the child thread ends, the parsing result of the child thread is returned
	return this.outcomes;
}
Copy the code

As you can see, the thread.join () method is used to make the main Thread wait for the child Thread that is parsing the auto-configured class. CountDownLatch should also be used to make the main Thread wait for the child Thread to finish. Finally, the result of child thread parsing is assigned to the firstHalf array.

5.2 OnBeanCondition and OnWebApplicationCondition

In the previous section 5.1 OnClassCondition, we analyzed in depth how OnClassCondition filters the auto-configuration classes, so the auto-configuration classes need to be filtered by OnClassCondition. Pass OnBeanCondition and OnWebApplicationCondition conditions of the two classes of filtering, here no longer expatiatory, interested friends can analysis.

6 AutoConfigurationImportListener

Here we continue with the previous section 4.1 AutoConfigurationImportSelector. GetAutoConfigurationEntry method automatic configuration class filter after the event to trigger fireAutoConfigurationImportEvents (configuratio ns, exclusions); This code.

We look at how the trigger point directly into the fireAutoConfigurationImportEvents method of events:

// AutoConfigurationImportSelector.java

private void fireAutoConfigurationImportEvents(List
       
         configurations, Set
        
          exclusions)
        
        {
	/ / in the spring. The factories always get to AutoConfigurationImportListener ConditionEvaluationReportAutoConfigurationImportListener namely
	List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners(); 
	if(! listeners.isEmpty()) {/ / create a new AutoConfigurationImportEvent events
		AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this,
				configurations, exclusions);
		/ / traverse AutoConfigurationImportListener just to get to
		for (AutoConfigurationImportListener listener : listeners) {
			// Various Aware methods are called here to assign values before triggering events, such as factory,environment, etc
			invokeAwareMethods(listener);
			/ / real trigger AutoConfigurationImportEvent onXXXEveent callback listener method. This is used to record the evaluation information for the auto-configuration classlistener.onAutoConfigurationImportEvent(event); }}}Copy the code

As above, fireAutoConfigurationImportEvents method to do the following two things:

  1. callgetAutoConfigurationImportListenersMethods fromspring.factorisConfiguration file acquisition implementationAutoConfigurationImportListenerEvent listeners for interfaces; As you can see in the figure below, getConditionEvaluationReportAutoConfigurationImportListener:

  1. Iterate through the acquired event listeners, and then call the listenersAwareMethod, which in turn calls back to the event listener’sonAutoConfigurationImportEventMethod to perform the logic to listen for events.

At this time we’ll look at ConditionEvaluationReportAutoConfigurationImportListener listener listens to the incident, what I’d done it onAutoConfigurationImportEvent method:

// ConditionEvaluationReportAutoConfigurationImportListener.java

public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) {
	if (this.beanFactory ! =null) {
		// Get the condition evaluation reporter object
		ConditionEvaluationReport report = ConditionEvaluationReport
				.get(this.beanFactory);
		// Add the qualified auto-configuration classes to the unconditionalClasses collection
		report.recordEvaluationCandidates(event.getCandidateConfigurations());
		// Record the automatic configuration class of exclude into the Exclusions collectionreport.recordExclusions(event.getExclusions()); }}Copy the code

As you can see, ConditionEvaluationReportAutoConfigurationImportListener listener after listening to the event, do is very simple, just record the qualified and exclude automatic configuration class.

7 AutoConfigurationPackages

AutoConfigurationPackage: SpringBoot AutoConfigurationPackage: SpringBoot AutoConfigurationPackage: SpringBoot AutoConfigurationPackage: SpringBoot AutoConfigurationPackage: SpringBoot AutoConfigurationPackage: SpringBoot AutoConfigurationPackage: SpringBoot AutoConfigurationPackage: SpringBoot AutoConfigurationPackage: SpringBoot AutoConfigurationPackage: SpringBoot AutoConfigurationPackage: SpringBoot AutoConfigurationPackage

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
Copy the code

As you can see, the @autoconfigurationpackage annotation is associated with the package where SpringBoot is automatically configured. The package to which the annotation was added is managed as an AutoConfigurationPackage.

Next we’ll look at AutoConfigurationPackages. The Registrar class do, see the source code directly:

//AutoConfigurationPackages.Registrar.java

static class Registrar implements ImportBeanDefinitionRegistrar.DeterminableImports {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    	register(registry, new PackageImport(metadata).getPackageName());
    }
    
    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
    	return Collections.singleton(newPackageImport(metadata)); }}Copy the code

You can see the Registrar class is a static inner class of AutoConfigurationPackages, implements the ImportBeanDefinitionRegistrar and DeterminableImports two interfaces. Now let’s focus on the Registrar implementation’s registerBeanDefinitions method, which as the name suggests is the way to registerBeanDefinitions. See it again call AutoConfigurationPackages register method, continue to follow up the source code:

// AutoConfigurationPackages.java

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
	if (registry.containsBeanDefinition(BEAN)) {
		BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
		ConstructorArgumentValues constructorArguments = beanDefinition
				.getConstructorArgumentValues();
		constructorArguments.addIndexedArgumentValue(0,
				addBasePackages(constructorArguments, packageNames));
	}
	else {
		GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
		beanDefinition.setBeanClass(BasePackages.class);
		beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(BEAN, beanDefinition); }}Copy the code

As you can see above, the Register method registers a packageNames bean associated with the package name where the auto-configuration class annotation @enableAutoConfiguration resides. So what is the purpose of registering this bean? The purpose of registering the beans associated with the autoconfiguration package name is to be referenced in other places, such as the JPA Entity Scanner.

8 subtotal

Well, SpringBoot automatic configuration of the source code analysis here, relatively long, some places are also very in-depth details, reading requires a certain amount of patience.

Finally, we summarize the principle of SpringBoot automatic configuration, mainly do the following things:

  1. Load the auto-configuration classes from the Spring.Factories configuration file;
  2. The loaded autoconfiguration class is removed@EnableAutoConfigurationannotationsexcludeProperty to specify the auto-configuration class;
  3. And then useAutoConfigurationImportFilterDoes the interface filter auto-configuration class conform to its annotations (if any)@ConditionalOnClass.@ConditionalOnBeanand@ConditionalOnWebApplicationIf all the conditions are met, the matching results will be returned;
  4. Then the triggerAutoConfigurationImportEventEvent, tellConditionEvaluationReportThe condition evaluation reporter object to record the conditions and respectivelyexcludeAutomatic configuration class of.
  5. Finally, Spring imports the filtered auto-configuration classes into the IOC container

Finally leave a question of their own, but also hope to know the answer of the big guy, here to express thanks:

In order to avoid unnecessary automatic loading configuration class cause waste of memory, FilteringSpringBootCondition filters spring. The factories of the automatic configuration file class, And why only OnOnBeanCondition FilteringSpringBootCondition OnClassCondition and onWebApplicationCondition classes are used to filter the three conditions, Why is there no onPropertyCondtion, onResourceCondition conditional class to filter the auto-configuration class?

Next: What is the startup process of SpringBoot? –SpringBoot source code (5)

Original is not easy, help point a praise!

Due to the author’s limited level, if there are mistakes in the article, please point out, thank you.

Reference:

1, @autoConfigurationPackage annotation