最详细 SpringBoot 自动配置原理!看完你就懂了!

1. SpringBoot 是什么

SpringBoot的诞生旨在极大简化Spring框架中繁琐的XML配置过程。其本质依然是Spring框架,但引入了更为便捷的方式,使得开发者无需依赖任何XML配置即可快速启动服务。这一特性在微服务架构中尤为突出,因为它允许我们更迅速地构建和部署应用。

简而言之,SpringBoot并非全新的框架,而是通过提供一系列默认配置,简化了众多框架的使用,从而加速了开发进程。

2. SpringBoot 的特点

  • 提供了固定的配置来简化配置,即约定大于配置

  • 尽可能地自动配置 Spring 和第三方库,即能自动装配

  • 内嵌容器,创建独立的 Spring 应用

  • 让测试变的简单,内置了 JUnit、Spring Boot Test 等多种测试框架,方便测试

  • 提供可用于生产的特性,如度量、运行状况检查和外部化配置。

  • 完全不需要生成代码,也不需要 XML 配置。

3. SpringBoot 启动类

下面探究 SpringBoot 的启动原理,关于一些细节就不赘述,我们捉住主线分析即可。

注意:本文的 SpringBoot 版本为 2.6.1

3.1 @SpringBootApplication

一切的来自起源 SpringBoot 的启动类,我们发现 main 方法上面有个注解:@SpringBootApplication.

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

@SpringBootApplication 标注在某个类上说明这个类是 SpringBoot 的主配置类, SpringBoot 就应该运行这个类的 main 方法来启动 SpringBoot 应用;它的本质是一个组合注解,我们点进去查看该类的元信息主要包含 3 个注解:

@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 {

  • @SpringBootConfiguration(里面就是 @Configuration,标注当前类为配置类,其实只是做了一层封装改了个名字而已)

  • @EnableAutoConfiguration(开启自动配置)

  • @ComponentScan(包扫描)

注:@Inherited 是一个标识,用来修饰注解,如果一个类用上了 @Inherited 修饰的注解,那么其子类也会继承这个注解。

我们下面逐一分析这 3 个注解作用。

3.1.1 @SpringBootConfiguration

我们继续点 @SpringBootConfiguration 进去查看源码如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}

@Configuration 标注在某个类上,表示这是一个 SpringBoot 的配置类。可以向容器中注入组件。

3.1.2 @ComponentScan
  • @ComponentScan:配置用于 Configuration 类的组件扫描指令

  • 提供与 Spring XML 的 <context:component-scan> 元素并行的支持

  • 可以 basePackageClasses 或basePackages 来定义要扫描的特定包 如果没有定义特定的包,将从声明该注解的类的包开始扫描

3.1.3 @EnableAutoConfiguration

@EnableAutoConfiguration 顾名思义就是开启自动导入配置,这个注解是 SpringBoot 的重点,我们下面详细讲解。

4. @EnableAutoConfiguration

我们点进去看看该注解有什么内容。

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

4.1 @AutoConfigurationPackage

自动导入配置包,点进去查看代码。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};

Class<?>[] basePackageClasses() default {};
}

@Import  为 Spring 的注解,导入一个配置文件,在 SpringBoot 中为给容器导入一个组件,而导入的组件由  AutoConfigurationPackages.class 的内部类 Registrar.class  执行逻辑来决定是如何导入的。

4.1.1 @Import({Registrar.class})

点 Registrar.class 进去查看源码如下:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//断点
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}

public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}

注:Registrar 实现了 ImportBeanDefinitionRegistrar 类,就可以被注解 @Import 导入到 Spring 容器里。

这个地方打断点。

运行可以查看到 (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]) 的值为 com.ljw.springbootwork,当前启动类所在的包名。

结论:@AutoConfigurationPackage 就是将主配置类(@SpringBootApplication 标注的类)所在的包下面所有的组件都扫描注冊到 Spring 容器中。

4.2 @Import({AutoConfigurationImportSelector.class})

AutoConfigurationImportSelector 开启自动配置类的导包的选择器,即是带入哪些类,有选择性的导入。

点 AutoConfigurationImportSelector.class 进入查看源码,这个类中有两个方法见名知意:

  1. selectImports:选择需要导入的组件

public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}

  1. getAutoConfigurationEntry:根据导入的 @Configuration 类的 AnnotationMetadata 返回 AutoConfigurationImportSelector.AutoConfigurationEntry.

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(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.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}

this.getCandidateConfigurations(annotationMetadata, attributes) 这里断点查看。

configurations 数组长度为 133,并且文件后缀名都为  AutoConfiguration.

结论:这些都是候选的配置类,经过去重,去除需要的排除的依赖,最终的组件才是这个环境需要的所有组件。有了自动配置,就不需要我们自己手写配置的值了,配置类有默认值的。

我们继续往下看看是如何返回需要配置的组件的。

4.2.1 getCandidateConfigurations(annotationMetadata, attributes)

方法如下:

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

这里有句断言: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.");

意思是:“在 META-INF/spring.factories 中没有找到自动配置类。如果您使用自定义包装,请确保该文件是正确的。”

结论:即是要 loadFactoryNames() 方法要找到自动的配置类返回才不会报错。

4.2.1.1 getSpringFactoriesLoaderFactoryClass()

我们点进去发现:this.getSpringFactoriesLoaderFactoryClass() 返回的是 EnableAutoConfiguration.class 这个注解。

这个注解和 @SpringBootApplication 下标识注解是同一个注解。

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

结论:获取一个能加载自动配置类的类,即 SpringBoot 默认自动配置类为 EnableAutoConfiguration.

4.2.2 SpringFactoriesLoader

SpringFactoriesLoader 工厂加载机制是 Spring 内部提供的一个约定俗成的加载方式,只需要在模块的 META-INF/spring.factories 文件,这个 Properties 格式的文件中的 Key 是接口、注解、或抽象类的全名,Value 是以逗号,分隔的实现类,使用 SpringFactoriesLoader 来实现相应的实现类注入 Spirng 容器中。

注:会加载所有 jar 包下的 classpath 路径下的 META-INF/spring.factories 文件,这样的文件不止一个

4.2.2.1 loadFactoryNames()

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

断点查看 factoryTypeName:

image.png

先是将  EnableAutoConfiguration.class  传给了  factoryType

然后 String factoryTypeName = factoryType.getName();,所以factoryTypeName  值为   org.springframework.boot.autoconfigure.EnableAutoConfiguration.

4.2.2.2 loadSpringFactories()

接着查看 loadSpringFactories 方法的作用。

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
//断点查看
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}

result = new HashMap<>();
try {
//注意这里:META-INF/spring.factories
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
//断点
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}

// Replace all lists with unmodifiable lists containing unique elements
//去重,断点查看result值
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}

这里的  FACTORIES\_RESOURCE\_LOCATION  在上面有定义:META-INF/spring.factories.

public final class SpringFactoriesLoader {

/**
* The location to look for factories.
* <p>Can be present in multiple JAR files.
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

META-INF/spring.factories 文件在哪里??

在所有引入的 Java 包的当前类路径下的 META-INF/spring.factories 文件都会被读取,如:

断点查看 result 值如下:

该方法作用是加载所有依赖的路径 META-INF/spring.factories 文件,通过 Map 结构保存,Key 为文件中定义的一些标识工厂类,Value 就是能自动配置的一些工厂实现的类,Value 用 List 保存并去重。

在回看 loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());

因为 loadFactoryNames 方法携带过来的第一个参数为 EnableAutoConfiguration.class,所以 factoryType 值也为 EnableAutoConfiguration.class,那么 factoryTypeName 值为 EnableAutoConfiguration。拿到的值就是 META-INF/spring.factories文件下的 Key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的值。

getOrDefault  当  Map  集合中有这个 Key 时,就使用这个 Key 值,如果没有就使用默认值空数组。

结论:

  • loadSpringFactories() 方法的作用是从 META-INF/spring.factories 文件中加载指定类型的工厂实现的完全限定类名,并将它们存储在一个 Map 中。而 loadFactoryNames() 方法则在 SpringBoot 的启动生命周期中扮演着关键角色。当系统需要加载自动配置类时,该方法会接收 org.springframework.boot.autoconfigure.EnableAutoConfiguration 作为参数,从前面提到的 Map 中查找与这个 Key 相关联的值。这些值代表自动配置类的完全限定名,它们随后通过反射机制被添加到 Spring 容器中。这一过程标志着 SpringBoot 自动配置的启动。

    只有当这些自动配置类被成功加载到容器中后,自动配置流程才会继续进行。若需要加载其他类型的配置,例如监听器(Listener),则可以通过传递不同的参数来获取相应的配置信息。

5. 流程总结图

6. 常用的 Conditional 注解

在加载自动配置类的时候,并不是将 spring.factories 的配置全部加载进来,而是通过 @Conditional 等注解的判断进行动态加载。

@Conditional 其实是 Spring 底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么配置类里边的配置才会生效

常用的 Conditional 注解:

  • @ConditionalOnClass:classpath 中存在该类时起效

  • @ConditionalOnMissingClass:classpath 中不存在该类时起效

  • @ConditionalOnBean:DI 容器中存在该类型 Bean 时起效

  • @ConditionalOnMissingBean:DI 容器中不存在该类型 Bean 时起效

  • @ConditionalOnSingleCandidate:DI 容器中该类型 Bean 只有一个或@Primary 的只有一个时起效

  • @ConditionalOnExpression:SpEL 表达式结果为 true 时

  • @ConditionalOnProperty:参数设置或者值一致时起效

  • @ConditionalOnResource:指定的文件存在时起效

  • @ConditionalOnJndi:指定的 JNDI 存在时起效

  • @ConditionalOnJava:指定的 Java 版本存在时起效

  • @ConditionalOnWebApplication:Web 应用环境下起效

  • @ConditionalOnNotWebApplication:非 Web 应用环境下起效

7. @Import 支持导入的三种方式

  1. 带有 @Configuration 的配置类

  2. ImportSelector 的实现

  3. ImportBeanDefinitionRegistrar 的实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值