小架构step系列22:加载系统配置
1 概述
开发一个服务,配置是经常要用到的。如果把配置分一下类,可以分为系统配置、Springboot/Spring自带的配置、业务服务自定义的配置。Spring提供了一整套配置文件的加载,需要了解一下这些配置文件的加载原理,以便更好地使用这些配置文件。这里先关注系统配置。
2 原理
2.1 配置资源加载过程
系统启动的时候会执行SpringApplication.run()方法,Spring使用Environment的概念承载配置资源数据,在创建和准备Environment对象的时候进行配置资源的加载。
这里称为“配置资源”,一是大家熟悉把“配置”写到application.properties文件里,二是源码里使用了PropertyResouce这个词来代表这些配置,Resource是“资源”的意思。可以理解为跟使用application.properties文件里的配置一样对待这里加载的所有“配置资源”。
// 源码位置:org.springframework.boot.SpringApplicationpublic ConfigurableApplicationContext run(String... args) { long startTime = System.nanoTime(); DefaultBootstrapContext bootstrapContext = createBootstrapContext(); ConfigurableApplicationContext context = null; configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(bootstrapContext, this.mainApplicationClass); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 1. 准备环境对象 ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); context.setApplicationStartup(this.applicationStartup); prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup); } listeners.started(context, timeTakenToStartup); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex); } // 省略其它代码}// 源码位置:org.springframework.boot.SpringApplicationprivate ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) { // 2. 根据application情况创建环境对象,一般有三种:ApplicationServletEnvironment(Web)、ApplicationReactiveWebEnvironment(相应式)、ApplicationEnvironment(普通) // 这里以第一种ApplicationServletEnvironment为例 ConfigurableEnvironment environment = getOrCreateEnvironment(); configureEnvironment(environment, applicationArguments.getSourceArgs()); ConfigurationPropertySources.attach(environment); listeners.environmentPrepared(bootstrapContext, environment); DefaultPropertiesPropertySource.moveToEnd(environment); Assert.state(!environment.containsProperty(\"spring.main.environment-prefix\"), \"Environment prefix cannot be set via properties.\"); bindToSpringApplication(environment); if (!this.isCustomEnvironment) { EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader()); environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment;}// 源码位置:org.springframework.boot.SpringApplicationprivate ConfigurableEnvironment getOrCreateEnvironment() { if (this.environment != null) { return this.environment; } // 3. 使用工厂来创建环境对象 // webApplicationType为上面所的“应用情况”,根据依赖包不同得到不同的值,比如依赖了spring-web相关的包得到的是webApplicationType=SERVLET // applicationContextFactory为DefaultApplicationContextFactory ConfigurableEnvironment environment = this.applicationContextFactory.createEnvironment(this.webApplicationType); if (environment == null && this.applicationContextFactory != ApplicationContextFactory.DEFAULT) { environment = ApplicationContextFactory.DEFAULT.createEnvironment(this.webApplicationType); } // 如果不是web,创建的就是普通的ApplicationEnvironment return (environment != null) ? environment : new ApplicationEnvironment();}// 源码位置:org.springframework.boot.DefaultApplicationContextFactorypublic ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) { // 4. 把创建环境对象的funtion=ApplicationContextFactory::createEnvironment作为参数传入以获取环境对象 return getFromSpringFactories(webApplicationType, ApplicationContextFactory::createEnvironment, null);}private T getFromSpringFactories(WebApplicationType webApplicationType, BiFunction action, Supplier defaultResult) { for (ApplicationContextFactory candidate : SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class, getClass().getClassLoader())) { // 5. 调用创建环境对象的function以创建环境对象 // SpringFactoriesLoader.loadFactories()得到的是两种工厂,分别用于创建ReactiveWeb、ServletWeb对应的环境对象,这里以ServletWeb为例: // (1) org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext$Factory // (2) org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext$Factory // 根据webApplicationType不同,只有匹配的那个工厂才会最终创建出环境对象 T result = action.apply(candidate, webApplicationType); if (result != null) { return result; } } return (defaultResult != null) ? defaultResult.get() : null;}// 源码位置:org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext.Factorypublic ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) { // 6. 创建ApplicationReactiveWebEnvironment对象 // 继承关系:ApplicationServletEnvironment < StandardServletEnvironment < StandardEnvironment 0) { String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; if (sources.contains(name)) { PropertySource source = sources.get(name); CompositePropertySource composite = new CompositePropertySource(name); composite.addPropertySource(new SimpleCommandLinePropertySource(\"springApplicationCommandLineArgs\", args)); composite.addPropertySource(source); sources.replace(name, composite); } else { // 14. 增加命令行资源对象,注意用的是addFirst,代表顺序在之前加载的资源对象前面 sources.addFirst(new SimpleCommandLinePropertySource(args)); } }}// 回到SpringApplication.prepareEnvironment()// 源码位置:org.springframework.boot.SpringApplicationprivate ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) { // 2. 根据application情况创建环境对象 ConfigurableEnvironment environment = getOrCreateEnvironment(); // 12. 配置环境对象 configureEnvironment(environment, applicationArguments.getSourceArgs()); // 15. attach资源对象 ConfigurationPropertySources.attach(environment); // 省略其它代码}// 源码位置:org.springframework.boot.context.properties.source.ConfigurationPropertySourcespublic static void attach(Environment environment) { Assert.isInstanceOf(ConfigurableEnvironment.class, environment); MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources(); PropertySource attached = getAttached(sources); if (attached == null || !isUsingSources(attached, sources)) { // 16. 增加资源对象,ATTACHED_PROPERTY_SOURCE_NAME = \"configurationProperties\" // 这个资源对象没有新的信息,而是封装了一个SpringConfigurationPropertySources,传入了整个MutablePropertySources对象,也就是从它这里也能访问到所有的资源信息 attached = new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME, new SpringConfigurationPropertySources(sources)); } sources.remove(ATTACHED_PROPERTY_SOURCE_NAME); // 注意位置,目前它排第一位 sources.addFirst(attached);}// 回到SpringApplication.prepareEnvironment()// 源码位置:org.springframework.boot.SpringApplicationprivate ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) { // 2. 根据application情况创建环境对象 ConfigurableEnvironment environment = getOrCreateEnvironment(); // 12. 配置环境对象 configureEnvironment(environment, applicationArguments.getSourceArgs()); // 15. attach资源对象 ConfigurationPropertySources.attach(environment); // 17. 调用各个listener,根据情况添加资源对象,listeners为SpringApplicationRunListeners类型 listeners.environmentPrepared(bootstrapContext, environment); // 省略其它代码}// 源码位置:org.springframework.boot.SpringApplicationRunListenersvoid environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) { // 18. 用doWithListeners封装了一些调用各个listener这个操作,这里有个lambda表达式,把它整体看做一个参数 doWithListeners(\"spring.boot.application.environment-prepared\", (listener) -> listener.environmentPrepared(bootstrapContext, environment));}private void doWithListeners(String stepName, Consumer listenerAction) { // 19. doWithListeners继续封装,listenerAction为上面传入的lambda表达式 doWithListeners(stepName, listenerAction, null);}private void doWithListeners(String stepName, Consumer listenerAction, Consumer stepAction) { StartupStep step = this.applicationStartup.start(stepName); // 20. 遍历各个listener,调用listener的environmentPrepared()方法,listenerAction为上面传入的lambda表达式提供了environmentPrepared()方法调用 // listeners里面只有一个listener:org.springframework.boot.context.event.EventPublishingRunListener this.listeners.forEach(listenerAction); if (stepAction != null) { stepAction.accept(step); } step.end();}// 源码位置:org.springframework.boot.context.event.EventPublishingRunListenerpublic void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) { // 21. 发送ApplicationEnvironmentPreparedEvent事件 this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));}// 源码位置:org.springframework.context.event.SimpleApplicationEventMulticasterpublic void multicastEvent(ApplicationEvent event) { // 22. 调用私有方法发送事件 this.multicastEvent(event, this.resolveDefaultEventType(event));}public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event); Executor executor = this.getTaskExecutor(); // 如果获取到线程池则异步发送事件 // 23. 遍历ApplicationListener,触发监听器处理事件,getApplicationListeners()获取到多个listener, // 这里只关注EnvironmentPostProcessorApplicationListener处理与资源对象有关的内容 for(ApplicationListener listener : this.getApplicationListeners(event, type)) { if (executor != null) { executor.execute(() -> this.invokeListener(listener, event)); } else { this.invokeListener(listener, event); } }}protected void invokeListener(ApplicationListener listener, ApplicationEvent event) { ErrorHandler errorHandler = this.getErrorHandler(); if (errorHandler != null) { try { this.doInvokeListener(listener, event); } catch (Throwable err) { errorHandler.handleError(err); } } else { // 24. 触发单个ApplicationListener处理事件 this.doInvokeListener(listener, event); }}private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) { try { // 25. 调用listener的onApplicationEvent接口 // 这里只关注EnvironmentPostProcessorApplicationListener处理ApplicationEnvironmentPreparedEvent事件 listener.onApplicationEvent(event); } catch (ClassCastException ex) { // 省略部分代码 }}// 源码位置:org.springframework.boot.env.EnvironmentPostProcessorApplicationListenerpublic void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { // 26. 处理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(); // 27. 遍历多个EnvironmentPostProcessor,后处理环境对象,EnvironmentPostProcessor有下面几个: // (1) org.springframework.boot.env.RandomValuePropertySourceEnvironmentPostProcessor // (2) org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor // (3) org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor // (4) org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor // (5) org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor // (6) org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor // (7) org.springframework.boot.autoconfigure.integration.IntegrationPropertiesEnvironmentPostProcessor // 这里只关注前两个和ConfigDataEnvironmentPostProcessor,其它的是某些情况才用到,用到的时候再摸索,也就是当发现有新的资源,可以翻翻看是哪个processor添加的 for (多个EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(), event.getBootstrapContext())) { postProcessor.postProcessEnvironment(environment, application); }}// 源码位置:org.springframework.boot.env.RandomValuePropertySourceEnvironmentPostProcessorpublic void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { // 28. 通过RandomValuePropertySource增加random资源对象 RandomValuePropertySource.addToEnvironment(environment, this.logger);}// 源码位置:org.springframework.boot.env.RandomValuePropertySourcestatic void addToEnvironment(ConfigurableEnvironment environment, Log logger) { MutablePropertySources sources = environment.getPropertySources(); PropertySource existing = sources.get(RANDOM_PROPERTY_SOURCE_NAME); if (existing != null) { logger.trace(\"RandomValuePropertySource already present\"); return; } RandomValuePropertySource randomSource = new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME); if (sources.get(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME) != null) { sources.addAfter(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, randomSource); } else { // 29. 增加random资源对象,RANDOM_PROPERTY_SOURCE_NAME = \"random\" sources.addLast(randomSource); } logger.trace(\"RandomValuePropertySource add to Environment\");}// 源码位置:org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessorpublic void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { // SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = \"systemEnvironment\" String sourceName = StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME; PropertySource propertySource = environment.getPropertySources().get(sourceName); if (propertySource != null) { // 30. 调用私有方法替换资源对象 replacePropertySource(environment, sourceName, propertySource, application.getEnvironmentPrefix()); }}private void replacePropertySource(ConfigurableEnvironment environment, String sourceName, PropertySource propertySource, String environmentPrefix) { Map originalSource = (Map) propertySource.getSource(); // 31. 没有增加资源,只是把资源对象类型把SystemEnvironmentPropertySource替换为OriginAwareSystemEnvironmentPropertySource SystemEnvironmentPropertySource source = new OriginAwareSystemEnvironmentPropertySource(sourceName, originalSource, environmentPrefix); environment.getPropertySources().replace(sourceName, source);}// 源码位置:org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessorpublic void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { // 32. 自定义的配置文件加载,都从这里加载,这块另外细讲 postProcessEnvironment(environment, application.getResourceLoader(), application.getAdditionalProfiles());}
2.2 系统属性
在上面加载过程的第11步,进行系统属性的加载,这个是比较熟悉的System.getProperties()操作:
// 源码位置:org.springframework.core.env.StandardEnvironmentprotected void customizePropertySources(MutablePropertySources propertySources) { // 增加系统属性信息资源对象,SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = \"systemProperties\" propertySources.addLast(new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));}// 源码位置:org.springframework.core.env.AbstractEnvironmentpublic Map getSystemProperties() { try { // 获取JVM的系统属性集合,如操作系统名称、Java 版本等。 return (Map) System.getProperties(); } catch (AccessControlException ex) { // 异常情况下返回一个只读map,等获取具体属性的时候再调System.getProperty return (Map) new ReadOnlySystemAttributesMap() { @Override @Nullable protected String getSystemAttribute(String attributeName) { try { return System.getProperty(attributeName); } catch (AccessControlException ex) { // 省略部分代码 } } }; }}
2.3 系统环境变量
在上面加载过程的第11步,进行系统环境变量的加载,这个是通过System.getenv()获取:
// 源码位置:org.springframework.core.env.StandardEnvironmentprotected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); // 增加系统环境变量资源对象,SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = \"systemEnvironment\" propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));}// 源码位置:org.springframework.core.env.AbstractEnvironmentpublic Map getSystemEnvironment() { if (suppressGetenvAccess()) { return Collections.emptyMap(); } try { // 获取系统环境变量 return (Map) System.getenv(); } catch (AccessControlException ex) { return (Map) new ReadOnlySystemAttributesMap() { @Override @Nullable // 异常情况下返回一个只读map,等获取具体属性的时候再调System.getenv protected String getSystemAttribute(String attributeName) { try { return System.getenv(attributeName); } catch (AccessControlException ex) { // 省略部分代码 } } }; }}
2.4 随机变量
在上面加载过程的第29步还提供了一个随机变量的配置资源,可以代替平时用的new Random(),也就是可以按正常的用@Value注入一个随机数,每次使用的都是一个新的随机数:
// 源码位置:org.springframework.boot.env.RandomValuePropertySourcestatic void addToEnvironment(ConfigurableEnvironment environment, Log logger) { MutablePropertySources sources = environment.getPropertySources(); PropertySource existing = sources.get(RANDOM_PROPERTY_SOURCE_NAME); if (existing != null) { logger.trace(\"RandomValuePropertySource already present\"); return; } // 增加random资源对象,RANDOM_PROPERTY_SOURCE_NAME = \"random\" RandomValuePropertySource randomSource = new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME); if (sources.get(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME) != null) { sources.addAfter(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, randomSource); } else { sources.addLast(randomSource); } logger.trace(\"RandomValuePropertySource add to Environment\");}public Object getProperty(String name) { // PREFIX = \"random.\",变量必须以random.作为前缀 if (!name.startsWith(PREFIX)) { return null; } logger.trace(LogMessage.format(\"Generating random property for \'%s\'\", name)); return getRandomValue(name.substring(PREFIX.length()));}private Object getRandomValue(String type) { // 支撑的随机数类型:int、long、uuid、字节,每调用一次都会产生不同的随机数 if (type.equals(\"int\")) { return getSource().nextInt(); } if (type.equals(\"long\")) { return getSource().nextLong(); } String range = getRange(type, \"int\"); if (range != null) { return getNextIntInRange(Range.of(range, Integer::parseInt)); } range = getRange(type, \"long\"); if (range != null) { return getNextLongInRange(Range.of(range, Long::parseLong)); } if (type.equals(\"uuid\")) { return UUID.randomUUID().toString(); } return getRandomBytes();}private Object getRandomBytes() { byte[] bytes = new byte[32]; getSource().nextBytes(bytes); return DigestUtils.md5DigestAsHex(bytes);}
可以这样使用:
// 使用方法一:直接用@Value注入// 产生一个在10到20直接的随机整数@Value(\"${random.int(10,20)}\")private int randomInt;// 产生一个不指定范围的随机长整形数@Value(\"${random.long}\")private long randomLong;// 产生一个不指定范围的随机长整形数@Value(\"${random.uuid}\")private String randomUuid;// 产生一个不指定范围的随机长整形数@Value(\"${random.other}\")private Object randomOtherType;输出:randomInt=12randomLong=7441928871694155576randomUuid=e0543df9-7623-42bf-9fe5-1ad7a180e74drandomOtherType=b57ed309eb16895fc360ea4bd4f35277// 注意:如果服务是一个单例的,则第一次赋值之后不会再变化。// 使用方法二:通过Environment获取// 在配置文件application.properties里配置srvpro.random.int=${random.int(10,20)}// 在代码里注入Environment@Autowiredprivate Environment environment;// 通过Environment获取environment.getProperty(\"srvpro.random.int\")// 注意:由于environment.getProperty最终会触发上面源码RandomValuePropertySource的getProperty(),// 这个方法每次调用都会产生一个新的随机数,所以每次的数据都是不同的。
3 架构一小步
(1) 使用@Value的方式获取配置,简单清晰;
(2) 如果使用Environment获取配置,最好封装一个服务,既可以隔离一下容易测试,也可以统一管理用到的系统变量。