Mysterious and powerful @ SpringBootApplication annotation

Posted by juschillinnow on Mon, 07 Mar 2022 23:06:45 +0100

1, Foreword

Most configurations can be replaced by Java class + annotation, and the @ SpringBootApplication annotation is the most common annotation in SpringBoot project, which is marked on each SpringBoot startup class.

How does this annotation affect the startup and automatic configuration of SpringBoot? This article will analyze its source code for you and uncover the mystery of @ SpringBootApplication annotation.

2, Text

I was very interested in the automatic configuration of SpringBoot project, so I learned its source code and sorted out some of its contents. If there is any error, please correct it ~ if you don't say much, go directly to the source code;

@The source code of SpringBootApplication annotation is as follows:

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

It can be seen that this is a composite annotation, including 7 different annotations. The following is an analysis of these 7 different annotations.

2.1 notes

2.1.1 annotation 1: @ Target({ElementType.TYPE})

Used to indicate the scope of annotation. TYPE indicates that the scope is a class or interface.

2.1.2 note 2: @ Retention(RetentionPolicy.RUNTIME)

2.1.3 annotation 3: @ Documented

Indicates that this comment is recorded by javadoc.

2.1.4 note 4: @ Inherited

Put it on the annotation. When the parent class adds the @ SpringBootApplication annotation, the child class will also inherit this annotation (it is invalid for the implementation class of the interface).

2.1.5 annotation 5: @ SpringBootConfiguration

The bottom layer is still @ Configuration annotation, and the source code is as follows:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

2.1.6 Note 6: @ ComponetScan

@The annotation of ComponentScan is very important in Spring. It corresponds to the element @ ComponentScan in XML configuration. In fact, the function of ComponentScan is to automatically scan and load qualified components (such as @ Component and @ Repository) or bean definitions, and finally load these bean definitions into IoC container.

You can fine-grained customize the scope of automatic scanning of @ ComponentScan through attributes such as basePackages. If not specified, the default Spring framework implementation will scan from the package of the class where @ ComponentScan is declared. Therefore, the startup class of SpringBoot is best placed under the root package, because basePackages is not specified by default.

2.2 notes: @ EnableAutoConfiguration

Personally, I think @ EnableAutoConfiguration is the most important Annotation. Its role can be summarized as follows: with the help of @ Import, load all bean definitions that meet the automatic configuration conditions into the IoC container.

Its source code is as follows:

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

Here, we need to pay attention to the two annotations @ AutoConfigurationPackage and @ Import(AutoConfigurationImportSelector.class).

2.2.1 notes: @ AutoConfigurationPackage

The source code is as follows:

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

It can be found that the core of this annotation is actually the Import annotation, which means that the package of the class marked with this annotation should be registered with AutoConfigurationPackages. Next, look at the class of Registrar:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
​
    @Override
      //metadata is the meta information where we annotate
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            //Register all components under the package where we annotate
      register(registry, new PackageImport(metadata).getPackageName());
    }
​
    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
      return Collections.singleton(new PackageImport(metadata));
    }
}

The core method in this class is the register method:

private static final String BEAN = AutoConfigurationPackages.class.getName();
  
  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);
    }
}

The logic of the register method is very clear: if the bean has been registered, get its constructor parameter value and add the package name; Otherwise, a new bean definition is created and registered. Through the annotation @ AutoConfigurationPackage, you can register all components under the package where the annotation is located.

2.2.2 notes: @ Import(AutoConfigurationImportSelector.class)

This annotation imports the AutoConfigurationImportSelector Class. The core method of this Class is the selectImports method, which implements the ImportSelector interface. The method is based on our POM Import the jar package and components configured in the XML file. Therefore, the method returns a String array of the full path of the Class, and the returned Class will be managed by the Spring container. The source code of the method is as follows:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
  if (!isEnabled(annotationMetadata)) {
    return NO_IMPORTS;
  }
  AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
      .loadMetadata(this.beanClassLoader);
  AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
      annotationMetadata);
  return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

The structure of this method is also very clear. First, judge whether it needs to be imported through isEnabled method. If it needs to be imported, obtain the configuration information through loadMetadata method and conduct automatic assembly through getAutoConfigurationEntry. The source code of isEnabled method is as follows:

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

This method is through enableautoconfiguration ENABLED_ OVERRIDE_ Property is the configuration item to judge whether automatic configuration is required. The default value is true. The source code of loadMetadata method is as follows:


protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";
​
  public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
    return loadMetadata(classLoader, PATH);
  }
​
  static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
    try {
      Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
          : ClassLoader.getSystemResources(path);
      Properties properties = new Properties();
      while (urls.hasMoreElements()) {
        properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
      }
      return loadMetadata(properties);
    }
    catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
    }
  }
  static AutoConfigurationMetadata loadMetadata(Properties properties) {
    return new PropertiesAutoConfigurationMetadata(properties);
  }

You can see that this method will load meta-inf / spring-autoconfigure-metadata All configuration information under properties is packaged and returned as an AutoConfigurationMetadata object.

Note: spring autoconfigure metadata The properties file is in spring-boot-autoconfigure-2.1.9 RELEASE. Jar / meta-inf.

The source code of getAutoConfigurationEntry method is as follows:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
      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 = filter(configurations, autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

This method is the main process method of AutoConfiguration. Each line of this method can be regarded as a step. The processing steps are as follows:

1. Load the attribute value with @ EnableAutoConfiguration annotation configured. getAttribute method:

protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
    String name = getAnnotationClass().getName();
    AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
    Assert.notNull(attributes, () -> "No auto-configuration attributes found. Is " + metadata.getClassName()
        + " annotated with " + ClassUtils.getShortName(name) + "?");
    return attributes;
}

2. Get meta-inf / spring In the factories file, take @ EnableAutoConfiguration fully qualified class name as the value of the key, and getCandidateConfigurations method:

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

Among them, springfactoryesloader The function of loadfactorynames () method is to use the given class loader to download from meta-inf / spring Factories loads the fully qualified class name of the factory implementation of the given type;

3. Weight removal;

4. Get the class names of the classes to be excluded, which can be configured in the @ EnableAutoConfiguration annotation;

5. Check these two sets;

6. Remove the classes to be excluded;

7. Filter according to OnBeanCondition, OnClassCondition and other conditions (you can have an in-depth understanding if you are interested);

8. Broadcast events, get all implementation classes of AutoConfigurationImportListener, and then generate events for broadcasting;

9. Encapsulate the fully qualified name of the class to be assembled and excluded into the return of the AutoConfigurationEntry object.

Therefore, @ EnableAutoConfiguration can be simply summarized as: search for all meta-inf / spring from the classpath Factories Configuration file, and instantiate the Configuration item corresponding to EnableAutoConfiguration into the corresponding IoC container Configuration class marked with @ Configuration through reflection, and load it into the IoC container.

3, Summary

From the above analysis, it can be seen that the operation of @ SpringBootApplication annotation is to label the class as a configuration class through the @ SpringApplicationConfiguration declaration, so as to be scanned by the AnnotationConfigApplicationContext and initialize the Spring container.

Scan, filter and load required components through @ EnableAutoConfiguration; Scan and register all classes marked with @ Component and its sub annotations through @ ComponentScan; The joint operation of these annotations realizes the powerful automatic configuration ability of springboot project.

The above is the whole content of this summary. I hope it can be helpful to you. If there are omissions and mistakes, please don't hesitate to correct them.

Author: vivo Internet development team - Peng peng