SpringBoot automatic configuration research

pom.xml

When we initialize a SpringBoot project, check the pom.xml configuration and find the following dependencies

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.5RELEASE</version>
</parent>

According to maven's inheritance dependencies, click in and find that it will continue to depend

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.1.5.RELEASE</version>
    <relativePath>../../spring-boot-dependencies</relativePath>
</parent>

If you click on it again, you can see that the dependency file is finally relied on spring-boot-dependencies, that is to say, the core dependency of SpringBoot is in the parent project. This file defines many jar packages and the versions of each Jar package. We do not need to specify when we write or introduce some SpringBoot dependencies. The version is because of these version libraries.

Note that I used the 2.1.5.RELEASE version above, and you can directly view the dependency inheritance relationship. If it is 2.2.2.RELEASE (the latest version), you can’t click it, and you can change it to the source code from the spring-boot-starter-web starter. Check

Launcher

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

main program

@SpringBootApplication
public class SpringBootApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(SpringBootApplication.class, args);
    }
}
  • @SpringBootApplication This annotation marks that this is a SpringBoot application

  • @SpringBootApplication is a combined annotation, click in and find that it is composed of multiple annotations

    @SpringBootConfiguration
    @EnableAutoConfiguration
    //扫描当前启动类同等级的包
    @ComponentScan(excludeFilters = {
          
          @Filter(type = FilterType.CUSTOM,classes = {
          
          TypeExcludeFilter.class}
    ), @Filter(type = FilterType.CUSTOM,classes = {
          
          AutoConfigurationExcludeFilter.class})})
    public @interface SpringBootApplication {
          
          
    }
    
  • @SpringBootConfiguration indicates that it is a SpringBoot configuration. Check the source code and find that the core annotation is composed of @Configuration, and @Configuration is a Spring annotation. Check the @Configuration source code and find that it is finally a @Component (component)

    @Target({
          
          ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration
    public @interface SpringBootConfiguration {
          
          
    }
    
    @Target({
          
          ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component
    public @interface Configuration {
          
          
        @AliasFor(annotation = Component.class)
        String value() default "";
    }
    
  • @EnableAutoConfiguration automatic configuration, view its source code

    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
          
          
    }
    

    @AutoConfigurationPackage is an automatic configuration package. Check its source code and find that the Registrar class is introduced using @Import

    @Import({
          
          Registrar.class})
    public @interface AutoConfigurationPackage {
          
          
    }
    

    View Registrar class source code

    public abstract class AutoConfigurationPackages {
          
          
        private static final Log logger = LogFactory.getLog(AutoConfigurationPackages.class);
        private static final String BEAN = AutoConfigurationPackages.class.getName();
    
        public AutoConfigurationPackages() {
          
          
        }
    
        public static boolean has(BeanFactory beanFactory) {
          
          
            return beanFactory.containsBean(BEAN) && !get(beanFactory).isEmpty();
        }
    
        public static List<String> get(BeanFactory beanFactory) {
          
          
            try {
          
          
                return ((AutoConfigurationPackages.BasePackages)beanFactory.getBean(BEAN, AutoConfigurationPackages.BasePackages.class)).get();
            } catch (NoSuchBeanDefinitionException var2) {
          
          
                throw new IllegalStateException("Unable to retrieve @EnableAutoConfiguration base packages");
            }
        }
    
        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(AutoConfigurationPackages.BasePackages.class);
                beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
                beanDefinition.setRole(2);
                registry.registerBeanDefinition(BEAN, beanDefinition);
            }
    
        }
        static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
          
          
            Registrar() {
          
          }
            public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
          
          
                AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
            }
            public Set<Object> determineImports(AnnotationMetadata metadata) {
          
          
                return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
            }
        }
    }
    
  • @Import(AutoConfigurationImportSelector.class) introduces the AutoConfigurationImportSelector class

    public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
          
          
        private static final AutoConfigurationImportSelector.AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationImportSelector.AutoConfigurationEntry();
        private static final String[] NO_IMPORTS = new String[0];
        private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
        private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
        private ConfigurableListableBeanFactory beanFactory;
        private Environment environment;
        private ClassLoader beanClassLoader;
        private ResourceLoader resourceLoader;
    
        public AutoConfigurationImportSelector() {
          
          
        }
    	//选择我们在 pom.xml 文件中引入的 jar 包或 组件
        public String[] selectImports(AnnotationMetadata annotationMetadata) {
          
          
            if (!this.isEnabled(annotationMetadata)) {
          
          
                return NO_IMPORTS;
            } else {
          
          
                AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
                AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
                return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
            }
        }
        //获取自动配置实体
        protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
          
          
            if (!this.isEnabled(annotationMetadata)) {
          
          
                return EMPTY_ENTRY;
            } else {
          
          
                AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
                //获取候选配置
                List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
                configurations = this.removeDuplicates(configurations);
                Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
                this.checkExcludedClasses(configurations, exclusions);
                configurations.removeAll(exclusions);
                configurations = this.filter(configurations, autoConfigurationMetadata);
                this.fireAutoConfigurationImportEvents(configurations, exclusions);
                return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
            }
        }
        //META-INF/spring.factories 是自动配置的核心文件
        protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
          
          
            List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
        }
        //EnableAutoConfiguration 这个类就是我们上文分析过的自动导入配置类,
        //这样就可以获取到被这个注解标注的类的所有配置
        //而 @SpringBootApplication 注解就继承了这个注解
        //也即获取了主启动类加载的所有组件(绕了很远...)
        protected Class<?> getSpringFactoriesLoaderFactoryClass() {
          
          
            return EnableAutoConfiguration.class;
        }
    }
    

    About spring.factories : It is a properties file, which is located in the classpath:/META-INF/ directory, and each jar package can have a spring.factories file. Spring provides a tool class SpringFactoriesLoader responsible for loading and parsing files, such as the spring.factories file in the META-INF directory in spring-boot-auto-configure-2.2.0.RELEASE.jar

    # Initializers
    org.springframework.context.ApplicationContextInitializer=\
    org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
    org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
    
    # Application Listeners
    org.springframework.context.ApplicationListener=\
    org.springframework.boot.autoconfigure.BackgroundPreinitializer
    
    # Auto Configuration Import Listeners
    org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
    org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
    
    # Auto Configuration Import Filters
    org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
    org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
    
    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
    org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
    .....
    

    Look at the SpringFactoriesLoader.loadFactoryNames() source code in the getCandidateConfigurations() method

    public final class SpringFactoriesLoader {
          
          
      //loadFactoryNames方法获取所有的加载配置
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
          
          
          String factoryTypeName = factoryType.getName();
          return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
      }
      private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
          
          
          MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
          if (result != null) {
          
          
              return result;
          } else {
          
          
              try {
          
          
                  //加载项目资源
                  Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                  LinkedMultiValueMap result = new LinkedMultiValueMap();
                  //遍历了所有的自动配置,封装成 properties 供我们使用
                  while(urls.hasMoreElements()) {
          
          
                      URL url = (URL)urls.nextElement();
                      UrlResource resource = new UrlResource(url);
                      Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                     。。。。。。
                  }
                  cache.put(classLoader, result);
                  return result;
              }
          }
      }
    }
    

    auto-configuration package
    insert image description here

@ConditionalOnXXXX annotation

There are so many configuration classes in spring.factories, and some of them are not effective. You need to import the corresponding starter to be effective. For example, find a configuration class called AopAutoConfiguration and check its source code. It will only take effect if the conditions in the @ConditionalOnXXXX annotation are met.

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnProperty(
    prefix = "spring.aop",
    name = {
    
    "auto"},
    havingValue = "true",
    matchIfMissing = true
)
public class AopAutoConfiguration {
    
    
}

Another example is the source code, as long as the @ConditionalOnClass condition is met, it will take effect

@Configuration(  proxyBeanMethods = false)
@ConditionalOnClass({
    
    DataSource.class, EmbeddedDatabaseType.class})
@EnableConfigurationProperties({
    
    DataSourceProperties.class})
@Import({
    
    DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class})
public class DataSourceAutoConfiguration {
    
    
}

How does the configuration of the yml file take effect?

According to the above analysis, SpringBoot will load all automatic classes of spring.factories. For the configuration class DataSourceAutoConfiguration, if the pom file has a corresponding startup class, it will take effect, and then the default configuration file will be loaded through @EnableConfigurationProperties. It can be seen from the source code that the default configuration file is DataSourceProperties. If we reconfigure some properties in yml, such as jdbc.name, jdbc.username, jdbc.password..., SpringBoot will read these properties and override the default properties.

@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
    
    
    private ClassLoader classLoader;
    private String name;
    private boolean generateUniqueName;
    private Class<? extends DataSource> type;
    private String driverClassName;
    private String url;
    private String username;
    private String password;
    private String jndiName;
    ....
}

Summarize

All automatic configurations of SpringBoot are scanned and loaded at startup. All automatic class configurations of spring.factories are here, but they may not take effect. To determine whether the conditions are true (via @ConditionalOnXXXX annotation), as long as the corresponding starter is imported There is a corresponding launcher. With the launcher, the automatic assembly will take effect, and then the configuration will be successful.

  1. SpringBoot gets the value specified by EnableAutoConfiguration from META-INF/spring.factories under the classpath at startup
  2. Import these values ​​into the container as an automatic configuration class, and the automatic configuration class will take effect, helping us with the automatic configuration work;
  3. In the past, we needed to configure things ourselves, and the automatic configuration class helped us solve them
  4. The overall solution and automatic configuration of the entire J2EE are in the jar package of springboot-autoconfigure;
  5. It returns all the components that need to be imported as full class names, and these components will be added to the container;
  6. It will import a lot of automatic configuration classes (xxxAutoConfiguration) into the container, that is, import all the components needed for this scene into the container, and configure these components;
  7. With the automatic configuration class, it saves us the work of manually writing configuration injection function components, etc.;

Guess you like

Origin blog.csdn.net/lhg_55/article/details/105900343