本文将重点介绍SpringBoot提供给我们的另一个扩展点EnvironmentPostProcessor,它允许我们到任意的指定目录、以任意的方式加载一组配置,并赋予任意的优先级

上文对prepareEnvironment方法的configureEnvironment做了一个收尾,本文继续看第三行代码listeners.environmentPrepared

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {ConfigurableEnvironment environment = this.getOrCreateEnvironment();this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());listeners.environmentPrepared((ConfigurableEnvironment)environment);this.bindToSpringApplication((ConfigurableEnvironment)environment);if (!this.isCustomEnvironment) {environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());}ConfigurationPropertySources.attach((Environment)environment);return (ConfigurableEnvironment)environment;}

这个listeners看过前文的朋友应该还有印象,里面只有一个元素EventPublishingRunListener

    public void environmentPrepared(ConfigurableEnvironment environment) {Iterator var2 = this.listeners.iterator();while(var2.hasNext()) {SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();listener.environmentPrepared(environment);}}

所以就相当于调用了EventPublishingRunListener的environmentPrepared方法,查看该方法的实现

    public void environmentPrepared(ConfigurableEnvironment environment) {this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));}

通过内部的事件多播器发布了一个事件ApplicationEnvironmentPreparedEvent,同时将SringApplication和Environment对象传播了出去,发布的流程跟之前的ApplicationStartingEvent事件如出一辙,不再赘述

本次捕捉到该事件的监听器共有7个

其中大多数监听器是做一些边边角角的初始化工作,本文就先聚焦到其中一个比较重要的监听器ConfigFileApplicationListener,它完成了对配置文件的加载,并且引入了本文要讲的EnvironmentPostProcessor,它也是SpringBoot提供给我们的又一个扩展点

直接进入ConfigFileApplicationListener接收事件的方法onApplicationEvent

    public void onApplicationEvent(ApplicationEvent event) {if (event instanceof ApplicationEnvironmentPreparedEvent) {this.onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent)event);}if (event instanceof ApplicationPreparedEvent) {this.onApplicationPreparedEvent(event);}}

事件类型为ApplicationEnvironmentPreparedEvent,走第一个分支

    private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {List<EnvironmentPostProcessor> postProcessors = this.loadPostProcessors();postProcessors.add(this);AnnotationAwareOrderComparator.sort(postProcessors);Iterator var3 = postProcessors.iterator();while(var3.hasNext()) {EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var3.next();postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());}}

第一行loadPostProcessors到META-INF/spring.factories中加载了EnvironmentPostProcessor的实现类,加载方式与SpringApplication初始化时的流程相似,虽然调用了不同的API,但最终都通过SpringFactoriesLoader.loadFactoryNames获取了spring.factories的内容

    List<EnvironmentPostProcessor> loadPostProcessors() {return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, this.getClass().getClassLoader());}

最终找到三个类,都在spring-boot下的spring.factories中

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor

回到onApplicationEnvironmentPreparedEvent方法,加载到EnvironmentPostProcessor的列表后,ConfigFileApplicationListener把自己也加入到了这个集合中,因为它本身也实现了EnvironmentPostProcessor接口

public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {

然后遍历EnvironmentPostProcessor列表,依次调用其postProcessEnvironment方法,传入Environment和SpringApplication

我们依次看下每个EnvironmentPostProcessor的实现类都做了什么事情

SpringApplicationJsonEnvironmentPostProcessor

    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {MutablePropertySources propertySources = environment.getPropertySources();propertySources.stream().map(SpringApplicationJsonEnvironmentPostProcessor.JsonPropertyValue::get).filter(Objects::nonNull).findFirst().ifPresent((v) -> {this.processJson(environment, v);});}

先遍历Environment中的PropertySource,依次传给内部类JsonPropertyValue的静态方法get

    public static SpringApplicationJsonEnvironmentPostProcessor.JsonPropertyValue get(PropertySource<?> propertySource) {String[] var1 = CANDIDATES;int var2 = var1.length;for(int var3 = 0; var3 < var2; ++var3) {String candidate = var1[var3];Object value = propertySource.getProperty(candidate);if (value != null && value instanceof String && StringUtils.hasLength((String)value)) {return new SpringApplicationJsonEnvironmentPostProcessor.JsonPropertyValue(propertySource, candidate, (String)value);}}return null;}

CANDIDATES是final变量,声明时做了初始化

    private static class JsonPropertyValue {private static final String[] CANDIDATES = new String[]{"spring.application.json", "SPRING_APPLICATION_JSON"};

也就是针对每个PropertySource,看里面有没有属性spring.application.json或者SPRING_APPLICATION_JSON,如果有的话,把它转化为JsonPropertyValue存储起来,然后取第一个,调用processJson方法

    private void processJson(ConfigurableEnvironment environment, SpringApplicationJsonEnvironmentPostProcessor.JsonPropertyValue propertyValue) {JsonParser parser = JsonParserFactory.getJsonParser();Map<String, Object> map = parser.parseMap(propertyValue.getJson());if (!map.isEmpty()) {this.addJsonPropertySource(environment, new SpringApplicationJsonEnvironmentPostProcessor.JsonPropertySource(propertyValue, this.flatten(map)));}}

这个方法里把找到的属性对应的value转化为map,然后构造了一个JsonPropertySource,它是MapPropertySource的子类,配置的组名为spring.application.json

 JsonPropertySource(SpringApplicationJsonEnvironmentPostProcessor.JsonPropertyValue  propertyValue, Map<String, Object> source) {super("spring.application.json", source);this.propertyValue = propertyValue;}

然后添加到Environemtn的PropertySource列表,优先级是如果列表中有jndiProperties就放在它后面,如果没有就放在systemProperties,如果都没有,就放在列表头部,作为优先级最高的配置

    private void addJsonPropertySource(ConfigurableEnvironment environment, PropertySource<?> source) {MutablePropertySources sources = environment.getPropertySources();String name = this.findPropertySource(sources);if (sources.contains(name)) {sources.addBefore(name, source);} else {sources.addFirst(source);}}
    private String findPropertySource(MutablePropertySources sources) {return ClassUtils.isPresent("org.springframework.web.context.support.StandardServletEnvironment", (ClassLoader)null) && sources.contains("jndiProperties") ? "jndiProperties" : "systemProperties";}

SystemEnvironmentPropertySourceEnvironmentPostProcessor

    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {String sourceName = "systemEnvironment";PropertySource<?> propertySource = environment.getPropertySources().get(sourceName);if (propertySource != null) {this.replacePropertySource(environment, sourceName, propertySource);}}

先从Environment中取了名为systemEnvironment的PropertySource,也就是系统的环境变量,然后调用replacePropertySource对它做了一个包装替换

    private void replacePropertySource(ConfigurableEnvironment environment, String sourceName, PropertySource<?> propertySource) {Map<String, Object> originalSource = (Map)propertySource.getSource();SystemEnvironmentPropertySource source = new SystemEnvironmentPropertySourceEnvironmentPostProcessor.OriginAwareSystemEnvironmentPropertySource(sourceName, originalSource);environment.getPropertySources().replace(sourceName, source);}

systemEnvironment原先的具体类型为SystemEnvironmentPropertySource,这里替换的类型OriginAwareSystemEnvironmentPropertySource是它的子类,它额外实现了OriginLookup接口

所以SystemEnvironmentPropertySourceEnvironmentPostProcessor就是对系统环境变量做了一个包装,不影响原有的功能

CloudFoundryVcapEnvironmentPostProcessor

    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {if (CloudPlatform.CLOUD_FOUNDRY.isActive(environment)) {Properties properties = new Properties();JsonParser jsonParser = JsonParserFactory.getJsonParser();this.addWithPrefix(properties, this.getPropertiesFromApplication(environment, jsonParser), "vcap.application.");this.addWithPrefix(properties, this.getPropertiesFromServices(environment, jsonParser), "vcap.services.");MutablePropertySources propertySources = environment.getPropertySources();if (propertySources.contains("commandLineArgs")) {propertySources.addAfter("commandLineArgs", new PropertiesPropertySource("vcap", properties));} else {propertySources.addFirst(new PropertiesPropertySource("vcap", properties));}}}
 CLOUD_FOUNDRY {public boolean isActive(Environment environment) {return environment.containsProperty("VCAP_APPLICATION") || environment.containsProperty("VCAP_SERVICES");}},

这个postProcessor主要针对引入了CloudFound的项目,如果配置了VCAP_APPLICATION或者VCAP_SERVICES属性,就往Environment的PropertySource列表添加名为vcap的配置

CloudFound这东西我也没见有项目用过,它是一个开源的PAAS,据说是业界最早的开源云平台,不过就跟之前提到的Liquibase一样,虽然被SpringBoot默认支持了,但是至少在国内的受众还是比较少的

ConfigFileApplicationListener

    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {this.addPropertySources(environment, application.getResourceLoader());}

参数中传递的resourceLoader可以将一个指定位置的文件抽象成Resource,供后续解析使用,不过这里还是空的,因为SpringApplication创建的时候并没有对该属性作初始化

    protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {RandomValuePropertySource.addToEnvironment(environment);(new ConfigFileApplicationListener.Loader(environment, resourceLoader)).load();}

第一行代码往Environment中添加了一个名为random的PropertySource

    public static void addToEnvironment(ConfigurableEnvironment environment) {environment.getPropertySources().addAfter("systemEnvironment", new RandomValuePropertySource("random"));logger.trace("RandomValuePropertySource add to Environment");}

类型为RandomValuePropertySource,存储资源的类型指定为Random

public class RandomValuePropertySource extends PropertySource<Random> {public static final String RANDOM_PROPERTY_SOURCE_NAME = "random";private static final String PREFIX = "random.";private static final Log logger = LogFactory.getLog(RandomValuePropertySource.class);public RandomValuePropertySource(String name) {super(name, new Random());}

其作用是为配置文件中的部分属性提供随机数功能,比如我们想让系统运行在5000-6000内的随机端口上,就可以添加如下配置

server:port: ${random.int[5000,6000]}

至于后面的一行代码就是真正去加载系统的配置文件了,我们后面再单独开一篇来讨论

自定义EnvironmentPostProcessor

通过上述几个内置的EnvironmentPostProcessor,我们大致了解了这个接口的作用,通过自定义这个接口的实现,可以对Environment为所欲为,甚至改变系统的默认配置,先做个实验,自定义一个EnvironmentPostProcessor,把上面添加的RandomValuePropertySource删掉,让系统失去对配置文件的随机数支持

首先正常启动项目,可以看到端口在5000-6000中随机产生

新建类MyEnvironmentPostProcessor实现EnvironmentPostProcessor 接口,将名为random的PropertySource从environment中删除

@Order(-2147483637)
public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor {@Overridepublic void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {MutablePropertySources propertySources = environment.getPropertySources();propertySources.remove("random");}
}

需要注意的一点是,EnvironmentPostProcessor 是一个列表,会按一定顺序执行,我们要在ConfigFileApplicationListener将random添加到environment之后删除才有效果

ConfigFileApplicationListener实现了Ordered 接口,并指定order为-2147483638

public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {......private int order = -2147483638;......public int getOrder() {return this.order;}......

order值越小的越先执行,所以我们自己的类加上@Order注解,order设置为-2147483637,只要比ConfigFileApplicationListener 大就行

再次运行抛出异常,无法为端口生成随机数

当然这个例子只是说明EnvironmentPostProcessor给了我们很高的权限,去干预正常启动过程中的环境配置,实际中我们可以借助它来做一些公用配置的处理

假如我们有A B C三个服务,共同依赖了底层模块commom,想把一些公用的配置放在common模块中,并拥有最高的优先级,就可以在common新建一个配置文件application-common.properties,然后定义一个实现类CommonEnvironmentPostProcessor,去解析这个文件,并加载到PropertySource列表首部,当然还要注意如果想让配置的优先级最高,还要尽可能保证它最晚执行,所以order要设置的比较大

@Order(Integer.99999)
@Slf4j
public class CommonEnvironmentPostProcessor implements EnvironmentPostProcessor {@Overridepublic void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {try {//定义一个资源加载器ResourceLoader resourceLoader = new DefaultResourceLoader();//找到classpath下的application-common.properties文件,抽象成ResourceResource resource = resourceLoader.getResource("classpath:/application-common.properties");if (!resource.exists()) {return;}Properties properties = new Properties();//从文件中装载配置properties.load(new InputStreamReader(resource.getInputStream()));PropertiesPropertySource propertiesPropertySource = new PropertiesPropertySource("commonProperties", properties);//获取environment中的配置列表MutablePropertySources mutablePropertySources = environment.getPropertySources();//将当前配置添加到列表首部,即拥有最高的优先级mutablePropertySources.addFirst(propertiesPropertySource);} catch (Exception e) {log.error("加载公共配置失败", e);}}}

SpringBoot源码解析(七)EnvironmentPostProcessor相关推荐

  1. springboot源码解析-管中窥豹系列之BeanFactoryPostProcessor(十一)

    一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...

  2. SpringBoot源码解析

    SpringBoot源码解析 1.启动的过程: 首先在main函数中启动当前应用程序(SpringApplication启动) 创建SpringApplication对象(new SpringAppl ...

  3. springboot源码解析autoconfigure之WebMvcAutoConfiguration

    2019独角兽企业重金招聘Python工程师标准>>> 说在前面 本次开始spring-boot-autoconfigure源码解析之WebMvcAutoConfiguration ...

  4. Spring源码解析(七)-Bean属性间的循环依赖

    首先复习一下前面学习的Spring容器启动的大致流程,首先Spring会先扫描所有需要实例化的Bean,将这些Bean的信息封装成一个个BeanDefinition,然后注册到BeanDefiniti ...

  5. webpack源码解析七(optimization)

    前言 前面我们写了几篇文章用来介绍webpack源码,跟着官网结合demo把整个webpack配置撸了一遍: webpack源码解析一 webpack源码解析二(html-webpack-plugin ...

  6. maven 公共模块依赖_「spring-boot 源码解析」spring-boot 依赖管理

    问题 maven 工程,依赖管理是非常基本又非常重要的功能,现在的工程越来越庞大,依赖越来越多,各种二方包.三方包太多太多,依赖冲突处理起来真是让人头疼,经常需要涉及到多个地方需要调整. 微信公众号: ...

  7. 【java】spring-boot源码解析之应用启动

    spring boot 项目使用默认配置的思想,极大的简化了 spring 项目的开发.下面的代码就是一个最简单的 spring 项目: @SpringBootApplication public c ...

  8. SpringBoot源码解析(十一)@Primary

    在SpringBoot中有许多类使用到了@Primary注解,关于用法,请看这篇博客:在spring中常被忽视的注解 @Primary. 这次我们不说用法,从源码层面来看下: 一.入口 在创建bean ...

  9. SpringBoot 源码解析——如何进行源码环境调试?

    已经分析过 spring-boot-tests/spring-boot-smoke-tests 下的冒烟测试和很早之前版本的 sample 是一样的,所以我们想直接利用这些 sample code 来 ...

最新文章

  1. c语言数组最大元调换,c语言数组元素交换有关问题,请高手过来看看
  2. 组件化开发 ——— 制作私有库
  3. QT的QHttpMultiPart类的使用
  4. .NET中的文件IO操作实例
  5. matlab中图像轮廓变细,Matlab中,用bwmorph函数提取二进制图像的轮廓
  6. linux 内存泄露检测工具——valgrind
  7. Java基础学习总结(158)——开发Leader如何做CodeReview
  8. DataTable数据集动态构造Table表结构
  9. Ubuntu 20.04配置FTP服务方法(非匿名登录)
  10. 【转】《风雨哈佛路》观后感
  11. ROS下面调用自定义的头文件和.cpp/.so文件(亲测有效)
  12. ubuntu上vsftpd服务配置
  13. 截图工具分享(可截成gif动图)
  14. 周记——20151214
  15. 查看linux镜像版本的命令,Linux镜像列表中 怎样决定自己下载哪个版本
  16. 人工智能作用现代认知战探析
  17. LL(1)文法构造FIRST、FOLLOW、分析表并分析
  18. 关于客户端下载文件而不是在服务器生成文件
  19. storm人偶_STORM TOYS 真人快打系列 MOTARO 茂太郎 可动人偶
  20. c罗说什么语言,C罗会说几种语言? 揭金球奖给梅西内马尔当翻译趣事

热门文章

  1. Education教育
  2. E. Physical Education Lessons
  3. Python编译成.so文件后调用
  4. SQL去重的三种方法汇总 ​
  5. IBM推出三大云计算产品
  6. 打印10 * 10的表格
  7. 时间轮python_源码笔记:Nodejs 如何高效的获取时间戳而不影响性能的?
  8. python小助手_Python实现微信小助手
  9. 天涯上截取的当前经济周期的看法
  10. 360加固保 mac无法正常使用 解决方案 macOS 11以上版本有效