SpringBoot 自动配置探究

SpringBoot 自动配置探究

  • pom.xml
  • 启动器
  • 主程序
  • @ConditionalOnXXXX 注解
  • yml 文件的配置是如何生效的?
  • 总结

pom.xml

当我们初始化一个 SpringBoot 工程,查看 pom.xml配置,发现有如下 依赖

<parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-parentartifactId>
    <version>2.1.5RELEASEversion>
parent>

根据 maven 的继承依赖关系,点进去发现还会继续依赖

<parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-dependenciesartifactId>
    <version>2.1.5.RELEASEversion>
    <relativePath>../../spring-boot-dependenciesrelativePath>
parent>

再点进去可以看到最终依赖了spring-boot-dependencies依赖文件,也就是说 SpringBoot的核心依赖在父工程中,这个文件定义了好多jar 包以及各个 Jar 包的 版本,我们写或引入一些 SpringBoot 依赖的时候不需要指定版本就因为有这些版本库。

注意我上面用的是 2.1.5.RELEASE 版本,可以直接查看依赖继承关系,如果是 2.2.2.RELEASE(最新版)点不了,改成从 spring-boot-starter-web 启动器进源码才可以查看

启动器

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>
  • 启动器说白了就是 SpringBoot 的启动场景
  • 比如 spring-boot-starter-web ,它就会帮我们自动导入 web 环境所有依赖
  • SpringBoot 会将所有的功能场景,都变成一个个的启动器,我们需要使用什么功能,只需要找到对应的启动器就可以了
  • 官网文档还罗列了好多其它启动器:https://docs.spring.io/spring-boot/docs/2.2.2.RELEASE/reference/html/using-spring-boot.html#using-boot-starter

主程序

@SpringBootApplication
public class SpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootApplication.class, args);
    }
}
  • @SpringBootApplication 这个注解它就标注了这是一个 SpringBoot 的应用

  • @SpringBootApplication 是一个组合注解,点进去发现由多个注解组成

    @SpringBootConfiguration
    @EnableAutoConfiguration
    //扫描当前启动类同等级的包
    @ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}
    ), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class})})
    public @interface SpringBootApplication {
    }
    
  • @SpringBootConfiguration 表明它是一个 SpringBoot 配置,查看源码发现核心注解由 @Configuration 组成,而 @Configuration 是个 Spring 注解,查看 @Configuration 源码发现最终是个 @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 自动配置,查看其源码

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

    @AutoConfigurationPackage 就是自动配置包,查看其源码,发现使用 @Import 引入了 Registrar 这个类

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

    查看 Registrar 类源码

    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) 则引入了 AutoConfigurationImportSelector 类

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

    关于spring.factories: 它是一个properties文件,它位于classpath:/META-INF/目录里面,每个jar包都可以有spring.factories的文件。Spring 提供工具类 SpringFactoriesLoader 负责加载、解析文件,如spring-boot-auto-configure-2.2.0.RELEASE.jar里面的META-INF目录里面就有spring.factories文件

    # 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,\
    .....
    

    再看看 getCandidateConfigurations()方法中的 SpringFactoriesLoader.loadFactoryNames() 源码

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

    自动配置包
    SpringBoot 自动配置探究_第1张图片

@ConditionalOnXXXX 注解

spring.factories 中那么多的配置类,有的并没有生效,需要导入对应的 starter 才能有作用,比如随便找个 AopAutoConfiguration 这个配置类,查看其源码,只有满足了 @ConditionalOnXXXX 注解里面的条件才会生效

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

再比如的源码,只要满足 @ConditionalOnClass 条件即会生效

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

yml 文件的配置是如何生效的?

根据上文分析,SpringBoot 会加载 spring.factories 的所有自动类,以 DataSourceAutoConfiguration 这个配置类来说,如果 pom 文件有对应的启动类,它就会生效,那么就会通过 @EnableConfigurationProperties 加载默认的配置文件,从源码可以看出默认的配置文件是 DataSourceProperties ,假如我们在 yml 中重新配置了某些属性,比如 jdbc.name、jdbc.username、jdbc.password…,SpringBoot 会读取这些属性并覆盖默认属性。

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

总结

SpringBoot 所有自动配置都是在启动的时候扫描并加载,spring.factories 所有的自动类配置都得了这里,但是不一定会生效,要判断条件是否成立(通过 @ConditionalOnXXXX 注解),只要导入了对应的 starter 就有对应的启动器了,有了启动器,自动装配就会生效,然后就配置成功。

  1. SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
  2. 将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
  3. 以前我们需要自己配置的东西 , 自动配置类都帮我们解决了
  4. 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
  5. 它将所有需要导入的组件以全类名的方式返回 , 这些组件就会被添加到容器中 ;
  6. 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
  7. 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;

你可能感兴趣的:(spring框架)