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 {}
三个核心注解 :
@SpringBootConfiguration:标记配置类
@EnableAutoConfiguration:启用自动配置
@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 , 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) { if (!isEnabled(metadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(metadata); List<String> configurations = getCandidateConfigurations(metadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(metadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = getConfigurationClassFilter().filter(configurations); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry (configurations, exclusions); }
#
getCandidateConfigurations 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; } 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 { 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) { } 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) { } }
#
查看报告 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 自动配置的源码设计精妙:
spring.factories :SPI 机制,声明式注册
DeferredImportSelector :延迟加载,保证 @Configuration 先处理
条件注解 :按需加载,避免不必要的 Bean
条件评估缓存 :启动时缓存结果,加速后续启动
理解自动配置源码,可以更好地开发自定义 Starter 和排查配置问题。
核心要点
日志级别设置:根据环境设置合适的级别
日志格式配置:添加 traceId 便于链路追踪
日志输出:控制台输出和文件输出的配置
日志归档:设置滚动策略和保留时间
总结 日志是排查问题的生命线,合理配置日志可以提升排查效率。在实际项目中,结合 ELK 等工具搭建日志系统,可以更好地管理和分析日志。