共计 10002 个字符,预计需要花费 26 分钟才能阅读完成。
SpringBoot 深入理解 — @AliasFor 注解的作用
SpringBoot 源码解析 — SpringBoot 启动过程
SpringBoot 源码解析 — AutoConfigure 的实现原理
SpringBoot 源码解析 — @ComponentScan 的实现原理
SpringBoot 源码解析 — @Value,@Autowired 实现原理
SpringBoot 源码解析 — Tomcat,SpringMVC 启动
SpringBoot 源码解析 — Logging,Environment 启动
本文通过阅读 SpringBoot 源码,分享 SpringBoot 中 Logging,Environment 组件的启动过程。
如果大家在使用 SpringBoot 过程中,遇到日志配置无效,Environment 中获取属性错误,希望本文可以给你们一个解决问题的思路。
源码分析基于 spring boot 2.1
Logging
Logging 组件通过 ApplicationListener 启动,对应的处理类为 LoggingApplicationListener(spring-boot.jar 中的 spring.factories 配置了)
LoggingApplicationListener#onApplicationStartingEvent
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
// #1
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
this.loggingSystem.beforeInitialize();}
#1
根据应用引入的日志框架,加载对应的日志框架 LoggingSystem
LoggingSystem#get
public static LoggingSystem get(ClassLoader classLoader) {String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
if (StringUtils.hasLength(loggingSystem)) {if (NONE.equals(loggingSystem)) {return new NoOpLoggingSystem();
}
return get(classLoader, loggingSystem);
}
return SYSTEMS.entrySet().stream().filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader)) // #1
.map((entry) -> get(classLoader, entry.getValue())).findFirst() // #2
.orElseThrow(() -> new IllegalStateException("No suitable logging system located"));
}
#1
LoggingSystem#SYSTEMS 中存放了 SpringBoot 中 Logback,Log4j2,Java Util Logging 几个日志框架适配器的路径,检查这些类适配器是否存在以判断这些日志框架是否引入#2
构造 LoggingSystem,取第一个结果。
LoggingApplicationListener#initialize
protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {new LoggingSystemProperties(environment).apply();
// #1
this.logFile = LogFile.get(environment);
if (this.logFile != null) {this.logFile.applyToSystemProperties();
}
// #2
initializeEarlyLoggingLevel(environment);
// #3
initializeSystem(environment, this.loggingSystem, this.logFile);
// #4
initializeFinalLoggingLevels(environment, this.loggingSystem);
registerShutdownHookIfNecessary(environment, this.loggingSystem);
}
#1
从 environment 读取 logging.file,logging.path 配置,构建 LogFile#2
读取 Environment 中 debug,trace 的配置到 springBootLogging 属性 #3
(1) Environment 中存在 logging.config 配置,使用该配置读取配置文件,初始化 LoggingSystem。否则执行(2) 步骤
(2) 存在 logging.file 或 logging.path,使用#1
中构造的 LogFile 读取对应的配置文件,初始化 LoggingSystem。否则执行 (3) 步骤
(3) 如果能从默认配置路径中读取日志配置文件,则初始化 LoggingSystem。否则执行(4) 步骤
每个日志框架默认路径不同,如 Logback 会查找如下路径
logback-test.groovy, logback-test.xml, logback.groovy, logback.xml
logback-test-spring.groovy, logback-test-spring.xml, logback-spring.groovy, logback-spring.xml
(4) 使用默认方案,不同日志框架默认处理方案也不同,SpringBoot 中默认使用 Logback,该日志框架从 Environment 中获取 logging.pattern.level,logging.pattern.dateformat 配置,用于初始化 LoggingSystem。#4
使用#1
步骤加载的 springBootLogging 属性设置 LoggingLevel,并处理 logging.group 配置的 LoggingLevel。
这里的日志级别会覆盖 #3
步骤中配置文件的日志级别。
Environment
Environment 代表当前应用运行环境,管理配置属性数据,并提供 Profile 特性,即可以根据环境得到相应配置属性数据。
Environment 的查询
Environment 的实现类都继承了 AbstractEnvironment,
AbstractEnvironment#propertySources 是一个 MutablePropertySources,它实际上是一个属性源 PropertySources 列表。
AbstractEnvironment#propertyResolver 是一个属性解析器 ConfigurablePropertyResolver,负责从属性源中查询对应属性。
AbstractEnvironment 也实现了 PropertyResolver。
AbstractEnvironment#getProperty -> PropertySourcesPropertyResolver#getProperty
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {if (this.propertySources != null) {
// #1
for (PropertySource<?> propertySource : this.propertySources) {
...
Object value = propertySource.getProperty(key);
if (value != null) {if (resolveNestedPlaceholders && value instanceof String) {
// #2
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
// #3
return convertValueIfNecessary(value, targetValueType);
}
}
}
...
return null;
}
#1
遍历所有的属性源 propertySources#2
嵌套解析属性值(属性值可以使用占位符引用其他属性值)#3
类型转换,将配置属性转换为对应的类型
可以看到,属性源 PropertySource 的顺序很重要,如果多个 PropertySource 存在同样的属性,只有前面 PropertySource 的属性值生效。
如果你发现查询到的属性值不是自己设置的值,可能属性被覆盖了。
Environment 的构造
SpringApplication#prepareEnvironment 负责构造 Environment
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// #1
ConfigurableEnvironment environment = getOrCreateEnvironment();
// #2
configureEnvironment(environment, applicationArguments.getSourceArgs());
// #3
ConfigurationPropertySources.attach(environment);
// #4
listeners.environmentPrepared(environment);
// #5
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
// #6
ConfigurationPropertySources.attach(environment);
return environment;
}
#1
根据 Application 的环境构造对应的 Environment,SERVLET 应用,使用的是 StandardServletEnvironment#2
使用 SpringApplication#run 方法的 args 参数构造属性源 SimpleCommandLinePropertySource#3
添加数据源 ConfigurationPropertySourcesPropertySource#4
触发 ApplicationEnvironmentPreparedEvent 事件,负责处理该事件的 ApplicationListener 也会添加 PropertySource#5
通过 Binder 机制,将属性源中 spring.main 开头的属性绑定到 SpringApplication 的属性上#6
重新构造 ConfigurationPropertySourcesPropertySource
AbstractEnvironment 的构造函数会调用 AbstractEnvironment#customizePropertySources 方法,该方法可以调整 AbstractEnvironment#propertySources 的内容。我们依次看一下各个 AbstractEnvironment 子类的 customizePropertySources 方法。
StandardServletEnvironment#customizePropertySources
protected void customizePropertySources(MutablePropertySources propertySources) {
// #1
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
// #2
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME)); // #3
}
super.customizePropertySources(propertySources);
}
#1
添加属性源 servletConfigInitParams,通过 ServletConfig#getInitParameter()读取属性值 #2
添加属性源 servletContextInitParams,通过 ServletContext#getInitParameter() 读取属性值 #3
如果在 JNDI 环境中,添加添加属性源 jndiProperties
这里 servletConfigInitParams,servletContextInitParams 的 StubPropertySource 只是在添加属性源列表中占位,StandardServletEnvironment#initPropertySources 方法才将其替换为真正的 PropertySource。
StandardEnvironment#customizePropertySources
protected void customizePropertySources(MutablePropertySources propertySources) {
// #1
propertySources.addLast(new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
// #2
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
#1
添加属性源 systemProperties,通过 System.getProperties()读取属性值 #2
添加属性源 systemEnvironment,通过 System.getenv() 读取属性值
还有一个很重要的,SpringApplication#prepareEnvironment 方法 #2
步骤 -> SpringApplication#configureEnvironment -> SpringApplication#configurePropertySources,将添加一个 SimpleCommandLinePropertySource 到属性源列表最开始位置,该属性源读取 SpringBoot 启动命令行参数,此时它的优先级最高。
SpringApplication#prepareEnvironment 方法 #3
步骤,添加一个 ConfigurationPropertySourcesPropertySource 到属性源列表开始位置,这个类实际上也是一个属性源集合,它将 Environment 中所有其他属性源转化为 ConfigurationPropertySource 并作为自己的属性源。ConfigurationPropertySource 是一个特殊属性源,它查询属性的结果都是 ConfigurationProperty,ConfigurationProperty 是对属性数据的封装,包含了 name,value,origin。
我们查询到到属性大部分都是通过 ConfigurationPropertySourcesPropertySource 查到的(它已经包含了 servletConfigInitParams,servletContextInitParams,systemProperties,systemEnvironment 等属性源)。
最后添加的是最常用的 properties,yml 等配置文件的属性源。他们是通过 ConfigFileApplicationListener 添加的。ConfigFileApplicationListener 是一个 ApplicationListener,处理 ApplicationEnvironmentPreparedEvent 事件。
ConfigFileApplicationListener#onApplicationEnvironmentPreparedEvent 加载所有的 EnvironmentPostProcessor,调用 EnvironmentPostProcessor#postProcessEnvironment 完成该工作。
而 ConfigFileApplicationListener 也是一个 EnvironmentPostProcessor,
ConfigFileApplicationListener#postProcessEnvironment -> ConfigFileApplicationListener#addPropertySources -> Loader#load
public void load() {this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
// #1
initializeProfiles();
while (!this.profiles.isEmpty()) {Profile profile = this.profiles.poll();
// #2
if (profile != null && !profile.isDefaultProfile()) {addProfileToEnvironment(profile.getName());
}
// #3
load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
// #4
resetEnvironmentProfiles(this.processedProfiles);
// #5
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources(); // #6}
#1
添加初始的 profile,包括 null(读取所有配置文件)和 default#2
添加 profile 到 Environment 中#3
加载配置文件以及配置文件中配置的 profile#4
使用上一步加载到的 profile 重置 Environment 的 Profiles#5
使用 Environment 的 Profiles 再次加载配置文件#6
将前面加载的属性源添加到 Environment 属性源列表末尾
#3
步骤,在 spring.config.location 配置的目录下,使用 PropertySourceLoader 加载不同的配置文件(从 spring.factories 中获取 PropertySourceLoader 的实现类),默认有 PropertiesPropertySourceLoader,YamlPropertySourceLoader,可以读取 properties,xml,yml,yaml 格式的配置文件。
注意:这里最终调用 loadForFileExtension,该方法根据 profile 加载对应的配置文件
另外,SystemEnvironmentPropertySourceEnvironmentPostProcessor 也是一个 EnvironmentPostProcessor,它会使用 OriginAwareSystemEnvironmentPropertySource 替换原来的 systemEnvironment,该类可以适配 spring.profiles.active,spring_profiles_active,spring-profiles-active 等属性名,使他们可以查询到 SPRING_PROFILES_ACTIVE 的系统属性。
常用属性源优先级:SpringBoot 启动命令行参数 > ServletConfig/ServletContext > 系统属性 > properties,yml 等配置文件
最后,说一下 Binder 机制,它是从 SpringBoot 2 开始提供的功能,负责处理对象与 ConfigurationPropertySource 之间的绑定,并且可以方便地进行类型转换,以及提供回调方法介入绑定的各个阶段。
看一个 Binder 的最简单用法,properties 文件如下
redis.host=127.0.0.1
redis.port=637
使用 Binder 绑定属性
RedisConfig config = Binder.get(context.getEnvironment())
.bind("redis", Bindable.of(RedisConfig.class))
.get();
这样就可以将 properties 配置文件的属性绑定到 RedisConfig#host,RedisConfig#port 属性中,
@ConfigurationProperties 也是通过 Binder 机制实现。
@EnableConfigurationProperties 使 @ConfigurationProperties 生效,它通过 @Import 引入了 EnableConfigurationPropertiesRegistrar,
EnableConfigurationPropertiesRegistrar 实现了 ImportBeanDefinitionRegistrar,向 Spring 上下文注册了 ConfigurationPropertiesBindingPostProcessor,BoundConfigurationProperties,ConfigurationPropertiesBeanDefinitionValidator,ConfigurationBeanFactoryMetadata 等功能类。
而 ConfigurationPropertiesBindingPostProcessor 会对 @ConfigurationProperties 标注的类,使用 ConfigurationPropertiesBinder 将配置属性数据与 bean 属性绑定,ConfigurationPropertiesBinder 最终使用 Binder 对象来完成工作。
EnableConfigurationPropertiesRegistrar 是 SpringBoot2.2 开始使用的类,比 SpringBoot2.1 使用的 EnableConfigurationPropertiesImportSelector 更简洁清晰,有兴趣的同学可以自行阅读代码。
如果您觉得本文不错,欢迎关注我的微信公众号,您的关注是我坚持的动力!