Note: This series of source code analysis is based on SpringBoot 2.2.2.RELEASE, the corresponding Spring version is 5.2.2.RELEASE, the source code of the Gitee repository repository address: funcy/ Spring-boot.
Autowaging is one of the cores of SpringBoot, and this article will explore how SpringBoot loads autowaging classes.
In the @SpringBootApplication annotation article, we mentioned that springBoot handles auto-assembly with @enableAutoConfiguration as follows:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// Automatic package assembly
@AutoConfigurationPackage
// The introduced autowiring class
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/** * you can define classes */ that exclude autowiringClass<? >[] exclude()default {};
/** * You can define the name of the class that excludes autowiring */
String[] excludeName() default {};
}
Copy the code
The above code consists of three parts:
@AutoConfigurationPackage
: specifies the package for automatic assembly;@Import(AutoConfigurationImportSelector.class)
: Introduces automatic assembly processing classesAutoConfigurationImportSelector
This class is the key to autowiring;@EnableAutoConfiguration
Properties:@EnableAutoConfiguration
Two properties are provided:exclude
withexcludeName
, can be used to exclude classes that do not require autowiring.
This paper to analyze AutoConfigurationImportSelector class.
1. AutoConfigurationImportSelector.AutoConfigurationGroup
The analysis of the AutoConfigurationImportSelector implements DeferredImportSelector about DeferredImportSelector, You can refer to the processing of ConfigurationClassPostProcessor @ Import annotations, here we directly give the conclusion:
-
DeferredImportSelector is a subinterface of ImportSelector with an internal interface Group that defines two methods:
public interface DeferredImportSelector extends ImportSelector {...interface Group { /** * handle the import operation */ void process(AnnotationMetadata metadata, DeferredImportSelector selector); /** * returns the imported class */ Iterable<Entry> selectImports(a)}}Copy the code
In dealing with the import DeferredImportSelector class, DeferredImportSelector Group# process method will first call, Then call DeferredImportSelector. Group# selectImports return to import classes;
-
DeferredImportSelector can specify a group of imported classes. When processing, the imported classes can be processed by group.
-
DeferredImportSelector handles imported classes by grouping them into a map, After processing the other Configuration classes (Spring’s Configuration classes are @Component, @ComponentScan, @import, @Configuration, @Bean tagged classes), we can process the imported classes in the group. That is, DeferredImportSelector imports classes that will be registered with the beanFactory after other classes are registered with it.
Let’s take a look at AutoConfigurationImportSelector code:
// Implements DeferredImportSelector
public class AutoConfigurationImportSelector implements DeferredImportSelector.BeanClassLoaderAware.ResourceLoaderAware.BeanFactoryAware.EnvironmentAware.Ordered {.../ * * * here implements DeferredImportSelector. Group * /
private static class AutoConfigurationGroup implements DeferredImportSelector.Group.BeanClassLoaderAware.BeanFactoryAware.ResourceLoaderAware {
/** * Save the imported class */
private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();
/** * handles the import class */
@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()));
/ / 1. Call AutoConfigurationImportSelector# getAutoConfigurationEntry (...). Method,
// The autoloader class is loaded in this method
AutoConfigurationEntry autoConfigurationEntry =
((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
// 2. Save the obtained autoConfigurationEntry
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata); }}/** * returns the imported class */
@Override
public Iterable<Entry> selectImports(a) {
if (this.autoConfigurationEntries.isEmpty()) {
return Collections.emptyList();
}
// 3. Get the filter class
Set<String> allExclusions = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream)
.collect(Collectors.toSet());
// 4. Convert autoConfigurationEntries to LinkedHashSet
Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
.collect(Collectors.toCollection(LinkedHashSet::new));
// 5. Remove classes that need to be filtered
processedConfigurations.removeAll(allExclusions);
// 6
return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata())
.stream().map((importClassName) -> new Entry(
this.entries.get(importClassName), importClassName)) .collect(Collectors.toList()); }... }}Copy the code
. Here we will DeferredImportSelector Group# process with DeferredImportSelector. Group# selectImports combined two methods, the steps summarized below:
- call
AutoConfigurationImportSelector#getAutoConfigurationEntry(...)
Method to load the automatic assembly class. - Save the resulting autowiring class to
autoConfigurationEntries
; - You get filter classes, and those filter classes are defined by
@EnableAutoConfiguration
theexclude
orexcludeName
The specified; - will
autoConfigurationEntries
convertLinkedHashSet
, the results forprocessedConfigurations
; - Get rid of
processedConfigurations
Classes to filter; - After sorting the classes obtained in step 5, return.
Let’s take a look at these key steps.
SelectImports () {selectImports(); selectImports(); selectImports(); selectImports(); In DeferredImportSelector, we also override this method:
All this method does is load the autorungclass and return the final imported class, but note that SpringBoot’s autorungclass is not handled here. At this point, you can make a breakpoint inside the method and see that the method does not run!
One final statement: Springboot automatically import the classes not in AutoConfigurationImportSelector# selectImports methods in the treatment of, But in AutoConfigurationImportSelector. AutoConfigurationGroup# selectImports processing method.
2. Obtain assembly class:AutoConfigurationImportSelector#getAutoConfigurationEntry
The loading code for the auto-configuration class is:
AutoConfigurationEntry autoConfigurationEntry =
((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
Copy the code
This code is used to load automatic assembling of the class, we directly into AutoConfigurationImportSelector# getAutoConfigurationEntry method:
protected AutoConfigurationEntry getAutoConfigurationEntry( AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
// Check whether auto assembly is enabled again
if(! isEnabled(annotationMetadata)) {return EMPTY_ENTRY;
}
// Get the attributes of the annotation
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 1. Load the candidate auto-configuration class
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// select * from set to list
configurations = removeDuplicates(configurations);
// 3. Exclude and excludeName of @enableAutoConfiguration
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 4. Filter classes that do not require autowiring
configurations = filter(configurations, autoConfigurationMetadata);
/ / 5. Trigger AutoConfigurationImportEvent events
fireAutoConfigurationImportEvents(configurations, exclusions);
// 6. The final return value
return new AutoConfigurationEntry(configurations, exclusions);
}
Copy the code
This method is very important and contains all the operations to get the autowire class as follows:
-
Load the candidate autowler classes. Springboot autowler classes are in meta-INF/Spring.Factories on your classpath. The key for org. Springframework. Boot. Autoconfigure. EnableAutoConfiguration, this behind us again detailed analysis;
-
In this case, the duplicate classes will be removed. The way to remove the duplicate classes is also very simple. Springboot just converts to Set first, and then to List.
-
Exclusion of excluded classes. As mentioned earlier, @enableAutoConfiguration can exclude and excludeName classes that need to be excluded. This step handles these two properties.
-
Filter classes that do not need automatic assembly, according to my debugging, found that the filtering is not completed:
Before filtering, it was 124:
After filtering, there are still 124:
-
Trigger AutoConfigurationImportEvent events;
-
Wrap the exclusion class from Step 3 and the autowore class from Step 4 and return it as an AutoConfigurationEntry.
Notice the last line of code:
// 6. The final return value
return new AutoConfigurationEntry(configurations, exclusions);
Copy the code
Configurations and Exclusions are passed into the AutoConfigurationEntry constructor. Let’s look at AutoConfigurationEntry:
protected static class AutoConfigurationEntry {
// Automatic assembly class
private final List<String> configurations;
// The autowiring classes that need to be excluded
private final Set<String> exclusions;
/** * constructor, which assigns the value */
AutoConfigurationEntry(Collection<String> configurations, Collection<String> exclusions) {
this.configurations = new ArrayList<>(configurations);
this.exclusions = newHashSet<>(exclusions); }... }Copy the code
As you can see, the final AutoConfigurationEntry returned contains two things:
configurations
: Autowiring classes have been removed to exclude classesexclusions
Through:@EnableAutoConfiguration
Specifies the classes to exclude
Now that the entire autowiring class is acquired, let’s look at the process of loading the candidate autowiring class.
3. Load the candidate autowiring classes
Automatic assembly of class loading in AutoConfigurationImportSelector# getCandidateConfigurations, the code is as follows:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
/ / call the method that is spring provides: SpringFactoriesLoader. LoadFactoryNames (...).
/ / getSpringFactoriesLoaderFactoryClass EnableAutoConfiguration () returns
List<String> configurations = SpringFactoriesLoader
.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations, "...");
return configurations;
}
protectedClass<? > getSpringFactoriesLoaderFactoryClass() {return EnableAutoConfiguration.class;
}
Copy the code
Proceed to SpringFactoriesLoader#loadFactoryNames:
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<? > factoryType,@Nullable ClassLoader classLoader) {
/ / get factoryTypeName is org. Springframework. Boot. Autoconfigure. EnableAutoConfiguration
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
/** * The META-INF/spring.factories property */ is loaded here
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if(result ! =null) {
return result;
}
try {
// Load the content of meta-INF /spring.factoriesEnumeration<URL> urls = (classLoader ! =null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// Convert the content of meta-INF/spring.Factories to a Properties object
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for(Map.Entry<? ,? > entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim();// StringUtils.commaDelimitedListToStringArray(...) Commas split into arrays
for (String factoryImplementationName :
StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex); }}... }Copy the code
As you can see, the meta-INF/Spring. factories file is loaded from your classpath. Note that there can be multiple of these files in different JAR packages.
The meta-INF /spring.factories (” spring-boot-autoconfigure “) are in the spring-boot-autoconfigure module.
Let’s take a look at spring.factories:
This file defines a number of configuration classes, stored as key-values, separated by commas (,), Mentioned above automatic assembly class key is org. Springframework. Boot. Autoconfigure. EnableAutoConfiguration, the corresponding value very much, does not show here.
After this step, the autowiring classes are registered with the Spring container. Note: when loading into the spring container or BeanDefinition, to be spring bean, also have to pass the test of ConditionalOnBean, ConditionalOnClass annotations, these reanalysis behind us.
4. Processing after obtaining the automatic assembly class
Let’s go back to AutoConfigurationImportSelector AutoConfigurationGroup, in section 1 we summarize process is as follows:
- call
AutoConfigurationImportSelector#getAutoConfigurationEntry(...)
Method to load the automatic assembly class. - Save the resulting autowiring class to
autoConfigurationEntries
; - You get filter classes, and those filter classes are defined by
@EnableAutoConfiguration
theexclude
orexcludeName
The specified; - will
autoConfigurationEntries
convertLinkedHashSet
, the results forprocessedConfigurations
; - Get rid of
processedConfigurations
Classes to filter; - After sorting the classes obtained in step 5, return.
Sections 2 and 3 above examined the loading process of auto-loading classes, so let’s take a look at the next steps.
The next steps are easy to follow in the code, so let’s go through them one by one.
-
Step 2, save the resulting autowiring class, which simply calls List#add(…) Method to save the resulting autoConfigurationEntry to autoConfigurationEntries, which are members of the AutoConfigurationGroup. In AutoConfigurationImportSelector. AutoConfigurationGroup# selectImports method will be used;
-
Step 3: Obtain all filter classes by iterating through autoConfigurationEntries and using the autoConfigurationEntry#getExclusions method. The autoConfigurationEntry contains only two member variables: Configurations (the auto-assembly class after excluding the excluded classes) and Exclusions (the excluded classes specified via @enableAutoConfiguration).
-
Step 4, convert the List to LinkedHashSet, without analyzing;
-
Step 5: Remove the excluded classes from all the autowiring classes again. The excluded objects are all the excluded classes. In this case, there should be multiple @enableAutoConfiguration in the same project. For example, the first @enableAutoConfiguration annotation excludes A and B classes, and the second @enableAutoConfiguration annotation excludes C and D classes, which ultimately excludes A, B, C, and D classes.
-
Step 6, the main operation of this step is sorting. This order determines the order in which the autologister classes are registered with the beanFactory. AutoConfigureOrder, @AutoConfigureAfter, and @AutoConfigureBefore are handled here. For this, see the SpringBoot autowiring sequence.
After these steps, the acquisition of autowiring is complete.
5. Customize autowiring classes
Now that we know how to load the autowiring class, we can also customize an autowiring class.
- Prepare an autowiring class
@Configuration
public class MyAutoConfiguration {
@Bean
public Object object(a) {
System.out.println("create object");
return newObject(); }}Copy the code
This class is simply a class marked @Configuration that uses the @Bean annotation to create a Bean, printing “Create Object” during Bean creation.
- To prepare
META-INF/spring.factories
As follows:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.learn.autoconfigure.demo01.configure.MyAutoConfiguration
Copy the code
- The main class
@SpringBootApplication
public class AutoconfigureDemo01Application {
public static void main(String[] args) { SpringApplication.run(AutoconfigureDemo01Application.class, args); }}Copy the code
The running results are as follows:
As you can see, the Create Object prints successfully.
Is this bean created through a package scan or is it auto-assembled and imported? Let’s take a look at the autowiring classes through debugging:
As you can see, MyAutoConfiguration is in the list of autowiring classes.
Note that MyAutoConfiguration is annotated with @Configuration. Is it scanned by the SPing container or by autowiring?
In the @SpringBootApplication annotation, we mentioned that @ComponentScan in the SpringBootApplication annotation specifies a filter: AutoConfigurationExcludeFilter, this filter will filter automatic assembly, here we look at what is the beanFactory beanName so far:
As you can see, there is no MyAutoConfiguration, so it has not been scanned into the beanFactory at this point.
Of course, we can also remove the @Configuration annotation from MyAutoConfiguration to avoid this confusion.
6. Summary
This article embarks from the @ EnableAutoConfiguration annotation, this paper analyzes the automatic assembling of the class loading process, loading process in AutoConfigurationImportSelector# getAutoConfigurationEntry method, Ultimate load is the meta-inf/spring. The key factories file is org springframework. Boot. Autoconfigure. EnableAutoConfiguration class.
Get automatic assembly after class, spring will be registered with the container, they still a BeanDefinition, at this time to be spring bean, also have to pass the test of ConditionalOnBean, ConditionalOnClass annotations, We’ll talk about that later.
Link to the original article:My.oschina.net/funcy/blog/…, limited to the author’s personal level, there are inevitable mistakes in the article, welcome to correct! Original is not easy, commercial reprint please contact the author to obtain authorization, non-commercial reprint please indicate the source.
【 Springboot source code analysis 】 Springboot source code analysis series of articles