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

原创
09/17 13:27
阅读数 99

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

推荐B站视频

b站最强新版Springboot教程 全程有废话我直播吃纸

程序员就是我呀


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 的实现类必须携带一个不带参数的构造方法;

文件中的内容:(实现类的路径)cn.tx.service.impl.AliPay


3.Spring Boot中的SPI机制

1) spring.factories

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

2) spring.factories实现原理

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

public static List<String> loadFactoryNames(Class<?> factoryClass, @NullableClassLoader 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");//定义存储全量工厂类的 mapLinkedMultiValueMap result = new LinkedMultiValueMap();//遍历 urlswhile(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 TRACElevel* is allowed.*/private boolean logRequestDetails;

7.总结

1).SpringBoot 启动会加载大量的自动配置类

2).我们看我们需要的功能有没有 SpringBoot 默认写好的自动配置类;

3).我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)

4).给容器中自动配置类添加组件的时候,会从 properties 类中获取某些属性。我们就可以在配置文件中指定这些属性


本文分享自微信公众号 - WHICH工作室(which_cn)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
打赏
0
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
0
分享
返回顶部
顶部