SpringBoot自动配置解析——思路最清晰

最近看了一些网上关于自动配置的教程视频和博客,在这里做个总结

推荐B站视频自动配置类讲解

按如下思路:

1.springboot特征:

(1)可以创建独立的Spring应用程序,并且基于其Maven或Gradle插件,可以创建可执行的JARs和 WARs;
(2)内嵌Tomcat或Jetty等Servlet容器;
(3)提供自动配置的“starter”项目对象模型(POMS)以简化Maven配置;
(4)尽可能自动配置Spring容器;
(5)提供准备好的特性,如指标、健康检查和外部化配置;
(6)绝对没有代码生成,不需要XML配置。

2.Java中的SPI思想

1) 简介

SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
  简单的总结下 java SPI 机制的思想。我们系统里抽象的各个模块,往往有很多不同的实现方案。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。
  Java SPI 就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似 IOC 的
思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。

2) Java SPI 规范:

要使用 Java SPI,需要遵循如下约定:
1.当服务提供者提供了接口的一种具体实现后,在 jar 包的 META-INF/services 目录下创建一个以“接口全路径名”为命名的文件,内容为实现类的全限定名;
2.接口实现类所在的 jar 包放在主程序的 classpath 中;
3.主程序通过 java.util.ServiceLoder 动态装载实现模块,它通过扫描 META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到 JVM;
4.SPI 的实现类必须携带一个不带参数的构造方法;
SPI规范
文件中的内容:(实现类的路径)cn.tx.service.impl.AliPay

在这里借用b站某个老师的例子进行描述:假设项目中需要用到支付模块,实现方式有阿里云支付与微信支付,具体的实现方法我们不用去关心,只需要项目写好接口,需要哪种实现方式去引入相应的依赖即可。
在这里插入图片描述

3.Spring Boot中的SPI机制

1) spring.factories

在 Spring 中也有一种类似与 Java SPI 的加载机制。它在 META-INF/spring.factories 文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。这种自定义的 SPI 机制是 Spring Boot Starter 实现的基础。
spring.factories

2) spring.factories实现原理

spring-core 包里定义了 SpringFactoriesLoader 类,这个类实现了检索 META-INF/spring.factories
文件,并获取指定接口的配置的功能。在这个类中定义了两个对外的方法:

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable
ClassLoader classLoader) {
//获得接口名字
String factoryClassName = factoryClass.getName();
//获得所有配置类,并且根据接口名字来获得
return loadSpringFactories(classLoader).getOrDefault(factoryClassName,Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//从缓存中获得 spring.factories 的全量信息
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
//在 classpath 下的所有 jar 包中查找 META-INF/spring.factories 文件
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") :
ClassLoader.getSystemResources("META-INF/spring.factories");
//定义存储全量工厂类的 map
LinkedMultiValueMap result = new LinkedMultiValueMap();
//遍历 urls
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
//加载属性集和
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
//遍历属性键值对的键
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
//获得 key 接口
String factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
//切分并且遍历接口实现类,加入结果集
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location
[META-INF/spring.factories]", var13);
}
}
}

在这个方法中会遍历整个 ClassLoader 中所有 jar 包下的spring.factories 文件。也就是说我们可以在自己的 jar 中配置 spring.factories 文件,不会影响到其它地方的配置,也不会被别人的配置覆盖。在 Spring Boot 的很多包中都能够找到 spring.factories 文件。spring.factories 的是通过 Properties 解析得到的,所以我们在写文件中的内容都是按照下面这种方式配置的:

//如果一个接口希望配置多个实现类,可以使用’,’进行分割。
com.xxx.interface=com.xxx.classname

在 Spring Boot 中,使用的最多的就是 starter。starter 可以理解为一个可拔插式的插件,例如,你想使用 JDBC 插件,那么可以使用 spring-boot-starter-jdbc;如果想使用 MongoDB,可以使用 spring-boot-starter-data-mongodb。

4.自动配置类原理

我们可以发现在 spring-boot-autoconfigure 中的 spring.factories里面保存着 springboot的默认提供的自动配置类。这里只是对自动配置类进行一个归纳。真正创建的类需要去关注@springbootApplication 注解,在springboot启动类的 bean 定义被加载的地方会执行当前的注解。
启动类
进入到@EnableAutoConfiguration 注解
在这里插入图片描述
在这里插入图片描述
@AutoConfigurationImportSelector 是引入自动配置类的位置。

protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
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 = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}

5.HTTP 编码自动配置类概览

以 HttpEncodingAutoConfiguration(Http 编码自动配置)为例解释自动配置原理;

@Configuration // 表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@EnableConfigurationProperties(HttpEncodingProperties.class) // 启动指定类的
ConfigurationProperties 功能;将配置文件中对应的值和 HttpEncodingProperties 绑定起来;并把
HttpEncodingProperties 加入到 ioc 容器中
@ConditionalOnWebApplication //Spring 底层 @Conditional 注解( Spring 注解版),根据不同的条件,如果
满足指定的条件,整个配置类里面的配置就会生效; 判断当前应用是否是 web 应用,如果是,当前配置类生效
@ConditionalOnClass(CharacterEncodingFilter.class) // 判断当前项目有没有这个类
CharacterEncodingFilter;SpringMVC 中进行乱码解决的过滤器;
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing =
true) // 判断配置文件中是否存在某个配置 spring.http.encoding.enabled ;如果不存在,判断也是成立的
// 即使我们配置文件中不配置 pring.http.encoding.enabled=true ,也是默认生效的;
public class HttpEncodingAutoConfiguration {
// 他已经和 SpringBoot 的配置文件映射了
private final HttpEncodingProperties properties;
// 只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
this.properties = properties;
}
@Bean // 给容器中添加一个组件,这个组件的某些值需要从 properties 中获取
@ConditionalOnMissingBean(CharacterEncodingFilter.class) // 判断容器没有这个组件?
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}

6.条件判断

在这里插入图片描述
自动配置类必须在一定的条件下才能生效

@ConfigurationProperties(prefix = "spring.http")
public class HttpProperties {
/**
* Whether logging of (potentially sensitive) request details at DEBUG and TRACE
level
* is allowed.
*/
private boolean logRequestDetails;

7.总结

  • 1).SpringBoot 启动会加载大量的自动配置类
  • 2).我们看我们需要的功能有没有 SpringBoot 默认写好的自动配置类;
  • 3).我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)
  • 4).给容器中自动配置类添加组件的时候,会从 properties 类中获取某些属性。我们就可以在配置文件中指定这些属性
  • 5)主要看xxxAutoConfiguration和xxxProperties这两个类

更多技术博客请关注微信公众号:WHICH工作室
WHICH工作室

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寅贝勒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值