本文将重点介绍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的实现类都做了什么事情
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"; }
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就是对系统环境变量做了一个包装,不影响原有的功能
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默认支持了,但是至少在国内的受众还是比较少的
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,我们大致了解了这个接口的作用,通过自定义这个接口的实现,可以对Environment为所欲为,甚至改变系统的默认配置,先做个实验,自定义一个EnvironmentPostProcessor,把上面添加的RandomValuePropertySource删掉,让系统失去对配置文件的随机数支持
首先正常启动项目,可以看到端口在5000-6000中随机产生
新建类MyEnvironmentPostProcessor实现EnvironmentPostProcessor 接口,将名为random的PropertySource从environment中删除
@Order(-2147483637) public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor { @Override public 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 { @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { try { //定义一个资源加载器 ResourceLoader resourceLoader = new DefaultResourceLoader(); //找到classpath下的application-common.properties文件,抽象成Resource Resource 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); } } }