Spring Boot startup annotation analysis

@[toc] Although we use Spring Boot a lot in our daily development, it is considered a standard configuration in the field of Java development, but friends, think about your interview experience carefully, what are the interview questions related to Spring Boot? Personally, I feel that there should be relatively few. Spring Boot is essentially the same as SSM, but the configuration is simplified through various starters. The others are exactly the same, so many interview questions in Spring Boot still have to return to Spring to answer ! Of course, this is not to say that there is nothing to ask in Spring Boot. There is actually a very classic interview question in Spring Boot, that is, how is automatic configuration in Spring Boot implemented? Brother Song is here today to chat with you guys about this issue.

In fact, Brother Song talked about related issues with his friends before, but they were all scattered and not systematically sorted out. He also led his friends to customize a starter before. I believe that all of you have a certain understanding of the principle of the starter, so I won’t go into too much detail in today’s article, you can read the previous article.

1. @SpringBootApplication

To talk about the automatic configuration of Spring Boot, we must @SpringBootApplicationstart . This is the starting point of the entire Spring Boot universe. Let's look at this annotation first:

@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 {

}

As you can see, @SpringBootApplicationannotations combine the functions of multiple common annotations, among which:

  • The first four are meta-annotations, which we won't discuss here.
  • The fifth @SpringBootConfigurationis an annotation that supports configuration classes, which we will not discuss here.
  • @EnableAutoConfigurationThe sixth annotation indicates that automatic configuration is turned on, which is the focus of our talk today.
  • The seventh @ComponentScanis a package scanning comment, why will be automatically scanned as long asSpring Boot it is placed in the right position in the project , it is related to this comment.Bean

Don't look at the many annotations here. In fact, there are only two annotations Spring Bootprovided , namely and@SpringBootConfiguration . The other annotations have existed for many years before the appearance of .@EnableAutoConfigurationSpring Boot

2. @EnableAutoConfiguration

Next, let's take a look @EnableAutoConfigurationat how to implement automatic configuration.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

}

This annotation plays a key role in two things:

  1. @AutoConfigurationPackage: This means to automatically scan various third-party annotations. In the previous article, Brother Song has already talked with you about the function of this annotation. Portal: What is the difference between @AutoConfigurationPackage and @ComponentScan?
  2. @ImportIt is importing AutoConfigurationImportSelectorthe configuration class, which is used to load various automatic configuration classes.

3. AutoConfigurationImportSelector

AutoConfigurationImportSelectorThere are many methods in the class, and the entry point is the process method, so we will start from the process method here:

@Override
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()));
	AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
		.getAutoConfigurationEntry(annotationMetadata);
	this.autoConfigurationEntries.add(autoConfigurationEntry);
	for (String importClassName : autoConfigurationEntry.getConfigurations()) {
		this.entries.putIfAbsent(importClassName, annotationMetadata);
	}
}

As can be seen from the class name, objects related to automatic configuration are AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);loaded .

Of course, getAutoConfigurationEntrythe method is actually the method provided by the current class, let's take a look at the method:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return EMPTY_ENTRY;
	}
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	List<string> configurations = getCandidateConfigurations(annotationMetadata, attributes);
	configurations = removeDuplicates(configurations);
	Set<string> exclusions = getExclusions(annotationMetadata, attributes);
	checkExcludedClasses(configurations, exclusions);
	configurations.removeAll(exclusions);
	configurations = getConfigurationClassFilter().filter(configurations);
	fireAutoConfigurationImportEvents(configurations, exclusions);
	return new AutoConfigurationEntry(configurations, exclusions);
}

The method naming of the source code here is well done, basically you can see the name and know the meaning, and you should follow this naming idea in your daily development. Next, let's take a look at the key methods here one by one.

3.1 isEnabled

First call the isEnabled method to determine whether the automatic configuration is enabled. This is mainly because after we introduced spring-boot-starter-xxx in the project in time, we can also disable all automatic configurations spring.boot.enableautoconfiguration=falseby

The relevant source code is as follows:

protected boolean isEnabled(AnnotationMetadata metadata) {
	if (getClass() == AutoConfigurationImportSelector.class) {
		return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
	}
	return true;
}

3.2 getCandidateConfigurations

Next, call getCandidateConfigurationsthe method to obtain all candidate automation configuration classes. These candidate automation configuration classes mainly come from two places:

  1. starterSong Ge talked with you in the previous custom , we need to claspath\:META-INF/spring.factoriesdefine all the automatic configuration classes in , this is the first source.
  2. Spring BootThe built-in automatic configuration class, this has been talked about many times with my friends in the previous vhr video, and the Spring Bootbuilt-in automatic configuration class is located in spring-boot-autoconfigure-3.0.6.jar!\META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.importsthe file .

The relevant source code is as follows:

protected List<string> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	List<string> configurations = new ArrayList&lt;&gt;(
			SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
	ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
	Assert.notEmpty(configurations,
			"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
					+ "are using a custom packaging, make sure that file is correct.");
	return configurations;
}

The full path of the automatic configuration class loaded here is stored in configurationsthe object , which has two places to obtain:

  1. Call SpringFactoriesLoader.loadFactoryNamesthe method to get it. I will not show you the details of this method. It is relatively simple. In essence, it is to load META-INF/spring.factoriesthe file. This file defines the full path of a large number of automatic configuration classes.
  2. Call ImportCandidates.loadthe method to load, this is to load spring-boot-autoconfigure-3.0.6.jar!\META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.importsthe automation configuration class in the file.

If neither place is loaded into any automation configuration class, then an exception will be thrown.

3.3 removeDuplicates

removeDuplicatesThe method means to remove the duplicated classes in the candidate automation configuration class. The idea of ​​removal is also very interesting, just use a LinkedHashSettransfer . The source code is as follows:

protected final <t> List<t> removeDuplicates(List<t> list) {
	return new ArrayList&lt;&gt;(new LinkedHashSet&lt;&gt;(list));
}

It can be seen that sometimes some solutions in these source codes are also very interesting.

3.4 getExclusions

getExclusionsThe method indicates that all excluded automation configuration classes need to be obtained. These excluded automation configuration classes can be obtained from three places:

  1. excludeThe property of the current annotation .
  2. excludeNameThe property of the current annotation .
  3. application.propertiesspring.autoconfigure.excludeproperty in the configuration file .

Take a look at the relevant source code:

protected Set<string> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	Set<string> excluded = new LinkedHashSet&lt;&gt;();
	excluded.addAll(asList(attributes, "exclude"));
	excluded.addAll(asList(attributes, "excludeName"));
	excluded.addAll(getExcludeAutoConfigurationsProperty());
	return excluded;
}

It corresponds to the three points explained above.

3.5 checkExcludedClasses

This method is to check all the excluded automation configuration classes. Since the automation configuration classes Spring Bootin can be customized, it is not necessary to uniformly implement a certain interface or uniformly inherit a certain class, so when writing an exclusion class, if the compilation is wrong It cannot be verified, like the following:

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

Since HelloControlleris not an automatic configuration class, an error will be reported when the project is started, as follows:

Where does this exception come from? In fact, it comes from checkExcludedClassesthe method , let's take a look at the method:

private void checkExcludedClasses(List<string> configurations, Set<string> exclusions) {
	List<string> invalidExcludes = new ArrayList&lt;&gt;(exclusions.size());
	for (String exclusion : exclusions) {
		if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) &amp;&amp; !configurations.contains(exclusion)) {
			invalidExcludes.add(exclusion);
		}
	}
	if (!invalidExcludes.isEmpty()) {
		handleInvalidExcludes(invalidExcludes);
	}
}
protected void handleInvalidExcludes(List<string> invalidExcludes) {
	StringBuilder message = new StringBuilder();
	for (String exclude : invalidExcludes) {
		message.append("\t- ").append(exclude).append(String.format("%n"));
	}
	throw new IllegalStateException(String.format(
			"The following classes could not be excluded because they are not auto-configuration classes:%n%s",
			message));
}

It can be seen that in checkExcludedClassesthe method , configurationsall the excluded automation configuration classes that are located in the current classpath but not included in will be found first. Since configurationsthere are all automation configuration classes in , these do not exist configurationsin Classes are all problematic, and they are not automatic configuration classes. Collect these problematic classes, store them in invalidExcludesvariables , and then perform additional processing.

The so-called extra processing is to throw an exception in handleInvalidExcludesthe method , which is where the exception in the previous screenshot comes from.

3.6 removeAll

This method is just one task, which is to remove those excluded automation configuration classes from configurationsit . configurationsIt is Lista collection , exclusionsand it is a Setcollection , so it can be removed directly here.

3.7 filter

Now we have loaded all the automatic configuration classes, but not all of these configuration classes will take effect. Whether they will take effect or not depends on whether your project uses specific dependencies.

For example, the automatic configuration loaded now contains RedisAutoConfiguration, which automatically configures Redis, but since Redis is not used in my project, this automatic configuration class will not take effect. This process is done getConfigurationClassFilter().filter(configurations);by .

Let me talk about a preliminary knowledge first:

Since there are so many automated configuration classes in our project, each automated configuration class will depend on other classes. When other classes exist, this automated configuration class will take effect. This bunch of dependencies between each other exists in spring-boot-autoconfigure-3.0.6.jar!/META-INF/spring-autoconfigure-metadata.propertiesthe file Among them, I randomly cite a configuration in this file:

  • org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.annotation.EnableRabbitIndicates that a prerequisite for the RabbitAnnotationDrivenConfiguration class to take effect is that it exists in the current project classpath org.springframework.amqp.rabbit.annotation.EnableRabbit.

Let's take a look at the annotations of the RabbitAnnotationDrivenConfiguration class:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(EnableRabbit.class)
class RabbitAnnotationDrivenConfiguration {
}

This class is consistent with the content in the configuration file.

If you understand this preliminary knowledge, the next content will be easy to understand.

First look at the getConfigurationClassFilter method, which is to get all the filters, as follows:

private ConfigurationClassFilter getConfigurationClassFilter() {
	if (this.configurationClassFilter == null) {
		List<autoconfigurationimportfilter> filters = getAutoConfigurationImportFilters();
		for (AutoConfigurationImportFilter filter : filters) {
			invokeAwareMethods(filter);
		}
		this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
	}
	return this.configurationClassFilter;
}

It can be seen that the filters obtained here are all of the AutoConfigurationImportFilter type, and there are only three instances of this type of filter, as shown in the following figure:

From the names of these three instances, you can basically see their respective functions:

  • OnClassCondition: This is the judgment condition @ConditionalOnClassof . It is used to judge whether a certain class exists under the current classpath by looking at the name.
  • OnWebApplicationCondition: This is the judgment condition ConditionalOnWebApplicationof , which is used to judge whether the current system environment is a Web environment.
  • OnBeanCondition: This is the judgment condition @ConditionalOnBeanof , which is to judge whether a Bean exists in the current system.

The three AutoConfigurationImportFilter filters obtained here are actually the above three. Next, execute the filter method, as follows:

List<string> filter(List<string> configurations) {
	long startTime = System.nanoTime();
	String[] candidates = StringUtils.toStringArray(configurations);
	boolean skipped = false;
	for (AutoConfigurationImportFilter filter : this.filters) {
		boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
		for (int i = 0; i &lt; match.length; i++) {
			if (!match[i]) {
				candidates[i] = null;
				skipped = true;
			}
		}
	}
	if (!skipped) {
		return configurations;
	}
	List<string> result = new ArrayList&lt;&gt;(candidates.length);
	for (String candidate : candidates) {
		if (candidate != null) {
			result.add(candidate);
		}
	}
	return result;
}

Here is to traverse these three filters, and then call their respective match methods to match with 144 automatic configuration classes. If the conditions required by these automatic configuration classes are met, the corresponding position of the match array will be true, otherwise it will be false.

Then traverse the match array, set the automatic configuration classes that do not meet the conditions to null, and finally remove these nulls.

In this way, the classes we need to automate configuration are obtained.

The last sentence fireAutoConfigurationImportEvents triggers the automatic configuration class import event, which is nothing to say~

After these automatic configuration classes are loaded, the next step is various conditional annotations to determine whether these configuration classes are effective. These are relatively simple. I have talked to my friends many times in vhr before, so I won’t stop here Long winded~

Well, after the above combing, I believe that the friends have a general understanding of the loading of Spring Boot automatic configuration classes~</string></string></string></autoconfigurationimportfilter></string></ string></string></string></string></string></t></t></t></string></string></string></string>

{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/lenve/blog/9815570