7

I. Introduction

Most of the configuration can be replaced by Java classes + annotations, and SpringBoot project is the @SpringBootApplication annotation, which is marked on each SpringBoot startup class.

What effect does this annotation have on the startup and automatic configuration SpringBoot This article will analyze its source code for you guys and unveil the mystery of @SpringBootApplication annotations.

Second, the text

I am very interested in the automatic configuration of the SpringBoot project, so I studied its source code and sorted out some of its contents. If there is an error, please correct me~ Don't say much, just go 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 {
...
}

You can see that this is a compound annotation, including a total of 7 different annotations, the following analysis of these 7 different annotations.

2.1 Annotation

2.1.1 Note 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 Note 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 is annotated with @SpringBootApplication, the subclass will also inherit this annotation (it is invalid for the implementation class of the interface).

2.1.5 Note 5: @SpringBootConfiguration

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

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

2.1.6 Note 6: @ComponetScan

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

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

2.2 Annotation: @EnableAutoConfiguration

I personally feel that @EnableAutoConfiguration is the most important Annotation. Its role can be summarized as: With the help of @Import, all bean definitions that meet the conditions of automatic configuration are loaded into the IoC container.

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

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

2.2.1 Comment: @AutoConfigurationPackage

The source code is as follows:

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

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. Then look at the Registrar class:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
​
    @Override
      //metadata是我们注解所在的元信息
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            //将我们注解所在包下所有的组件进行注册
      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, create a new bean definition and register it. Through the annotation @AutoConfigurationPackage, all components under the package where the annotation is located can be registered.

2.2.2 Comments: @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 the jar package and components configured in the pom.xml file to import. So 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 method source code 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, use the isEnabled method to determine whether you need to import. If you need to import, use the loadMetadata method to obtain the configuration information, and use getAutoConfigurationEntry for automatic assembly. The source code of the 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 uses the configuration item EnableAutoConfiguration.ENABLED\_OVERRIDE\_PROPERTY to determine whether automatic configuration is required, and the default is true. The source code of the 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 all the configuration information under META-INF/spring-autoconfigure-metadata.properties and package it into an AutoConfigurationMetadata object to return.

Note: The spring-autoconfigure-metadata.properties file is under spring-boot-autoconfigure-2.1.9.RELEASE.jar/META-INF.

The source code of the 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, then the processing steps are as follows:

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

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 the value of the key with @EnableAutoConfiguration fully qualified class name in the META-INF/spring.factories file, the 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, the function of the SpringFactoriesLoader.loadFactoryNames() method is to use the given class loader to load the fully qualified class name of the given type of factory implementation from META-INF/spring.factories;

3. Removal;

4. Get the class names of the classes that need to be excluded. These classes can be configured in the @EnableAutoConfiguration annotation;

5. Check these two sets;

6. Remove the classes that need to be excluded;

7. Filter according to OnBeanCondition, OnClassCondition and other conditions (if you are interested, you can learn more);

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 an AutoConfigurationEntry object and return.

Therefore, @EnableAutoConfiguration can be simply summarized as: search for all META-INF/spring.factories configuration files from the classpath, and instantiate the configuration items corresponding to EnableAutoConfiguration into the corresponding IoC container configuration class marked with @Configuration through reflection. And load it into the IoC container.

III. Summary

From the above analysis, we can see that @ SpringBootApplica ti on The operation of the annotation is to declare the marked class as a configuration class by @SpringApplicationConfiguration to be scanned by AnnotationConfigApplicationContext and initialize the Spring container.

Use @EnableAutoConfiguration to scan, filter and load the required components; use @ComponentScan to scan and register all classes marked with @Component and its sub-annotations; the joint operation of these annotations realizes the powerful automatic configuration capabilities of the springboot project.

The above is all the content of this summary, I hope it can be helpful to everyone. If there are omissions and errors, please feel free to correct me.

Author: vivo Internet development team -Peng peng

vivo互联网技术
3.3k 声望10.2k 粉丝