做网站哪个好,单位网站建设情况调查情况,在哪里可以看直播免费的,wordpress资讯类主题✅作者简介#xff1a;大家好#xff0c;我是Leo#xff0c;热爱Java后端开发者#xff0c;一个想要与大家共同进步的男人#x1f609;#x1f609; #x1f34e;个人主页#xff1a;Leo的博客 #x1f49e;当前专栏#xff1a;每天一个知识点 ✨特色专栏#xff1a…
✅作者简介大家好我是Leo热爱Java后端开发者一个想要与大家共同进步的男人 个人主页Leo的博客 当前专栏每天一个知识点 ✨特色专栏 MySQL学习 本文内容第05天 SpringBoot自动配置原理 ️个人小站 个人博客欢迎大家访问 个人知识库 知识库欢迎大家访问
首先看下一下自动配置的整个流程图
自从有了 SpringBoot 之后咋们就起飞了各种零配置开箱即用而我们之所以开发起来能够这么爽自动配置的功劳少不了今天我们就一起来讨论一下 SpringBoot 自动配置原理。 Spring Boot的自动配置是通过EnableAutoConfiguration注解实现的。当该注解被标记在一个类上时Spring Boot就会根据应用程序中所引入的依赖自动配置应用程序所需的Bean、服务和其他组件。 1. Spring Boot自动配置的概念 Spring Boot自动配置是指在Spring Boot中通过一些规则来自动配置应用程序所需的Bean、服务和其他组件。这种自动配置的方式可以大大减少开发人员的工作量因为他们不需要手动配置每个组件而只需要在应用程序中引入所需的模块即可。 2. 逐步分析
2.1 SpringBootApplication
一切的来自起源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个注解作用
2.2 SpringBootConfiguration
我们继续点SpringBootConfiguration进去查看源码如下
Target({ElementType.TYPE})
Retention(RetentionPolicy.RUNTIME)
Documented
Configuration
Indexed
public interface SpringBootConfiguration {AliasFor(annotation Configuration.class)boolean proxyBeanMethods() default true;
}
Configuration标注在某个类上表示这是一个 springboot的配置类。可以向容器中注入组件。
2.3 ComponentScan
ComponentScan配置用于 Configuration 类的组件扫描指令。提供与 Spring XML 的 context:component-scan 元素并行的支持。可以 basePackageClasses 或basePackages 来定义要扫描的特定包。 如果没有定义特定的包将从声明该注解的类的包开始扫描。
2.4 EnableAutoConfiguration
ComponentScan配置用于 Configuration 类的组件扫描指令。提供与 Spring XML 的 context:component-scan 元素并行的支持。可以 basePackageClasses 或basePackages 来定义要扫描的特定包。 如果没有定义特定的包将从声明该注解的类的包开始扫描。
3. 自动配置
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 {};
}
3.1 AutoConfigurationPackage
自动导入配置包点进去查看代码
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 SetObject 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 容器中。
3.2 Import({AutoConfigurationImportSelector.class})
作用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);// 这打个断点看看 返回的数据ListString configurations this.getCandidateConfigurations(annotationMetadata, attributes);//删除重复项configurations this.removeDuplicates(configurations);SetString 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
结论 这些都是候选的配置类经过去重去除需要的排除的依赖最终的组件才是这个环境需要的所有组件。有了自动配置就不需要我们自己手写配置的值了配置类有默认值的。
我们继续往下看看是如何返回需要配置的组件的
1. getCandidateConfigurations(annotationMetadata, attributes)
方法如下 protected ListString getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {ListString 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方法要找到自动的配置类返回才不会报错。
2. getSpringFactoriesLoaderFactoryClass()
我们点进去发现this.getSpringFactoriesLoaderFactoryClass()返回的是EnableAutoConfiguration.class这个注解。这个注解和SpringBootApplication下标识注解是同一个注解。
protected Class? getSpringFactoriesLoaderFactoryClass() {return EnableAutoConfiguration.class;
}
protected Class? getSpringFactoriesLoaderFactoryClass() {return EnableAutoConfiguration.class;
}结论获取一个能加载自动配置类的类即SpringBoot默认自动配置类为EnableAutoConfiguration
loadFactoryNames()
public static ListString 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()
接着查看loadSpringFactories方法的作用
private static MapString, ListString loadSpringFactories(ClassLoader classLoader) {//断点查看MapString, ListString result cache.get(classLoader);if (result ! null) {return result;}result new HashMap();try {//注意这里META-INF/spring.factoriesEnumerationURL 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.* pCan 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值如果没有就使用默认值空数组
结论
loadSpringFactories()该方法就是从“META-INF/spring.factories”中加载给定类型的工厂实现的完全限定类名放到map中loadFactoryNames()是根据SpringBoot的启动生命流程当需要加载自动配置类时就会传入org.springframework.boot.autoconfigure.EnableAutoConfiguration参数从map中查找key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值这些值通过反射加到容器中之后的作用就是用它们来做自动配置这就是Springboot自动配置开始的地方只有这些自动配置类进入到容器中以后接下来这个自动配置类才开始进行启动当需要其他的配置时如监听相关配置listenter就传不同的参数获取相关的listenter配置。
4. 自动配置流程细节梳理
导入starter-web导入了web开发场景
1、场景启动器导入了相关场景的所有依赖starter-json、starter-tomcat、springmvc2、每个场景启动器都引入了一个spring-boot-starter核心场景启动器。3、核心场景启动器引入了spring-boot-autoconfigure包。4、spring-boot-autoconfigure里面囊括了所有场景的所有配置。5、只要这个包下的所有类都能生效那么相当于SpringBoot官方写好的整合功能就生效了。6、SpringBoot默认却扫描不到 spring-boot-autoconfigure下写好的所有配置类。这些配置类给我们做了整合操作默认只扫描主程序所在的包。
2、主程序SpringBootApplication SpringBootApplication由三个注解组成SpringBootConfiguration、EnableAutoConfiguratio、ComponentScan SpringBoot默认只能扫描自己主程序所在的包及其下面的子包扫描不到 spring-boot-autoconfigure包中官方写好的配置类 **EnableAutoConfiguration**SpringBoot 开启自动配置的核心。 是由Import(AutoConfigurationImportSelector.class)提供功能批量给容器中导入组件。SpringBoot启动会默认加载 142个配置类。这142个配置类来自于spring-boot-autoconfigure下 META-INF/spring/**org.springframework.boot.autoconfigure.AutoConfiguration**.imports文件指定的项目启动的时候利用 Import 批量导入组件机制把 autoconfigure 包下的142 xxxxAutoConfiguration类导入进来自动配置类虽然导入了142个自动配置类 按需生效 并不是这142个自动配置类都能生效每一个自动配置类都有条件注解ConditionalOnxxx只有条件成立才能生效
3、**xxxxAutoConfiguration**自动配置类
给容器中使用Bean 放一堆组件。每个自动配置类都可能有这个注解EnableConfigurationProperties(**ServerProperties**.class)用来把配置文件中配的指定前缀的属性值封装到 xxxProperties属性类中以Tomcat为例把服务器的所有配置都是以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、修改配置文件就能修改底层行为。
5. 常用的Conditional注解
在加载自动配置类的时候并不是将spring.factories的配置全部加载进来而是通过Conditional等注解的判断进行动态加载Conditional其实是spring底层注解意思就是根据不同的条件来进行自己不同的条件判断如果满足指定的条件那么配置类里边的配置才会生效。常用的Conditional注解 ConditionalOnClass classpath中存在该类时起效ConditionalOnMissingClass classpath中不存在该类时起效ConditionalOnBean DI容器中存在该类型Bean时起效ConditionalOnMissingBean DI容器中不存在该类型Bean时起效ConditionalOnSingleCandidate DI容器中该类型Bean只有一个或Primary的只有一个时起效ConditionalOnExpression SpEL表达式结果为true时ConditionalOnProperty 参数设置或者值一致时起效ConditionalOnResource 指定的文件存在时起效ConditionalOnJndi 指定的JNDI存在时起效ConditionalOnJava 指定的Java版本存在时起效ConditionalOnWebApplication Web应用环境下起效ConditionalOnNotWebApplication 非Web应用环境下起效