Spring Boot starts annotation analysis

Although we use Spring Boot a lot in daily development, and it is considered a standard in the current Java development field, but friends, think carefully about your interview experience, what are the interview questions related to Spring Boot? Personally, there should be relatively few. Spring Boot is essentially the same as SSM. It just simplifies the configuration through various starters. Everything else is exactly the same, so many interview questions in Spring Boot still need to be answered in Spring. ! Of course, this does not mean that there is nothing to ask in Spring Boot. There is actually a very classic interview question in Spring Boot, that is, how is the automated configuration in Spring Boot implemented? Today Brother Song is here to talk to all of you about this issue.

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

1. @SpringBootApplication

To talk about the automated configuration of Spring Boot, we must start with the startup class of the project @SpringBootApplication. This is the starting point of the entire Spring Boot universe. Let’s first look at 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 {

}

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

  • The first four are meta-annotations, which we will not discuss here.
  • The fifth @SpringBootConfigurationis an annotation that supports configuration classes, which we will not discuss here.
  • The sixth @EnableAutoConfigurationannotation means turning on automated configuration, which is the focus of our discussion today.
  • The seventh one @ComponentScanis a package scanning annotation. Why everything Spring Bootin the project Beanwill be automatically scanned as long as it is placed in the right position is related to this annotation.

Don’t look at the many annotations here. In fact, Spring Bootthere are only two annotations provided by , namely @SpringBootConfigurationand @EnableAutoConfiguration. The other annotations Spring Boothave existed for many years before the appearance of .

2. @EnableAutoConfiguration

Next, let’s take a look @EnableAutoConfigurationat how to implement automated 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 automatically scanning various third-party annotations. In the previous article, Brother Song has already talked to everyone about the role 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 automated configuration classes.

3. AutoConfigurationImportSelector

AutoConfigurationImportSelectorThere are many methods in the class, and the entry point is the process method, so we will start with 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 automated configuration are AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);loaded by.

Of course, the method here getAutoConfigurationEntryis actually the method provided by the current class. Let’s take a look at this 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 in the source code here is done well, and basically everyone can understand the meaning after seeing the name. Friends should follow this naming idea in their 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 automated configuration is enabled. This is mainly because after we introduced spring-boot-starter-xxx in the project in time, we can also spring.boot.enableautoconfiguration=falseturn off all automated configuration by configuring in application.properties.

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 automated configuration classes. These candidate automated configuration classes mainly come from two places:

  1. In the previous customization starterZhongsong brother talked to everyone, we need to claspath\:META-INF/spring.factoriesdefine all the automated configuration classes in , this is the first source.
  2. Spring BootThe built-in automated configuration class has been talked about many times with friends in previous vhr videos. Spring BootThe built-in automated configuration class is located spring-boot-autoconfigure-3.0.6.jar!\META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.importsin the file.

The relevant source code is as follows:

protected List<String> getCandidateConfigurations (AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = new  ArrayList <>(
            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 automated 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. It is essentially to load META-INF/spring.factoriesthe file. This file defines the full paths of a large number of automated configuration classes.
  2. Call ImportCandidates.loadthe method to load, which is to load spring-boot-autoconfigure-3.0.6.jar!\META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.importsthe automated configuration class in the file.

If no automated configuration classes are loaded in either place, an exception will be thrown.

3.3 removeDuplicates

removeDuplicatesThe method means to remove duplicate classes in the candidate automation configuration classes. 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 <>( new  LinkedHashSet <>(list));
}

You can see that sometimes some solution ideas in these source codes are also very interesting.

3.4 getExclusions

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

  1. Properties of the current annotation exclude.
  2. Properties of the current annotation excludeName.
  3. application.propertiesspring.autoconfigure.excludeproperties in the configuration file .

Let’s take a look at the relevant source code:

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

It corresponds exactly to the three points explained above.

3.5 checkExcludedClasses

This method is to check all excluded automated configuration classes. Since Spring Bootthe automated configuration classes in can be customized, there is no need to uniformly implement a certain interface or uniformly inherit a certain class. Therefore, when writing excluded classes, if you write the wrong compile 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 automated configuration class, an error will be reported when the project is started, as follows:

Where does this anomaly 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 <>(exclusions.size());
     for (String exclusion : exclusions) {
         if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !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 the method, all excluded automated configuration classes that checkExcludedClassesare located on the current classpath but are not included in will be found first. Since those in are all automated configuration classes, these do not exist in All classes are problematic, and none of them are automatically configured. Collect these problematic classes, store them in variables, and then perform additional processing.configurationsconfigurationsconfigurationsinvalidExcludes

The so-called additional processing is handleInvalidExcludesto throw an exception in the method. The exception in the previous screenshot comes from here.

3.6 removeAll

This method has one task, which is to remove those excluded automated configuration classes configurationsfrom . It is a collection configurationsitself , so it can be removed directly here.ListexclusionsSet

3.7 filter

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

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

Let’s talk about some preliminary knowledge first:

Since there are so many automated configuration classes in our project, each automated configuration class will depend on other classes. This automated configuration class will only take effect when other classes exist. This bunch of mutual dependencies exist in spring-boot-autoconfigure-3.0.6.jar!/META-INF/spring-autoconfigure-metadata.propertiesfiles Among them, I just give 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 must exist in the current project class path 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.

Once you understand this preparatory knowledge, the following content will be easier to understand.

Let’s first look at the getConfigurationClassFilter method. This is to get all 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;
}

As you can see, the filters obtained here are all of the AutoConfigurationImportFilter type. There are only three instances of this type of filter, as shown below:

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

  • OnClassCondition: This is @ConditionalOnClassthe judgment condition of the conditional annotation. You can tell from the name that it is used to judge whether a certain class exists under the current classpath.
  • OnWebApplicationCondition: This is ConditionalOnWebApplicationthe judgment condition of the conditional annotation, which is used to judge whether the current system environment is a Web environment.
  • OnBeanCondition: This is @ConditionalOnBeanthe judgment condition of the conditional annotation, which is to judge whether a certain Bean exists in the current system.

The three AutoConfigurationImportFilter filters obtained here are actually the three above. 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 < match.length; i++) {
             if (! match[i]) {
                candidates[i] = null ;
                skipped = true ;
            }
        }
    }
    if (!skipped) {
         return configurations;
    }
    List<String> result = new  ArrayList <>(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 and 144 automated configuration classes to match. If the conditions required by these automated configuration classes are met, the corresponding position in the match array will be true, otherwise it will be false.

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

In this way, we get the classes we need to configure automatically.

The last sentence fireAutoConfigurationImportEvents triggers the automatic configuration class import event. There is nothing to say about this ~

After these automated configuration classes are loaded in, the next step is various conditional annotations to determine whether these configuration classes are effective. These are relatively simple. I have talked about it many times with my friends in vhr before, so I won’t do it here. So long-winded~

Okay, after the above review, I believe you all have a general understanding of the loading of Spring Boot automated configuration classes~