SpringBoot自动配置源码解析

SpringBoot自动配置源码解析

Spring Boot 简化了配置,但日志管理依然需要重视。日志配置、链路追踪、排查思路都是日常开发中会遇到的问题。本文讲实际项目中的日志管理经验。

入口:@SpringBootApplication

@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 {
}

三个核心注解

  1. @SpringBootConfiguration:标记配置类
  2. @EnableAutoConfiguration:启用自动配置
  3. @ComponentScan:组件扫描

@EnableAutoConfiguration 解析

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
}

关键@Import(AutoConfigurationImportSelector.class)

AutoConfigurationImportSelector

#

类结构

public class AutoConfigurationImportSelector implements 
DeferredImportSelector, // 延迟导入,在@Configuration处理完后执行
BeanClassLoaderAware,
ResourceLoaderAware,
BeanFactoryAware,
EnvironmentAware,
Ordered {

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 检查是否禁用自动配置
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}

// 获取自动配置条目
AutoConfigurationEntry autoConfigurationEntry =
getAutoConfigurationEntry(annotationMetadata);

return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}

#

getAutoConfigurationEntry

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata metadata) {
// 1. 检查是否禁用
if (!isEnabled(metadata)) {
return EMPTY_ENTRY;
}

// 2. 获取注解属性
AnnotationAttributes attributes = getAttributes(metadata);

// 3. 获取候选配置列表
List<String> configurations = getCandidateConfigurations(metadata, attributes);

// 4. 去重
configurations = removeDuplicates(configurations);

// 5. 获取排除项
Set<String> exclusions = getExclusions(metadata, attributes);

// 6. 检查排除项合法性
checkExcludedClasses(configurations, exclusions);

// 7. 移除排除项
configurations.removeAll(exclusions);

// 8. 按条件过滤(核心)
configurations = getConfigurationClassFilter().filter(configurations);

// 9. 触发自动配置导入事件
fireAutoConfigurationImportEvents(configurations, exclusions);

return new AutoConfigurationEntry(configurations, exclusions);
}

#

getCandidateConfigurations

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, 
AnnotationAttributes attributes) {
// 从 META-INF/spring.factories 加载
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;
}

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}

#

SpringFactoriesLoader

public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

public static List<String> loadFactoryNames(Class<?> factoryType, ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

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, k -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
} catch (IOException ex) {
throw new IllegalArgumentException(...);
}

cache.put(classLoader, result);
return result;
}
}

条件过滤

#

ConfigurationClassFilter

protected final ConfigurationClassFilter getConfigurationClassFilter() {
if (this.configurationClassFilter == null) {
List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
for (AutoConfigurationImportFilter filter : filters) {
invokeAwareMethods(filter);
}
this.configurationClassFilter = new ConfigurationClassFilter(
this.beanClassLoader, filters);
}
return this.configurationClassFilter;
}

#

OnClassCondition

@Order(Ordered.HIGHEST_PRECEDENCE)
public class OnClassCondition extends FilteringSpringBootCondition {

@Override
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
// 分批检查 @ConditionalOnClass
// 使用多线程加速
}

private ConditionOutcome getOutcome(String candidates) {
try {
// 检查类是否存在
for (String candidate : StringUtils.commaDelimitedListToStringArray(candidates)) {
Class.forName(candidate, false, getClass().getClassLoader());
}
return ConditionOutcome.match();
} catch (ClassNotFoundException ex) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(...));
}
}
}

自动配置类示例

#

DataSourceAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.InitializationSpecificCredentialsDataSourceInitializationConfiguration.class,
DataSourceInitializationConfiguration.SharedCredentialsDataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {

@Configuration(proxyBeanMethods = false)
@Conditional(EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedDatabaseConfiguration {
}

@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class,
DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class,
DataSourceConfiguration.Generic.class,
DataSourceConfiguration.OracleUcp.class })
protected static class PooledDataSourceConfiguration {
}

@Bean
@ConditionalOnMissingBean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
return transactionManager;
}
}

自动配置报告

#

AutoConfigurationReportListener

public class AutoConfigurationReportListener implements 
ApplicationListener<AutoConfigurationImportEvent> {

@Override
public void onApplicationEvent(AutoConfigurationImportEvent event) {
// 记录匹配和不匹配的配置类
// 用于生成自动配置报告
}
}

#

查看报告

# DEBUG 日志
logging.level.org.springframework.boot.autoconfigure=DEBUG

# 条件报告
java -jar app.jar --debug

自定义条件注解

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnCustomCondition.class)
public @interface ConditionalOnCustom {
String value();
}

public class OnCustomCondition extends SpringBootCondition {

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(
ConditionalOnCustom.class.getName());
String value = (String) attributes.get("value");

String property = context.getEnvironment().getProperty("my.custom." + value);
if (property != null) {
return ConditionOutcome.match();
}

return ConditionOutcome.noMatch("Property my.custom." + value + " not found");
}
}

加载流程总结

SpringApplication.run()
|
v
refreshContext()
|
v
AbstractApplicationContext.refresh()
|
├── invokeBeanFactoryPostProcessors()
│ └── ConfigurationClassPostProcessor.processConfigBeanDefinitions()
│ └── parser.parse(candidates)
│ └── 处理 @Import
│ └── AutoConfigurationImportSelector
│ ├── loadFactoryNames()
│ │ └── 读取 spring.factories
│ ├── 去重
│ ├── 排除
│ └── filter()
│ └── OnClassCondition
│ └── Class.forName()

└── 注册过滤后的配置类为 BeanDefinition

总结

Spring Boot 自动配置的源码设计精妙:

  1. spring.factories:SPI 机制,声明式注册
  2. DeferredImportSelector:延迟加载,保证 @Configuration 先处理
  3. 条件注解:按需加载,避免不必要的 Bean
  4. 条件评估缓存:启动时缓存结果,加速后续启动

理解自动配置源码,可以更好地开发自定义 Starter 和排查配置问题。

核心要点

  1. 日志级别设置:根据环境设置合适的级别

  2. 日志格式配置:添加 traceId 便于链路追踪

  3. 日志输出:控制台输出和文件输出的配置

  4. 日志归档:设置滚动策略和保留时间

总结

日志是排查问题的生命线,合理配置日志可以提升排查效率。在实际项目中,结合 ELK 等工具搭建日志系统,可以更好地管理和分析日志。


   转载规则


《SpringBoot自动配置源码解析》 小乐 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录