Springboot Analysis (1) - Automatic Configuration

Why learn Springboot, on the one hand, it is mainly because Springboot is too widely used. It is necessary to deepen your understanding of it for your own development work, because if you encounter or business scenarios, you need to do some slightly more advanced Springboot-based extensions and Application, may not understand or not understand. On the other hand, as a coding enthusiast, I am curious about such an excellent framework as springboot. The article is based on SpringBoot2.1.9.

1. Manual assembly of SpringBoot

(1) The way of manual assembly

Mainly include the following methods:

  • Use pattern annotations such as @Component (Spring2.5+), which is commonly used, but cannot assemble components in the jar package directory. For this reason, you can use @Configuration and @Bean to manually assemble components
  • Using the configuration classes @Configuration and @Bean (Spring3.0+), too many registrations will lead to problems such as high coding costs and inflexible maintenance.
  • Use module assembly @EnableXXX and @Import (Spring3.1+), @Import is used to assemble the specified class, @EnableXXX is used to enable the specified @Import

The first one or two are often used in projects, so I won't introduce them too much. Here we mainly introduce the use of module assembly, which is also the main way that various Spring Starters can be assembled.

(2) Use of @EnableXXX and @Import

There are four scenarios here, importing common classes, importing configuration classes, importing ImportSelector, and importing ImportBeanDefinitionRegistrar. Next, 4 small demos are used to demonstrate how to use it.

1. Import common classes

Our goal is to import a TestService class, TestService is a normal class.

step

(1) Create a new annotation EnableTest
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(TestService.class)
public @interface EnableTest {
}
(2) Annotate @EnableTest in the configuration class or startup class that can be scanned
@SpringBootApplication
@EnableTest
public class SpringbootexampleApplication {

  public static void main(String[] args) {
    SpringApplication.run(SpringbootexampleApplication.class, args);
  }
}

2. Import the configuration class

The @Configuration annotation without a bit and startup class will not be scanned by default. At this time, the class marked with @Configuration and the beans registered under it need to be assembled manually.

step

(1) Create a new configuration class
@Configuration
public class TestRegistrarConfiguration {
    @Bean
    public Test2Service yellow() {
        return new Test2Service();
    }
}
(2) Modify EnableTest and add the introduction of the configuration class
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({TestService.class,TestRegistrarConfiguration.class})
public @interface EnableTest {
}

In this way, all beans under the TestRegistrarConfiguration configuration class are also indirectly assembled at startup.

3. Import ImportSelector

The ImportSelector interface specifies the String[] selectImports(AnnotationMetadata importingClassMetadata)method, and the return value of this method is an array of String, which represents the array of classNames of the bean to be assembled. The automatic assembly of SpringBoot is actually used in this way.

step

(1) Create a new ImportSelector
public class TestImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 返回类.getName()。
        return new String[]{Test3Service.class.getName()};
    }
}

Note: The parameter importingClassMetadata represents the information of the class marked by @Import. (Not counting annotations), here is the configuration class modified by @EnableTest, namely SpringbootexampleApplication.

(2) Modify EnableTest and add the introduction of the import selector

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({TestService.class, TestRegistrarConfiguration.class, TestImportSelector.class})
public @interface EnableTest {
}

4. Import ImportBeanDefinitionRegistrar

The ImportBeanDefinitionRegistrar interface specifies the void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)method. There are two parameters AnnotationMetadata, which are the same as the previous ImportSelector interface method parameters. The registry is the registrant used to register the BeanDefinition.

step

(1) Create a new ImportBeanDefinitionRegistrar
public class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        registry.registerBeanDefinition("Test4Service", new RootBeanDefinition(Test4Service.class));
    }
}

(2) Modify EnableTest and add the introduction of the registrar defined by the imported bean

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({TestService.class, TestRegistrarConfiguration.class, TestImportSelector.class, TestImportBeanDefinitionRegistrar.class})
public @interface EnableTest {
}

Second, the automatic configuration of SpringBoot

Automatic configuration is actually that the Spring framework applies the principle of manual assembly, assembles its own default components, and also provides some extension points, allowing applications to extend our own components. Of course, SpringBoot can also complete the assembly of configuration classes through component scanning @ComponentScan for components extended under the basePackage application.

(1) A brief look at @ComponentScan

We know that @SpringBootApplication is a composite annotation, including @SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan. as follows:

@SpringBootConfiguration  
@EnableAutoConfiguration  
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication

Here @ComponentScan defines two filters by default, TypeExcludeFilter and AutoConfigurationExcludeFilter, where the core code of TypeExcludeFilter is as follows:

public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
        throws IOException {
    if (this.beanFactory instanceof ListableBeanFactory && getClass() == TypeExcludeFilter.class) {
        //获取容器内实现了TypeExcludeFilter的所有bean
        Collection<TypeExcludeFilter> delegates = ((ListableBeanFactory) this.beanFactory)
                .getBeansOfType(TypeExcludeFilter.class).values();
        for (TypeExcludeFilter delegate : delegates) {
 		//遍历获取的TypeExcludeFilter的bean,如果满足,则通过
            if (delegate.match(metadataReader, metadataReaderFactory)) {
                return true;
            }
        }
    }
    return false;
}

It mainly provides an extension point to get all beans of type TypeExcludeFilter in the container, execute the match method, and if it returns true, filter it. Two of the parameters metadataReader are used to read the type information obtained by scanning. metadataReaderFactory is used to obtain other types of metadataReader. For example, if you do not want to scan a certain class, register a subclass bean of TypeExcludeFilter. The logic of the match method is to obtain the ClassName from the metadataReader, and then return true to filter if it is equal to xxxClass.

The role of AutoConfigurationExcludeFilter is to exclude whether it is an automatically assembled Configuration, and if so, scan and filter. The core code is as follows:

@Override  
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)  
  throws IOException {  
  return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);  
}  
  
private boolean isConfiguration(MetadataReader metadataReader) {  
  return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());  
}  
  
private boolean isAutoConfiguration(MetadataReader metadataReader) {  
  return getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());  
}  
  
protected List<String\> getAutoConfigurations() {  
  if (this.autoConfigurations == null) {  
  this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,  
 this.beanClassLoader);  
  }  
  return this.autoConfigurations;  
}

isConfiguration(metadataReader) judges whether it is annotated with @Configuration, and isAutoConfiguration(metadataReader) judges whether it is an automatic assembly class (the automatic assembly method here will be described in detail later), so through this we know that the @Component component scanning and the automatic assembly mechanism do not conflict.

Second, the principle of automatic configuration

The core annotation is @EnableAutoConfiguration. Its contents are as follows:

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

(1) The role of AutoConfigurationPackage

Among them, the content of @AutoConfigurationPackage is as follows:

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

An inner class of AutoConfigurationPackages is introduced here: Registrar, let's take a look at the content:

/**
	 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
	 * configuration.
	* (用于保存导入的配置类所在的根包。)
	 */
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    @Override
    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));
    }

}

register(registry, new PackageImport(metadata).getPackageName()) uses the register method of the external class AutoConfigurationPackages, because metadata here is actually the metadata of the startup class, so new PackageImport(metadata).getPackageName() returns the startup The package path of the class. Take a look at the register method of AutoConfigurationPackages:

public static void register(BeanDefinitionRegistry registry, String... packageNames) {  
 private static final String BEAN = AutoConfigurationPackages.class.getName();

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
    // 判断 BeanFactory 中是否包含名为AutoConfigurationPackages.class.getName()的beanDefinition
    if (registry.containsBeanDefinition(BEAN)) {
        BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
        ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
        // addBasePackages:merge当前的packageNames参数(根包)到原来的构造器参数里面
        constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
    }
    else {
	   //构造一个BasePackages的BD,设置构造器参数为当前的packageNames参数
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(BasePackages.class);
        beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(BEAN, beanDefinition);
    }
}
}

To sum up, the function of AutoConfigurationPackage is to register a BeanDefinition of BasePackage, whose constructor contains the package path of the class identified by EnableAutoConfiguration.

(2) The role of @Import(AutoConfigurationImportSelector.class)

Take a look at AutoConfigurationImportSelector:

@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 return value is StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()), focus on how the Configurations in autoConfigurationEntry are generated, and enter the getAutoConfigurationEntry method:

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

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

The key here is SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); the first parameter is EnableAutoConfiguration.class, followed by a class loader. SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); This method is very common in Springboot source code, such as container startup to get the initializer ApplicationContextInitializer instance, etc. Next, let's see what is done here:

blic static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
	//通过factoryClassName的className从一个map里面取相应的className集合
    return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
   //如果有,这从缓存里面拿,否则去生成
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }
	//使用 classLoader 去加载了指定常量路径下的资源: FACTORIES_RESOURCE_LOCATION ,而这个常量指定的路径实际是:META-INF/spring.factories 。
    try {
        Enumeration<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);
			//以 Properties 的形式加载
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryClassName = ((String) entry.getKey()).trim();
                for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
					//键为Properties文件的键,值为properties文件的值用逗号分隔
                    result.add(factoryClassName, factoryName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                                       FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

spring.factories can be in the meta-inf directory of any project. For example, we can find the spring.factories file under the spring-boot-autoconfiguration package as shown in the figure:

In summary, the role of @Import(AutoConfigurationImportSelector.class) is to load the set of classNames identified in the spring.factories file with the className of EnableAutoConfiguration as the key, and then register the BeanDefinition of these corresponding classes to the container. If you have an understanding of JDK's SPI mechanism, you will feel very familiar. This mechanism here is a bit similar to JDK's native SPI mechanism. It obtains the corresponding className from the configuration file and instantiates it. The SPI of jdk refers to the An interface defines its own implementation class. The method is also to edit the content of the configuration file of the corresponding interface in the meta-inf/service directory, and identify the className of the implementation class. But I personally think that there are some differences with SpringFactoryLoader. The SPI of the JDK is to abstract the loaded class into a strategy (strategy mode), and load the configuration file configuration through the abstract class. Spring is more like abstracting the loaded class as a product (factory pattern), and loading the fully qualified class name configured by the configuration file through the fully qualified class name of the given factory, so that the configured classes can be loaded accordingly.

(3) Summary

The core annotation @EnableAutoConfiguration of automatic configuration mainly does two things:

  1. Assembling AutoConfigurationPackages.Registrar, the registrant registers a BeanDefinition whose class is BasePackages, and adds the startup classpath to the constructor parameter of this BD.
  2. Assemble AutoConfigurationImportSelector, the selector scans all classes whose key is EnableAutoConfiguration.class.getName in the spring.factories file in the META-INF directory. Register its BeanDefinition into the container.

3. Summary

Today, I mainly introduce manual assembly and automatic configuration, and understand the principle of Springboot automatic configuration. Through this, we can also develop some packages for other frameworks by ourselves, or develop a set of components by ourselves, and then import jar packages into other projects for automatic configuration, which is very convenient without having to configure it ourselves.

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

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=324078691&siteId=291194637