✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人
个人主页:Leo的博客
当前专栏:每天一个知识点
✨特色专栏: MySQL学习
本文内容:第05天 SpringBoot自动配置原理
️个人小站 :个人博客,欢迎大家访问
个人知识库: 知识库,欢迎大家访问
自从有了 SpringBoot 之后,咋们就起飞了!各种零配置开箱即用,而我们之所以开发起来能够这么爽,自动配置的功劳少不了,今天我们就一起来讨论一下 SpringBoot 自动配置原理。
Spring Boot的自动配置是通过@EnableAutoConfiguration注解实现的。当该注解被标记在一个类上时,Spring Boot就会根据应用程序中所引入的依赖,自动配置应用程序所需的Bean、服务和其他组件。
Spring Boot自动配置是指,在Spring Boot中,通过一些规则来自动配置应用程序所需的Bean、服务和其他组件。这种自动配置的方式可以大大减少开发人员的工作量,因为他们不需要手动配置每个组件,而只需要在应用程序中引入所需的模块即可。
一切的来自起源SpringBoot的启动类,我们发现main方法上面有个注解:@SpringBootApplication
package com.leo.demo02;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
/**
* @author : Leo
* @description : 启动类
* @date 2023/8/10 11:22
*/
@SpringBootApplication
@MapperScan("com.leo.demo02.mapper")
public class Application
{
public static void main(String[] args)
{
ConfigurableApplicationContext context = SpringApplication.run(Application.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个注解作用
我们继续点@SpringBootConfiguration
进去查看源码如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
@Configuration
标注在某个类上,表示这是一个 springboot的配置类
。可以向容器中注入组件。
@ComponentScan
:配置用于 Configuration 类的组件扫描指令。Spring XML
的
元素并行的支持。basePackageClasses
或basePackages
来定义要扫描的特定包。 如果没有定义特定的包,将从声明该注解的类的包开始扫描
。@ComponentScan
:配置用于 Configuration 类的组件扫描指令。Spring XML
的
元素并行的支持。basePackageClasses
或basePackages
来定义要扫描的特定包。 如果没有定义特定的包,将从声明该注解的类的包开始扫描
。@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 {};
}
@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
执行逻辑来决定是如何导入的。
@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.leo.Applicaiton
:当前启动类所在的包名
结论: @AutoConfigurationPackage 就是将主配置类(@SpringBootApplication 标注的类)所在的包下面所有的组件都扫描注册到 spring 容器中。
作用:AutoConfigurationImportSelector开启自动配置类的导包的选择器
,即是带入哪些类,有选择性的导入
点AutoConfigurationImportSelector.class进入查看源码,这个类中有两个方法见名知意:
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());
}
}
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数组长度为127,并且文件后缀名都为 **AutoConfiguration
结论: 这些都是候选的配置类,经过去重,去除需要的排除的依赖,最终的组件才是这个环境需要的所有组件。有了自动配置,就不需要我们自己手写配置的值了,配置类有默认值的。
我们继续往下看看是如何返回需要配置的组件的
方法如下:
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()方法要找到自动的配置类返回才不会报错。
我们点进去发现:this.getSpringFactoriesLoaderFactoryClass()返回的是EnableAutoConfiguration.class
这个注解。这个注解和@SpringBootApplication下标识注解是同一个注解。
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
结论:获取一个能加载自动配置类的类,即SpringBoot默认自动配置类为EnableAutoConfiguration
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());
}
先是将 EnableAutoConfiguration.class
传给了 factoryType
然后String factoryTypeName = factoryType.getName();
,所以factoryTypeName
值为 org.springframework.boot.autoconfigure.EnableAutoConfiguration
接着查看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.
* 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文件都会被读取,如:
该方法作用是加载所有依赖的路径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值,如果没有就使用默认值空数组
结论:
导入starter-web
:导入了web开发场景
starter-json
、starter-tomcat
、springmvc
spring-boot-starter
,核心场景启动器。spring-boot-autoconfigure
包。spring-boot-autoconfigure
里面囊括了所有场景的所有配置。spring-boot-autoconfigure
下写好的所有配置类。(这些配置类给我们做了整合操作),默认只扫描主程序所在的包。2、主程序:@SpringBootApplication
@SpringBootApplication
由三个注解组成@SpringBootConfiguration
、@EnableAutoConfiguratio
、@ComponentScan
SpringBoot默认只能扫描自己主程序所在的包及其下面的子包,扫描不到 spring-boot-autoconfigure
包中官方写好的配置类
**@EnableAutoConfiguration**
:SpringBoot 开启自动配置的核心。
@Import(AutoConfigurationImportSelector.class)
提供功能:批量给容器中导入组件。spring-boot-autoconfigure
下 META-INF/spring/**org.springframework.boot.autoconfigure.AutoConfiguration**.imports
文件指定的autoconfigure
包下的142 xxxxAutoConfiguration
类导入进来(自动配置类)142
个自动配置类按需生效:
142
个自动配置类都能生效@ConditionalOnxxx
,只有条件成立,才能生效3、**xxxxAutoConfiguration**
自动配置类
@EnableConfigurationProperties(**ServerProperties**.class)
,用来把配置文件中配的指定前缀的属性值封装到 xxxProperties
属性类中server
开头的。配置都封装到了属性类中。**xxxProperties**
。**xxxProperties**
都是和配置文件绑定。**4、**写业务,全程无需关心各种整合(底层这些整合写好了,而且也生效了)
核心流程总结:
1、导入starter
,就会导入autoconfigure
包。
2、autoconfigure
包里面 有一个文件 META-INF/spring/**org.springframework.boot.autoconfigure.AutoConfiguration**.imports
,里面指定的所有启动要加载的自动配置类
3、@EnableAutoConfiguration 会自动的把上面文件里面写的所有自动配置类都导入进来。xxxAutoConfiguration 是有条件注解进行按需加载
4、xxxAutoConfiguration
给容器中导入一堆组件,组件都是从 xxxProperties
中提取属性值
5、xxxProperties
又是和配置文件进行了绑定
**效果:**导入starter
、修改配置文件,就能修改底层行为。