【SpringBoot新手篇】SpringBoot 自动配置原理

SPI机制

SPI机制简介

SPI全称为 (Service Provider Interface),翻译过来就是服务提供者的接口,是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制,一种解耦非常优秀的思想。

SPI的工作原理: 就是ClassPath路径下的META-INF/services文件夹中, 以接口的全限定名来命名文件名,文件里面写该接口的实现。然后再资源加载的方式,读取文件的内容(接口实现的全限定名), 然后再去加载类。

SPI可以很灵活的让接口和实现分离, 让api提供者只提供接口, 第三方来实现。

在这里插入图片描述
优点:

  • 使用Java SPI机制的优势是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。

缺点:

  • 虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。
  • 多个并发多线程使用ServiceLoader类的实例是不安全的。

SPI机制使用

在这里插入图片描述

public interface Pay {
    String payType();

    void doPay();
}

在这里插入图片描述
创建META-INF/services文件夹,然后创建以Pay接口全限定名为名字的文件,文件内容为实现类的全路径名cn.zysheep.AliPaySdkImpl

public class AliPaySdkImpl implements Pay{
    @Override
    public String payType() {
        return "ali";
    }

    @Override
    public void doPay() {
        System.out.println("阿里支付开始......");
        System.out.println("阿里支付生成流水号......");
        System.out.println("阿里支付结束......");
    }
}

在这里插入图片描述
创建META-INF/services文件夹,然后创建以Pay接口全限定名为名字的文件,文件内容为实现类的全路径名cn.zysheep.WxPaySdkImpl

public class WxPaySdkImpl implements Pay{
    @Override
    public String payType() {
        return "wx";
    }

    @Override
    public void doPay() {
        System.out.println("微信支付开始......");
        System.out.println("微信支付生成流水号......");
        System.out.println("微信支付结束......");
    }
}

在这里插入图片描述

public class PayTest {
    public static void main(String[] args) {
        ServiceLoader<Pay> serviceLoader = ServiceLoader.load(Pay.class);
        for (Pay pay : serviceLoader) {
            System.out.println("检测到实现类:"+ pay.getClass().getSimpleName());

            if ("ali".equals(pay.payType())) {
                pay.doPay();
            }
        }
    }
}

SPI机制在框架中的使用

日志框架中使用

市面上常见的日志框架有很多。通常情况下,日志是由一个抽象层+实现层的组合来搭建的,而用户通常来说不应该直接使用具体的日志实现类,应该使用日志的抽象层。

1、SLF4J + Logback实现
在这里插入图片描述

2、SLF4J + Log4j2实现在这里插入图片描述

数据库驱动中使用

1、mysql驱动

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.23</version>
</dependency>

在这里插入图片描述
2、oracle驱动

<dependency>
    <groupId>com.oracle.database.jdbc</groupId>
    <artifactId>ojdbc8</artifactId>
    <version>21.3.0.0</version>
</dependency>

在这里插入图片描述

SpringBoot框架中使用

SpringBoot自动配置也使用到了SPI的思想。和JDK中的原理相同。

工具类不同:

  • JDK使用的工具类是ServiceLoader
  • SpringBoot中使用的类是SpringFactoriesLoader

文件路径不同

  • JDK配置在 META-INF/services文件夹,然后创建以接口全限定名为名字的文件,文件内容为实现类的全路径名
  • SpringBoot配置放在 META-INF/spring.factories
  1. Spring Boot默认扫描启动类所在的包下的主类与子类的所有组件,但并没有包括依赖包中的类,那么依赖包中的bean是如何被发现和加载的?
  2. 配置文件到底能写什么?怎么写?

常用配置

这里指的配置主要是application.yml或者application.properties修改默认属性的文件

server.port=9090 # 服务端口号
server.tomcat.uri-encoding=UTF-8 #以Tomcat为web容器时的字符编码
spring.data.mongodb.uri=mongodb://localhost:27017/mydb #mongodb连接

spring.application.name=customer # 应用名称,一般就是项目名称,这个名称在SpringCloud中比较关键
spring.profiles.active=dev #指定当前的活动配置文件,主要用于多环境多配置文件的应用中
spring.http.encoding.charset=UTF-8 #http请求的字符编码
spring.http.multipart.max-file-size=10MB #设置文件上传时单个文件的大小限制
spring.http.multipart.max-request-size=100MB #设置文件上传时总文件大小限制

spring.thymeleaf.prefix=classpath:/templates/ #配置在使用Thymeleaf做页面模板时的前缀,即页面所在路径
spring.thymeleaf.suffix=.html #设置在使用Thymeleaf做页面模板时的后缀
spring.thymeleaf.cache=false #设置在使用Thymeleaf做页面模板时是否启用缓存

spring.mvc.static-path-pattern=/** #设置静态资源的请求路径
spring.resources.static-locations=classpath:/static/,classpath:/public/ #指定静态资源的路径

##以下是使用MySQL数据库的配置
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect #指定数据库方言
hibernate.show_sql=true #是否显示sql语句
hibernate.hbm2dll.auto=update #设置使用Hibernate的自动建表方式
entitymanager.packagesToScan=com.zslin #设置自动扫描的包前缀

spring.datasource.url=jdbc:mysql://localhost:3306/customer?\
useUnicode=true&characterEncoding=utf-8&useSSL=true&autoReconnect=true #数据库链接
spring.datasource.username=root #数据库用户名
spring.datasource.password=123 #数据库用户对应的密码
spring.datasource.driver-class-name=com.mysql.jdbc.Driver #数据库驱动名称

自动配置原理

在这里插入图片描述

  1. 先看@SpringBootApplication
  2. @SpringBootConfiguration:组合注解,标记当前类为配置类
  3. @EnableAutoConfiguration:开启自动配置
  4. @ComponentScan:扫描主类所在的同级包以及子级包里的Bean

关键注解@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 {};
}

自动配置包扫描

@AutoConfigurationPackage : 将主配置类(@SpringBootConfiguration标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器中,所以说,默认情况下主配置类包及子包以外的组件,Spring 容器是扫描不到的。

包扫描问题: 如果是引入的其他jar包,需要加载的bean组件的包路径与配置类的扫描包路径相同则可以扫描到,否则无法扫描到容器。(编译成jar时,同包合并。),一般只有我们自己的maven聚合工程项目才会按照规则创建相同的包,引入第三方jar包时,往往都不一样,我们可以使用下面方式解决:

如下:ruoyi-system引入了ruoyi-common-swaggerjar包,ruoyi-system需要使用swagger相关的组件

在这里插入图片描述
在这里插入图片描述

1、@ComponentScan指定第三方jar包的组件路径,但是@ComponentScan@SpringBootApplication注解的包扫描有冲突,@ComponentScan注解包扫描会覆盖掉@SpringBootApplication的包扫描。解决办法就是在@ComponentScan(basePackages={"com.ruoyi.common.swagger.config","com.ruoyi.system"})的基础上加上@SpringBootApplication扫描的包

2、使用 @Configuration@Bean 注解,必须在com.ruoyi.system包下创建,保证被启动类包扫描到。部分代码不完整

@Configuration
public class SwaggerAutoConfiguration
{

    @Bean
    public Docket api(SwaggerProperties swaggerProperties)
    {
        ApiSelectorBuilder builder = new Docket(DocumentationType.SWAGGER_2).host(swaggerProperties.getHost())
                .apiInfo(apiInfo(swaggerProperties)).select()
                .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()));

        return builder.build().securitySchemes(securitySchemes()).securityContexts(securityContexts()).pathMapping("/");
    }
}

3、使用@Import方法,启动类添加自定义主键

@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({ SwaggerAutoConfiguration.class })
public @interface EnableCustomSwagger2
{

}

加载自动配置项

而通过@Import(AutoConfigurationImportSelector.class)导入配置类的功能;
AutoConfigurationImportSelector中的方法selectImports()调用getAutoConfigurationEntry()在调用getCandidateConfigurations()loadFactoryNames(),得到待配置的class的类名集合,这个集合就是所有需要进行自动配置的类,而是否自动配置的关键在于META-INF/spring.factories·文件中是否存在该配置信息

loadFactoryNames() 中关键的三步:

  1. 从当前项目的类路径中获取所有 META-INF/spring.factories 这个文件下的信息。
  2. 将上面获取到的信息封装成一个 Map 返回。
  3. 从返回的 Map 中通过刚才传入的 EnableAutoConfiguration.class 参数,获取该 key 下的所有值。

在这里插入图片描述

总结 :自动配置调用链

@SpringBootApplication--->@EnableAutoConfiguration--->
@Import(AutoConfigurationImportSelector.class)--->
selectImports()--->getAutoConfigurationEntry()--->
getCandidateConfigurations()--->loadFactoryNames()

在这里插入图片描述

打开META-INF/spring.factories文件,如下图可以看到所有需要配置的类全路径都在文件中,每行一个配置,多个类名逗号分隔,而\表示忽略换行

在这里插入图片描述

DataSourceAutoConfiguration类来看其主要构成部分
在这里插入图片描述

@Configuration(proxyBeanMethods = false) 
// 声明为一个配置类
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
// 当类路径下存在DataSource和EmbeddedDatabaseType类,才会实例化这个Bean
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
// ioc容器中没有指定的Bean,才会实例化这个Bean
@EnableConfigurationProperties(DataSourceProperties.class)
// 开启配置属性绑定,并把DataSourceProperties注册到ioc容器中
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class })
// 导入一个配置类
public class DataSourceAutoConfiguration {}

都能看到各种各样的条件判断注解,满足条件时就加载这个Bean并实例化,此类的条件注解是:@ConditionalOnProperty

@ConditionalOnBean:当容器里有指定Bean的条件下
@ConditionalOnClass:当类路径下有指定的类的条件下
@ConditionalOnExpression:基于SpEL表达式为true的时候作为判断条件才去实例化
@ConditionalOnJava:基于JVM版本作为判断条件
@ConditionalOnJndi:在JNDI存在的条件下查找指定的位置
@ConditionalOnMissingBean:当容器里没有指定Bean的情况下
@ConditionalOnMissingClass:当容器里没有指定类的情况下
@ConditionalOnWebApplication:当前项目时Web项目的条件下
@ConditionalOnNotWebApplication:当前项目不是Web项目的条件下
@ConditionalOnProperty:指定的属性是否有指定的值
@ConditionalOnResource:类路径是否有指定的值
@ConditionalOnOnSingleCandidate:当指定Bean在容器中只有一个,或者有多个但是指定首选的Bean

这些注解都组合了@Conditional注解,只是使用了不同的条件组合最后为true时才会去实例化需要实例化的类,否则忽略
这种spring4.X带来的动态组合很容易后期配置,从而避免了硬编码,使配置信息更加灵活多变,同时也避免了不必要的意外异常报错。使用的人只要知道配置的条件即可也不用去阅读源码,方便快捷,这也是sprignboot快捷方式带来的好处

参考HttpEncodingAutoConfiguration配置信息如下

@Configuration
@EnableConfigurationProperties(HttpEncodingProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {}

@Configuration //标明为配置类
@EnableConfigurationProperties(HttpEncodingProperties.class) //开启配置属性绑定,并把HttpEncodingProperties注册到容器中
@ConditionalOnClass(CharacterEncodingFilter.class)//当CharacterEncodingFilter在类路径的条件下
@ConditionalOnProperty(prefix = "spring.http.encoding", value = “enabled”, matchIfMissing = true)//当spring.http.encoding=enabled的情况下,如果没有设置则默认为true,即条件符合
@ConditionalOnMissingBean //当ioc容器中没有这个Bean时新建Bean

所以说配置文件能写什么取决与xxxAutoConfiguration自动配置
通过@EnableConfigurationProperties(xxxProperties.class)注解开启xxxProperties类属性配置绑定,xxxProperties配置属性类@ConfigurationProperties(prefix = "spring.datasource")让属性与配置文件进行绑定,所以说属性配置类有什么配置文件就可以写什么。

DataSourceAutoConfiguration:
在这里插入图片描述
DataSourceProperties:
在这里插入图片描述

spring.factories将被弃用

SpringBoot升级到了2.7.0版本,其中有一项是改变原来的自动化配置注册方式;如果你之前写过相关starter类或者研究过自动化配置的源码知道,配置自动化配置类需要在META-INF/spring.factories文件中配置配置类,而最新版本是配置META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中配置,不过目前是兼容两种配置模式共存。

配置方式改变

原配置方式2.7.0版本配置方式
META-INF/spring.factoriesMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@Configuration@AutoConfiguration

springboot2.7.0目前会向后兼容老版本配置模式spring.factories

新注解@AutoConfiguration

新注解@AutoConfiguration是被用在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports中的自动化配置类上用来替换@Configuration@AutoConfigurationAfter@AutoConfigurationBefore注解,其中@Configuration对应的proxyBeanMethods属性值一直为false。

在这里插入图片描述
@AutoConfiguration替换@Configuration@AutoConfigurationAfter@AutoConfigurationBefore注解
在这里插入图片描述

新版本如何做到新老注册方式同时兼容

  • SpringFactoriesLoader用来加载spring.factories配置类
  • ImportCandidates用来加载META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports配置文件;

自动配置总结

Bean的加载

在Spring Boot应用中要让一个普通类交给Spring容器管理,通常有以下方法:

1、使用 @Configuration与@Bean 注解

2、使用@Controller @Service @Repository @Component 注解标注该类并且启用@ComponentScan自动扫描

3、使用@Import 方法

其中Spring Boot实现自动配置使用的是@Import注解这种方式,AutoConfigurationImportSelector类的selectImports方法返回一组从META-INF/spring.factories文件中读取的bean的全类名,这样Spring Boot就可以加载到这些Bean并完成实例的创建工作。

自动配置原理总结

我们可以将自动配置的关键几步以及相应的注解总结如下:

1、@Configuration与@Bean:基于Java代码的bean配置

2、@Conditional:设置自动配置条件依赖

3、@EnableConfigurationProperties与@ConfigurationProperties:读取配置文件转换为bean

4、@EnableAutoConfiguration与@Import:实现bean发现与加载

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

李熠漾

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

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

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

打赏作者

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

抵扣说明:

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

余额充值