🤖🤖-摘要: 本文介绍了SpringBoot自动配置的原理, 包括自动配置流程,核心配置流程以及如何实施自动配置。首先导入’starter’以获得来自’autoconfigure’包的配置, 然后使用‘@EnableAutoConfiguration’自动载入所有自动配置类。根据条件选择需要的配置类后,上述类将使用由配置文件中的特定前缀属性值提取而成的组件,从而实现自动配置。
我的SpringBoot项目 第二个模块
在日常开发中, 通常我们只需要引入某个场景启动器, 再加上一些相应的配置即可, 无需费心复杂的整合操作, 这也是 SpringBoot 的强大之处.
SpringBoot 是如何省去繁杂的整合过程的呢?
接下来按照流程一步一步分析.
SpringBoot自动配置流程 1.导入starter 以web场景为例, 导入了web开发场景
场景启动器导入了相关场景的所有依赖, 如下:starter-json,starter-tomcat,springMVC 每个场景启动器都引入了一个spring-boot-starter, 核心场景启动器 核心场景启动器 引入了spring-boot-autoconfigure包spring-boot-autoconfigure里面囊括了所有场景的所有配置只要这个包下的所有类都能生效, 那么相当于SpringBoot官方写好的整合功能就生效了 SpringBoot默认却扫描不到spring-boot-autoconfigure下写好的所有配置类 .这些配置类 给我们做了整合操作,默认只扫描主程序所在的包 2.主程序:@SpringBootApplication 为什么引入场景,无需配置就已经整合完成了?一切都要从@SpringBootApplication开始
@SpringBootApplication由三个注解组成@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan@SpringBootConfiguration 其实就是@Configuration, 声明主程序类是一个配置类 @ComponentScan SpringBoot默认只能扫描自己主程序所在的包及其下面的子包, 扫描不到spring-boot-autoconfigure包中官方写好的配置类 @EnableAutoConfiguration就是SpringBoot开启自动配置 的核心@Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { }
@Import(AutoConfigurationImportSelector.class) 可以批量给容器中导入组件SpringBoot@3.1.0 会批量导入146个自动配置类 (xxxAutoConfiguration)public class AutoConfigurationImportSelector implements DeferredImportSelector , BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } protected AutoConfigurationEntry getAutoConfigurationEntry (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 = getConfigurationClassFilter().filter(configurations); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry (configurations, exclusions); } protected List<String> getCandidateConfigurations (AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()) .getCandidates(); Assert.notEmpty(configurations, "No auto configuration classes found in " + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you " + "are using a custom packaging, make sure that file is correct." ); return configurations; } }
按需生效 虽然导入了146个自动配置类 , 并不是都能生效 每一个自动配置类 , 都有条件注解@ConditionalOnxxx, 只有条件成立, 才能生效 导入对应的依赖包, 即满足条件, 该自动配置类就会生效 3.xxxAutoConfiguration自动配置类 每个xxxAutoConfiguration自动配置类,都需要满足某个条件 @ConditionalOnClass(Xxx.class) @Conditional(XxxCondition.class) @EnableConfigurationProperties(XxxProperties.class) public class XxxAutoConfiguration { }
条件满足, 自动配置类 就会给容器中使用@Bean放一堆组件
每个自动配置类 都可能有这个注解@EnableConfigurationProperties(XxxProperties.class)
用来把配置文件中配的指定前缀 的属性值封装到XxxProperties属性类 中 以Tomcat 为例:把服务器的所有配置都是以server开头的。配置都封装到了属性类中
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) public class ServerProperties { }
SpringBoot核心配置流程 导入starter, 就会导入autoconfigure包 autoconfigure包里有文件:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ,里面指定的所有启动要加载的自动配置类 @EnableAutoConfiguration会自动的把上面文件里所有自动配置类 都导入进来. XxxAutoConfiguration是有条件注解进行按需加载 XxxAutoConfiguration给容器中导入一堆组件, 组件都是从xxxProperties中提取属性值XxxProperties又和配置文件 进行绑定达到的效果:导入starter –> 修改配置文件, 就能修改底层行为
小试牛刀 接下来检验下SpringBoot自动配置原理的理解, 尝试回答如下问题:
为什么项目启动后默认端口号是8080, 以及如何修改??? 引入web场景 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency >
自动引入了spring-boot-starter和spring-boot-starter-tomcat
<dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter</artifactId > <version > 3.1.0</version > <scope > compile</scope > </dependency >
<dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-tomcat</artifactId > <version > 3.1.0</version > <scope > compile</scope > </dependency >
spring-boot-starter自动引入依赖spring-boot-autoconfigure包
<dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-autoconfigure</artifactId > <version > 3.1.0</version > <scope > compile</scope > </dependency >
spring-boot-autoconfigure包里有文件:
additional-spring-configuration-metadata.json , 里面包含了所有的默认值{ "name" : "server.port" , "defaultValue" : 8080 }
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ,里面指定的所有启动要加载的自动配置类 合成注解@SpringBootApplication中的@EnableAutoConfiguration 自动的把上面文件里所有自动配置类 都导入进来. XxxAutoConfiguration是有条件注解进行按需加载 其中就包括ServletWebServerFactoryAutoConfiguration.java
@AutoConfiguration(after = SslAutoConfiguration.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @ConditionalOnClass(ServletRequest.class) @ConditionalOnWebApplication(type = Type.SERVLET) @EnableConfigurationProperties(ServerProperties.class) @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, ServletWebServerFactoryConfiguration.EmbeddedJetty.class, ServletWebServerFactoryConfiguration.EmbeddedUndertow.class }) public class ServletWebServerFactoryAutoConfiguration { }
绑定了属性文件ServerProperties
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) public class ServerProperties { private Integer port; }
只需要在自己的配置文件中自定义server.port的值
SpringBoot启动时就会去resources路径下加载符合要求的文件, 从该文件中查找配置来覆盖默认配置, 即完成了配置自定义
spring-boot-starter-parent-3.1.0.pom文件:
<build > <resources > <resource > <directory > ${basedir}/src/main/resources</directory > <filtering > true</filtering > <includes > <include > **/application*.yml</include > <include > **/application*.yaml</include > <include > **/application*.properties</include > </includes > </resource > <resource > <directory > ${basedir}/src/main/resources</directory > <excludes > <exclude > **/application*.yml</exclude > <exclude > **/application*.yaml</exclude > <exclude > **/application*.properties</exclude > </excludes > </resource > </resources > ... </build >