大家好,我是
方圆
不废话,直入正题!
目录
1. SpringBoot的核心注解是哪个?
启动类上的注解为@SpringBootApplication
,它由@SpringBootConfiguration
,@EnableAutoConfiguration
,@ComponentScan
组成。
“春天里的瑞士军刀”,《Spring实战》(一)
2. SpringBoot自动配置的原理
2.1 绕不开的@EnableAutoConfiguration
2.1.1 @AutoConfigurationPackage
被该注解修饰的包,应该在AutoConfigurationPackages
中注册
咱从一开始学 SpringBoot 就知道一件事:主启动类必须放在所有自定义组件的包的最外层
,以保证Spring能扫描到它们。由此可知是它起的作用。
进入AutoConfigurationPackages.Registrar,其中有一个register方法
如下
private static final String BEAN = AutoConfigurationPackages.class.getName();
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
// 判断 BeanFactory 中是否包含 AutoConfigurationPackages
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
// addBasePackages:添加根包扫描包
constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
}
else {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(BasePackages.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}
它的作用就是获取主启动类所在包及其子包下的组件,也解释了启动器类在最外层的原因
2.2 @Import(AutoConfigurationImportSelector.class)
DeferredImportSelector
处理自动配置。如果需要自定义扩展 @EnableAutoConfiguration,则也可以编写该类的子类。
DeferredImportSelector
的执行时机,是在 @Configuration
注解中的其他逻辑被处理完毕之后(包括对 @ImportResource、@Bean 这些注解的处理)再执行,换句话说,DeferredImportSelector
的执行时机比 ImportSelector
更晚。
回到 AutoConfigurationImportSelector
,它的核心部分,就是 ImportSelector 的 selectImport 方法
:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
// 加载自动配置类
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
我们再进入getAutoConfigurationEntry
方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 【核心】加载候选的自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
在向下进入getCandidateConfigurations
方法,其中getSpringFactoriesLoaderFactoryClass()传入的便是@EnableAutoConfiguration
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// SPI机制加载自动配置类
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;
}
再进入loadFactoryNames
方法
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
// ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
源码中使用 classLoader 去加载了指定常量路径下的资源: FACTORIES_RESOURCE_LOCATION
,而这个常量指定的路径实际是:META-INF/spring.factories
2.3 这些jar包的位置
它的位置如下,我们可以通过Maven导入的依赖,找到它的位置
spring.factories
文件中,内容如下
它都是一些以xxxAutoConfiguration
的形式结尾JavaConfig配置类
2.3 自动配置的生效
我们以WebServicesAutoConfiguration
为例来说明,我们可以从下图中看见有@EnableConfigurationProperties
注解修饰,该注解有(WebServicesProperties.clss)
我们进去看看WebServicesProperties(xxxProperties)
,如下
我们很快的发现有一个@ConfigurationProperties
的注解,其中写了前缀的格式,那么我们看右边application.properties
配置文件中,我们可以根据它的前缀标识,来对配置类中的字段属性进行修改。
- 我们屡一下思路,
@ConfigurationProperties
实现的就是将配置文件中的属性绑定到对应的bean上,@EnableConfigurationProperties
负责将绑定了属性的配置类,放到spring容器中,真正控制着配置类属性的是xxxProperties.class
2.4 翻译成口语我们该怎么说
@EnableAutoConfiguration
注解是一个派生注解,它包括@AutoConfigurationPackage
,它的作用是获取主启动类所在包及其子包下的组件,也解释了启动器类在最外层的原因,还有@Import注解导入了AutoConfigurationImportSelector.class
,其中有selectImport方法,它会调用loadFactoryNames方法,进入META-INF目录下的spring.factories文件
,对其进行加载,这些配置类命名形式都是结尾都是AutoConfiguration,它们都是JavaConfig的形式,通过@EnableConfigurationProperties
注解修饰,可以看见xxxProperties结尾的类获取配置文件的属性,而XxxxProperties类是通过与全局配置文件中对应的属性进行绑定的。(配置类上还有Condition条件)
Spring Boot面试杀手锏————自动配置原理
3. Spring-boot-starter-parent有什么用?
我们创建一个SpringBoot的项目,其中默认都是有parent,这个parent就是Spring-boot-starter-parent,它有以下作用
- 继承自spring-boot-dependencies,这个里边定义了依赖的版本,也正是因为继承了这个依赖,所以我们在写依赖时才不需要写版本号。
- 针对 application.properties 和 application.yml 的资源过滤,包括通过 profile 定义的不同环境的配置文件,例如 application-dev.properties 和 application-dev.yml
加油儿!