SpringBoot自动配置原理浅析
1. 从@SpringBootApplication注解入手
SpringBoot自动配置来源于@SpringBootApplication注解,该注解为复合注解!
@SpringBootApplication
public class HellowordApplication {
public static void main(String[] args) {
SpringApplication.run(HellowordApplication.class, args);
}
}
@SpringBootApplication该注解下有
@SpringBootConfiguration
:springboot的配置 、
@EnableAutoConfiguration
:自动配置
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
:扫描当前主启动类同级包
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration //springboot的配置
@EnableAutoConfiguration //自动配置
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@SpringBootConfiguration注解
@SpringBootConfiguration注解下为@Configuration:spring的配置,@Configuration下为@Component:说明这也是一个spring的组件
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@EnableAutoConfiguration注解
@EnableAutoConfiguration注解下有@AutoConfigurationPackage
与@Import(AutoConfigurationImportSelector.class)
- @AutoConfigurationPackage:自动配置包
1.1 @Import(AutoConfigurationImportSelector.class):导入选择器包注册 - @Import(AutoConfigurationImportSelector.class):自动配置导入选择,自动导入包的核心
2.1 AutoConfigurationImportSelector:自动导入选择器,里面有环境、资源加载器等
AutoConfigurationImportSelector选择了什么东西
- selectImports():选择组件,加载源数据,选择pom.xml配置的东西
- getAutoConfigurationEntry():获取自动配置的实体
//获取候选的配置
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
- 获取候选配置文件中配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
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;
}
/**
1.SpringFactoriesLoader.loadFactoryNames:获取所有的加载配置 详细见本文的spring工厂的加载
2.getSpringFactoriesLoaderFactoryClass():标注了@EnableAutoConfiguration注解的类,就可以获取这下面的所有配置,就是获取主启动类加载的所有组件 EnableAutoConfiguration.class标注了这个类的所有包、所有配置
3.getBeanClassLoader()→return this.beanClassLoader;当前类的加载器
4.Assert.notEmpty:断言、非空,如果配置不为空就会找META-INF/spring.factories
**/
2. spring.factories文件
META-INF/spring.factories 自动配置的核心文件,里面有很多配置,如果没有的就需要手动配置,所有的自动配置类都在这里了
- #Initializers 初始化的
- #Application Listeners 监听的
- #Auto Configuration Import Listeners 自动配置导入监听器
- #Auto Configuration Import Filters 自动配置导入过滤器
- #Auto Configure 自动配置,如果没有需手动导入
这么多自动配置为什么有的没有生效,需要导入对应的start才能有作用?
因为配置中有@Configuration(proxyBeanMethods = false)与@ConditionalOnXxx,@ConditionalOnXxx为核心注解,如果这里的条件都满足,才会生效
3.SpringFactoriesLoader:spring工厂的加载
//获取所有的加载配置,加载了一个类,即使标注了@SpringBootApplication的这样一个类
//作用就是从META-INF/spring.factories文件中读取指定类对应的类名称列表
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();//工厂的加载
}
String factoryTypeName = factoryType.getName();//获取类的名字
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
/**
枚举去遍历url,url从类加载器中获取所有的资源
获取所有的系统资源,有一个while循环,这个url判断有没有更多的元素,如果有就把它放到url里,然后把url加载到properties里面,最后所有的东西都放到配置类里面去了,所有的资源都加载到配置类中
**/
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
try {
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
//有一个while循环,这个url判断有没有更多的元素,如果有就把它放到url里
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
//把url加载到properties里面
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
}
result.replaceAll((factoryType, implementations) -> {
return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
});
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
4、总结
结论:springboot所有自动配置都是在启动的时候扫描并加载,所有的自动装配类都在spring.factories里面,但是不一定生效,@ConditionalOnXxx注解要判断是否条件成立,只要导入了对应的start,就有对应的启动器了,有了启动器我们自动装配就会生效
- springboot在启动的时候,从类路径下/META-INF/spring.factories获取@EnableAutoConfiguration指定的值;
- 将这些自动配置的类导入容器,自动配置就会生效,帮我们自动配置;
- 以前我们需要自动配置的东西,现在springboot帮我们做了
- 它会把所有需要导入的组件,以全类名的方式返回,这些组件就会被添加到容器
- 容器中也会存在非常多的xxxAutoConfiguration的文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件,并自动配置,@Configuration,JavaConfig
- 有了自动配置类,免去了我们手动编写配置文件的工作
精髓:
- springboot启动会加载大量的自动配置类
- 我们看我i们需要的功能有没有在SpringBoot默认写好的自动配置类当中
- 再看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
- 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性,我们只需要在配置文件中指定这些属性的值即可
- xxxxAutoConfiguration:自动配置类;给容器中添加组件
- xxxxProperties:封装配置文件中相关属性
@Conditional指定的条件成立,才给容器中添加组件,配置里面的所有内容才会生效
@Conditional扩展注解
@ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。
@ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。
@ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。
@ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。
@ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。
@ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。
@ConditionalOnBean:当容器中有指定Bean的条件下进行实例化。
@ConditionalOnMissingBean:当容器里没有指定Bean的条件下进行实例化。
@ConditionalOnClass:当classpath类路径下有指定类的条件下进行实例化。
@ConditionalOnMissingClass:当类路径下没有指定类的条件下进行实例化。
@ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
@ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。
@ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
@ConditionalOnExpression:基于SpEL表达式的条件判断。
@ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
@ConditionalOnResource:当类路径下有指定的资源时触发实例化。
@ConditionalOnJndi:在JNDI存在的条件下触发实例化。
@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。
可以通过启动debug=true属性,使控制台打印