1 启动参数args解析
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
这个没啥好说的
// 解析args参数并封装为PropertySource,名字为commandLineArgs
// 正确格式--foo=bar
// **下面是解析逻辑**
public CommandLineArgs parse(String... args) {
CommandLineArgs commandLineArgs = new CommandLineArgs();
for (String arg : args) {
// 必须以 -- 开头
if (arg.startsWith("--")) {
String optionText = arg.substring(2);
String optionName;
String optionValue = null;
int indexOfEqualsSign = optionText.indexOf('=');
// 截取key value
if (indexOfEqualsSign > -1) {
optionName = optionText.substring(0, indexOfEqualsSign);
optionValue = optionText.substring(indexOfEqualsSign + 1);
}
else {optionName = optionText;}
if (optionName.isEmpty()) { throw new IllegalArgumentException("Invalid argument syntax: " + arg);}
commandLineArgs.addOptionArg(optionName, optionValue);
}
else { commandLineArgs.addNonOptionArg(arg);}
}
return commandLineArgs;
}
2 Environment环境准备
prepareEnvironment(listeners, bootstrapContext, applicationArguments);
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
// 这里获取了web环境推断,如果是SERVLET会返回ApplicationServletEnvironment
// ApplicationServletEnvironment构造会生成servletConfigInitParams/servletConfigInitParams/systemProperties/systemEnvironment属性源
// systemProperties/systemEnvironment都是一些系统环境变量,jdk版本/用户/系统目录等等
// 详见2.1
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 环境资源整合,把args也分装成一个PropertySource
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 把所有资源变成标准规范合在一起放在第一个
ConfigurationPropertySources.attach(environment);
// 发布了环境准备事件
// 详见2.2
listeners.environmentPrepared(bootstrapContext, environment);
// 将defaultProperties移动到最后一位
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
// 将环境对象绑定到当前Spring应用
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
// 再次 把所有资源变成标准规范合在一起放在第一个
ConfigurationPropertySources.attach(environment);
return environment;
}
2.1 创建environment 环境对象
ConfigurableEnvironment environment = getOrCreateEnvironment();
根据先前的类型推断,我们需要创建各自不同的环境容器,虽然三个环境获得的类不同,其实是基本没有区别都继承了StandardServletEnvironment并且重写了一样的方法,只是新增了不同环境对于的不同PropertySource(mvc新增了servletConfigInitParams和servletConfigInitParams)
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {return this.environment;}
// 更具先前的类型推断选择Environment对象
switch (this.webApplicationType) {
case SERVLET:
return new ApplicationServletEnvironment();
case REACTIVE:
return new ApplicationReactiveWebEnvironment();
default:
return new ApplicationEnvironment();
}
}
ApplicationServletEnvironment的类关系图
ApplicationServletEnvironment()的实例化啥都没有
这里就要看父类的父类的父类AbstractEnvironment的构造函数
protected AbstractEnvironment(MutablePropertySources propertySources) {
this.propertySources = propertySources;
// createPropertyResolver在StandardServletEnvironment中被重写
this.propertyResolver = createPropertyResolver(propertySources);
// 被StandardEnvironment重写
customizePropertySources(propertySources);
}
StandardEnvironment是spring提供的标准的环境容器
为我们提供了systemEnvironment(系统的一些参数)和 systemProperties(jvm的一些参数)
public class StandardEnvironment extends AbstractEnvironment {
/** System environment property source name: {@value}. */
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
/** JVM system properties property source name: {@value}. */
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
public StandardEnvironment() {
}
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(
new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
}
StandardServletEnvironment是springboot为Servlet服务提供的一个扩展环境
新增了servletContextInitParams和servletConfigInitParams但是好像都是空的,没有具体参数
public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
/** Servlet context init parameters property source name: {@value}. */
public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";
/** Servlet config init parameters property source name: {@value}. */
public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";
public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
// 新增了两个PropertySource
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
super.customizePropertySources(propertySources);
}
}
2.2 环境增强
到这里加上集合一共五个source, 此时我们的配置文件yml和properties都还没有读取,之后spring发布了一个环境准备完成事件
listeners.environmentPrepared(bootstrapContext, environment);
众多监听器中之一,EnvironmentPostProcessorApplicationListener监听到了这一事件
public class EnvironmentPostProcessorApplicationListener implements SmartApplicationListener, Ordered {
@Override
public void onApplicationEvent(ApplicationEvent event) {
// 监听到环境准备完成事件
if (event instanceof ApplicationEnvironmentPreparedEvent) {
// 传入ApplicationEnvironmentPreparedEvent进行扩展
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);}
if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent();}
if (event instanceof ApplicationFailedEvent) {onApplicationFailedEvent();}
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
SpringApplication application = event.getSpringApplication();
// 获取了SpringFactories中所有的EnvironmentPostProcessor
for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),
event.getBootstrapContext())) {
// 循环调用回调接口
postProcessor.postProcessEnvironment(environment, application);
}
}
}
spring获取springFactories中的EnvironmentPostProcessor,听名字就知道这是环境增强器
这里更像是对我们环境的一次扩充,读取yml,properties...等等一系列文件都可以在这完成,包括对一些${}动态参数的注入,或者随机数配置的赋值。 有些博客将这个增强器看作是获取环境参数的地方,其实不然,有些参数这时候并没有加载(之后我会详细介绍Apollo的加载顺序)。
springboot默认提供了9个环境增强器EnvironmentPostProcessor
每个环境增强器各司其职,有解析json文件的,有随机值赋值的,一切看似很美好,但是我们似乎还是没有看到我们想看到的,properties和yml到底在哪? 由于springboot2.4之后,对配置环境进行了大改,对外部读取配置读取进行再一次增强,这一切都归功于ConfigDataEnvironmentPostProcessor,逻辑过于复杂,这里就不作展示 有兴趣可以看看这两篇博客
3 springboot扩展-阿波罗Apollo
Apollo是如何扩展springboot应用的,首先提供了一个spring.factories
// 自动配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ctrip.framework.apollo.spring.boot.ApolloAutoConfiguration
// prepareContext阶段调用
org.springframework.context.ApplicationContextInitializer=\
com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer
// 环境增强器
org.springframework.boot.env.EnvironmentPostProcessor=\
com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer
这里主要关注ApplicationContextInitializer和EnvironmentPostProcessor
官方使用文档 www.apolloconfig.com/#/zh/usage/…
图来自Apollo文档,但是有的类已经过时,ConfigFileApplicationListener已经过时(2.4.0之后被EnvironmentPostProcessorApplicationListener替换),但是依然不会影响主要功能,同时Apollo也提供了对2.4之后扩展的实现(详见apollo-client-config-data这个项目)
(2.4.0之后:环境准备阶段发布的环境准备事件被EnvironmentPostProcessorApplicationListener接收,然后调用所有EnvironmentPostProcessor进行增强)
3.1 阿波罗 加载配置的时机
3.1.1 EnvironmentPostProcessor
@Override
public void postProcessEnvironment(ConfigurableEnvironment configurableEnvironment, SpringApplication springApplication) {
// 初始化必要参数
initializeSystemProperty(configurableEnvironment);
// 是否早加载,默认false apollo.bootstrap.eagerLoad.enabled
Boolean eagerLoadEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_EAGER_LOAD_ENABLED, Boolean.class, false);
// 直接返回
if (!eagerLoadEnabled) {return;}
// 是否从配置平台读取配置 apollo.bootstrap.enabled
Boolean bootstrapEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, Boolean.class, false);
if (bootstrapEnabled) {
DeferredLogger.enable();
// 加载配置 详见3.2
initialize(configurableEnvironment);
}
}
PropertySourcesConstants的一些配置参数名,也可以说是Apollo的引导类配置
public interface PropertySourcesConstants {
String APOLLO_PROPERTY_SOURCE_NAME = "ApolloPropertySources";
String APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME = "ApolloBootstrapPropertySources";
String APOLLO_BOOTSTRAP_ENABLED = "apollo.bootstrap.enabled";
String APOLLO_BOOTSTRAP_EAGER_LOAD_ENABLED = "apollo.bootstrap.eagerLoad.enabled";
String APOLLO_BOOTSTRAP_NAMESPACES = "apollo.bootstrap.namespaces";
}
主要参数
apollo.bootstrap.enabled 开启从配置平台读取配置
apollo.bootstrap.eagerLoad.enabled 是否早加载
apollo.bootstrap.namespaces 需要使用的命名空间
我们可以看到EnvironmentPostProcessorApplicationListener优先于LoggingApplicationListener加载,如果配置文件中包含logging.level.root=info和logback-spring.xml中的参数,那么配置平台的配置将覆盖这些文件,实现日志的可配置化
3.1.2 initialize
如果没有开启早加载(默认关闭,不然阿波罗的加载会没有日志),那阿波罗到底是在哪加载配置的呢?
不知道大家还有没有印象,在springboot new SpringApplication() 的时候注册了所有ApplicationContextInitializer,在prepareContext阶段的applyInitializers方法对所有ApplicationContextInitializer的方法进行了一次回调
此时所有的yml、proprties等等都已加载完毕,只需要把自己加载的文件放入配置文件集合的第一位,即可完成所有属性的覆盖,正常情况下此时其他框架已经不会修改environment了,那apollo完全可以为所欲为了
apollo提供的ApolloApplicationContextInitializer
@Override
public void initialize(ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
// 还是判断一下是否开启了 apollo.bootstrap.enabled
if (!environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, Boolean.class, false)) {
logger.debug("Apollo bootstrap config is not enabled for context {}, see property: ${{}}", context, PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED);
return;
}
logger.debug("Apollo bootstrap config is enabled for context {}", context);
// 加载配置详见3.2
initialize(environment);
}
3.2 加载配置文件
Apollo提供了ConfigService.getConfig(namespace) 来实现获取不同命名空间的加载,具体实现就不看了
protected void initialize(ConfigurableEnvironment environment) {
// 判重,如果环境已经加载了,那就啥都不干
if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
DeferredLogger.replayTo();
return;
}
// 获取所有namespaces
String namespaces = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, ConfigConsts.NAMESPACE_APPLICATION);
logger.debug("Apollo bootstrap namespaces: {}", namespaces);
// 用逗号分隔namespaces,因为我们的namespaces一般配置的都是以逗号分割字符串
List<String> namespaceList = NAMESPACE_SPLITTER.splitToList(namespaces);
CompositePropertySource composite;
// 获取配置工具类,存放apollo的配置项
final ConfigUtil configUtil = ApolloInjector.getInstance(ConfigUtil.class);
// 判断是否开启 apollo.property.names.cache.enable默认false
if (configUtil.isPropertyNamesCacheEnabled()) {
// 听说能启动更快
composite = new CachedCompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
} else {
// 默认这个
composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
}
// 获取每个命名空间的配置,加入composite
for (String namespace : namespaceList) {
Config config = ConfigService.getConfig(namespace);
composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
}
// 把apollo的PropertySources放进第一个
environment.getPropertySources().addFirst(composite);
}
这里把环境对象传了进来,创建了一个名为 ApolloBootstrapPropertySources 的PropertySource放入了配置集合首位。
4 总结
环境准备阶段是spring启动的第一个关键步骤,一次性为容器加载了所有配置,之后的每一步都必须依赖于当前的配置参数。如果需要对环境篇配置进行扩展和增强,推荐使用EnvironmentPostProcessor