前言
最近公司项目要接入配置中心,后来调研下来决定使用apollo,但是在使用的时候发现有
个小细节特别难受,apollo不支持通过项目代码配置默认的environment,官网文档如下:

这样会导致代码拉下来不能直接启动服务,还需要通过上面的三种方式之一来指定environment,非常的麻烦。
进行魔改
我想着是能不能项目代码的配置文件里配置一个默认的environment,这样在没有外部指定environment的情况下就默认使用配置文件里的。
通过跟踪源码可以看到加载environment是通过DefaultServerProvider类的initialize方法进行加载的,然后通过DefaultProviderManager类中的register方法进行注册,时序图:

本来理论上来说魔改DefaultServerProvider这个类就可以实现目的了,然而这源码里没有为接口ServerProvider实现SPI机制,是直接new出来的:

那么只能从DefaultProviderManager类进行入手了,这个接口是通过SPI机制去加载的实现类:

现在唯一的入口点就是register方法了,通过这个方法可以把注册的DefaultServerProvider魔改掉,这个时候设计模式就派上用场了,我直接进行一个装饰器模式的秀:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | public class HookServerProviderManager extends DefaultProviderManager {
 @Override
 public synchronized void register(Provider provider) {
 
 if (provider instanceof DefaultServerProvider) {
 super.register(new DefaultServerProviderWrapper((DefaultServerProvider) provider));
 } else {
 super.register(provider);
 }
 }
 }
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 
 | public class DefaultServerProviderWrapper implements ServerProvider {
 private DefaultServerProvider serverProvider;
 
 private String defaultEnv;
 
 public DefaultServerProviderWrapper(DefaultServerProvider serverProvider) {
 this.serverProvider = serverProvider;
 
 
 if (serverProvider.getEnvType() == null) {
 Properties prop = new Properties();
 try (InputStream inputStream = Thread.currentThread().getContextClassLoader()
 .getResourceAsStream(DefaultApplicationProvider.APP_PROPERTIES_CLASSPATH.substring(1))) {
 prop.load(inputStream);
 this.defaultEnv = prop.getProperty("default.env");
 } catch (Exception e) {
 log.warn("load app.properties fail", e);
 }
 }
 }
 
 @Override
 public String getEnvType() {
 String originEnv = serverProvider.getEnvType();
 return originEnv != null ? originEnv : defaultEnv;
 }
 
 @Override
 public boolean isEnvTypeSet() {
 return getEnvType() != null;
 }
 
 @Override
 public String getDataCenter() {
 return serverProvider.getDataCenter();
 }
 
 @Override
 public boolean isDataCenterSet() {
 return serverProvider.isDataCenterSet();
 }
 
 @Override
 public void initialize(InputStream in) throws IOException {
 serverProvider.initialize(in);
 }
 
 @Override
 public Class<? extends Provider> getType() {
 return serverProvider.getType();
 }
 
 @Override
 public String getProperty(String name, String defaultValue) {
 return serverProvider.getProperty(name, defaultValue);
 }
 
 @Override
 public void initialize() {
 serverProvider.initialize();
 }
 }
 
 | 
代码写好之后,按 SPI 的规范在META-INF/services下新建文件com.ctrip.framework.foundation.spi.ProviderManager,把刚刚的HookServerProviderManager类路径填入即可。
这样的话如果没有读到environment的话就会去读取META-INF/app.propertie的default.env配置。
吐槽
这个应该是一个很常见的需求,不知道为啥阿波罗竟然不支持,也有可能是我的使用方式不对吧,PR 就懒得提了,毕竟不是一定要改源码才能实现。