全文参考:https://mp.weixin.qq.com/s/G5BV5BIdOtB3LlxNsr4ZDQ
https://blog.csdn.net/crystonesc/article/details/106630412
https://www.cnblogs.com/deepSleeping/p/14565774.html
背景:近期在接入行内配置中心,因此对配置的加载接入有了一些兴趣,由于当时接入Apollo配置中心出现过不少问题,所以做以下整理。
此处参考:https://blog.csdn.net/crystonesc/article/details/106630412
使用Apollo接入,但由于是SpringMVC项目,且原先部分配置与Apollo加载存在冲突,无法使用SpringBean方式管理接入配置。因此使用Apollo中获取的配置方式接入。具体代码如下:
Config config = ConfigService.getConfig("application.properties");
String userNmae = config.getProperty("st.username", null);
- 此处 ConfigService.getConfig() 传入的是namespace,在Apollo配置中心默认是 application.properties
- getConfig()追溯到最终的实现类,通过namespaces。
ConfigService.getConfig()方法是在ConfigService类中的,ConfigService是一个单例,也就是说对于应用程序来说只会有一个ConfigService的实例,并且实例是被通过私有静态变量被持有在ConfigService当中。
private static final ConfigService s_instance = new ConfigService();
同时我们看到ConfigService持有两个属性ConfigManager和ConfigRegistry,其中ConfigManager是配置(ConfigManager)的管理器,ConfigRegistry用于手工配置注入,这两个属性的初始化均是通过ApolloInjector来注入的。ConfigService中的另外一个方法getAppConfig,该方法用户获取application配置文件的内容(apollo中创建的默认配置文件(namespace))。而getAppConfig中会实际调用getConfig获取配置,getConfig则是通过ConfigManager去获取配置。
// 通过namespace获取Config,首先从m_configs缓存中获取,
// 如果没有获取则通过ConfigFacotryManager获取ConfigFactory并创建Config
public Config getConfig(String namespace) {
Config config = m_configs.get(namespace);
if (config == null) {
synchronized (this) {
config = m_configs.get(namespace);
if (config == null) {
ConfigFactory factory = m_factoryManager.getFactory(namespace);
config = factory.create(namespace);
m_configs.put(namespace, config);
}
}
}
return config;
}
源码中,关于配置的获取,可以追溯到config = factory.create(namespace);
ConfigFactory factory = m_factoryManager.getFactory(namespace);
config = factory.create(namespace);
m_configs.put(namespace, config);
也就是说,在此完成了配置的创建和获取。
public Config create(String namespace) {
// 判断namespace的文件类型
ConfigFileFormat format = this.determineFileFormat(namespace);
ConfigRepository configRepository = null;
// 判断文件属性是否不为 properties,需要注意 format !=
if (ConfigFileFormat.isPropertiesCompatible(format) && format != ConfigFileFormat.Properties) {
configRepository = this.createPropertiesCompatibleFileConfigRepository(namespace, format);
} else {
// application.properties 会走到这个方法
configRepository = this.createConfigRepository(namespace);
}
logger.debug("Created a configuration repository of type [{}] for namespace [{}]", configRepository.getClass().getName(), namespace);
return this.createRepositoryConfig(namespace, (ConfigRepository)configRepository);
}
在 configRepository = this.createConfigRepository(namespace); 方法中,实际上会去访问 本地持久化的 apollo 配置,默认地址为 /opt/data。
注:以下代码之间非一个类中,为方便关联查看进行了位置调整。
LocalFileConfigRepository createLocalConfigRepository(String namespace) {
if (this.m_configUtil.isInLocalMode()) {
logger.warn("==== Apollo is in local mode! Won't pull configs from remote server for namespace {} ! ====", namespace);
return new LocalFileConfigRepository(namespace);
} else {
// 此处往下调用
return new LocalFileConfigRepository(namespace, this.createRemoteConfigRepository(namespace));
}
}
public LocalFileConfigRepository(String namespace, ConfigRepository upstream) {
this.m_sourceType = ConfigSourceType.LOCAL;
this.m_namespace = namespace;
this.m_configUtil = (ConfigUtil)ApolloInjector.getInstance(ConfigUtil.class);
// 此处往下调用
this.setLocalCacheDir(this.findLocalCacheDir(), false);
this.setUpstreamRepository(upstream);
this.trySync();
}
private File findLocalCacheDir() {
try {
// 此处往下调用
String defaultCacheDir = this.m_configUtil.getDefaultLocalCacheDir();
Path path = Paths.get(defaultCacheDir);
if (!Files.exists(path, new LinkOption[0])) {
Files.createDirectories(path);
}
if (Files.exists(path, new LinkOption[0]) && Files.isWritable(path)) {
return new File(defaultCacheDir, "/config-cache");
}
} catch (Throwable var3) {
}
return new File(ClassLoaderUtil.getClassPath(), "/config-cache");
}
public String getDefaultLocalCacheDir() {
String cacheRoot = this.getCustomizedCacheRoot();
if (!Strings.isNullOrEmpty(cacheRoot)) {
return cacheRoot + File.separator + this.getAppId();
} else {
// 此处可以看到会去默认的 /opt/data/ 寻找缓存apollo配置中心的应用配置
cacheRoot = this.isOSWindows() ? "C:\\opt\\data\\%s" : "/opt/data/%s";
return String.format(cacheRoot, this.getAppId());
}
}
在回到 create()方法 返回值为return this.createRepositoryConfig(namespace, (ConfigRepository)configRepository);,定睛一看,实际上最后返回了DefaultConfig。
protected Config createRepositoryConfig(String namespace, ConfigRepository configRepository) {
return new DefaultConfig(namespace, configRepository);
}
return new DefaultConfig(namespace, configRepository);
而DefaultConfig的构造方法中 this.loadFromResource(this.m_namespace); 完成了配置的读取。
public DefaultConfig(String namespace, ConfigRepository configRepository) {
this.m_sourceType = ConfigSourceType.NONE;
this.m_namespace = namespace;
this.m_resourceProperties = this.loadFromResource(this.m_namespace);
this.m_configRepository = configRepository;
this.m_configProperties = new AtomicReference();
this.m_warnLogRateLimiter = RateLimiter.create(0.017);
this.initialize();
}
在 loadFromResource(this.m_namespace); 中,取到了在 META-INF/config/ 下的为 namespace 的配置。
private Properties loadFromResource(String namespace) {
String name = String.format("META-INF/config/%s.properties", namespace);
InputStream in = ClassLoaderUtil.getLoader().getResourceAsStream(name);
Properties properties = null;
if (in != null) {
properties = this.propertiesFactory.getPropertiesInstance();
try {
properties.load(in);
} catch (IOException var14) {
Tracer.logError(var14);
logger.error("Load resource config for namespace {} failed", namespace, var14);
} finally {
try {
in.close();
} catch (IOException var13) {
}
}
}
return properties;
}
至此配置完成了获取。配置如何刷新、拉取,有时间会再次进行更新,还请各位看官耐心等待。
小结: