【SpringBoot新手篇】SpringBoot 自动配置原理
- SPI机制
- SPI机制简介
- SPI机制使用
- SPI机制在框架中的使用
- 日志框架中使用
- 数据库驱动中使用
- SpringBoot框架中使用
- 常用配置
- 自动配置原理
- 自动配置包扫描
- 加载自动配置项
- spring.factories将被弃用
- 配置方式改变
- 新注解@AutoConfiguration
- 新版本如何做到新老注册方式同时兼容
- 自动配置总结
- Bean的加载
- 自动配置原理总结
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
中
- Spring Boot默认扫描启动类所在的包下的主类与子类的所有组件,但并没有包括依赖包中的类,那么依赖包中的bean是如何被发现和加载的?
- 配置文件到底能写什么?怎么写?
常用配置
这里指的配置主要是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 #数据库驱动名称
自动配置原理
- 先看
@SpringBootApplication
-
@SpringBootConfiguration
:组合注解,标记当前类为配置类 - @EnableAutoConfiguration:开启自动配置
-
@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()
中关键的三步:
- 从当前项目的类路径中获取所有
META-INF/spring.factories
这个文件下的信息。 - 将上面获取到的信息封装成一个 Map 返回。
- 从返回的
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.factories | META-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发现与加载