Netflix Archaius是一个功能强大的配置管理库。它是一个可用于从许多不同来源收集配置属性的框架,提供对配置信息的快速及线程安全访问。
image.png
Archaius的优点如下:
/**
* @author xiaoma
* @version V1.0
* @Description: TODO
* @date 2020/3/15 2:19
*/
public class SampleBean {
/**
* Default CTOR
*/
public SampleBean() {
// value of field obtained during init only
this.name = DynamicPropertyFactory
.getInstance()
.getStringProperty(
"com.netflix.config.samples.SampleApp.SampleBean.name",
"Sample Bean").get();
}
/**
* Name of this bean :-) The value for this can be obtained from Properties
*/
public String name;
/**
* Number of seeds in this bean. The value for this is automatically
* populated by the Configuration. Additionally, if the property value is
* modified, the new value will be reflected as well We can assign a default
* value as well in case a value was not configured or the configuration was
* not successfully loaded
*
*/
public DynamicIntProperty numSeeds = DynamicPropertyFactory.getInstance()
.getIntProperty(
"com.netflix.config.samples.SampleApp.SampleBean.numSeeds",
2);
/**
* This field utilizes the feature of Callbacks.
* When a property value is changed at runtime, it will receive a callback
* which can be used to trigger other operations or used for bookkeeping
*/
DynamicStringProperty sensitiveBeanData = DynamicPropertyFactory
.getInstance()
.getStringProperty(
"com.netflix.config.samples.SampleApp.SampleBean.sensitiveBeanData",
"magic", new Runnable() {
public void run() {
// let my auditing system know about this change
System.out
.println("SampleBean.sensitiveData changed to:"
+ sensitiveBeanData.get());
}
});
/**
* returns the name of this bean
* @return
*/
public String getName(){
return name;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("SampleBean ->name:");
sb.append(name);
sb.append("\t numSeeds:");
sb.append(numSeeds);
sb.append("\tsensitiveBeanData:");
sb.append(sensitiveBeanData);
return sb.toString();
};
}
/**
* @author xiaoma
* @version V1.0
* @Description: TODO
* @date 2020/3/15 2:12
*/
public class SampleApplication extends Thread {
static ConfigMBean configMBean = null;
public SampleApplication() {
// This application is set up as a non-daemon app
// as we want it to just hang around allowing us to launch jconsole
// to perform properties based operations
setDaemon(false);
start();
}
public void run() {
while (true) {
try {
sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
/**
* SampleApplication entrypoint
* @param args
*/
public static void main(String[] args) {
//@SuppressWarnings(value = {"unused" })
new SampleApplication();
// Step 1.
// Create a Source of Configuration
// Here we use a simple ConcurrentMapConfiguration
// You can use your own, or use of the pre built ones including JDBCConfigurationSource
// which lets you load properties from any RDBMS
AbstractConfiguration myConfiguration = new ConcurrentMapConfiguration();
myConfiguration.setProperty("com.netflix.config.samples.sampleapp.prop1", "value1");
myConfiguration.setProperty(
"com.netflix.config.samples.SampleApp.SampleBean.name",
"A Coffee Bean from Gautemala");
// STEP 2: Optional. Dynamic Runtime property change option
// We would like to utilize the benefits of obtaining dynamic property
// changes
// initialize the DynamicPropertyFactory with our configuration source
DynamicPropertyFactory.initWithConfigurationSource(myConfiguration);
// STEP 3: Optional. Option to inspect and modify properties using JConsole
// We would like to inspect properties via JMX JConsole and potentially
// update
// these properties too
// Register the MBean
//
// This can be also achieved automatically by setting "true" to
// system property "archaius.dynamicPropertyFactory.registerConfigWithJMX"
configMBean = ConfigJMXManager.registerConfigMbean(myConfiguration);
// once this application is launched, launch JConsole and navigate to
// the
// Config MBean (under the MBeans tab)
System.out
.println("Started SampleApplication. Launch JConsole to inspect and update properties");
System.out.println("To see how callback work, update property com.netflix.config.samples.SampleApp.SampleBean.sensitiveBeanData from BaseConfigBean in JConsole");
SampleBean sampleBean = new SampleBean();
System.out.println("SampleBean:" + sampleBean);
System.out.println(sampleBean.getName());
}
}
可以看输出结果,可以看到SampleBean的name属性已经发生变更了。name属性从Sample Bean
变更到A Coffee Bean from Gautemala
。
Started SampleApplication. Launch JConsole to inspect and update properties
To see how callback work, update property com.netflix.config.samples.SampleApp.SampleBean.sensitiveBeanData from BaseConfigBean in JConsole
SampleBean:SampleBean ->name:A Coffee Bean from Gautemala numSeeds:DynamicProperty: {name=com.netflix.config.samples.SampleApp.SampleBean.numSeeds, current value=2} sensitiveBeanData:DynamicProperty: {name=com.netflix.config.samples.SampleApp.SampleBean.sensitiveBeanData, current value=magic}
A Coffee Bean from Gautemala
上文说过Archaius支持JMX, 通过ConfigJMXManager.registerConfigMbean(myConfiguration)
这行代码暴露配置信息,我们通过jvisualvm可以看到JMX管理的beans。
image.png
Config-com.netflix.config.jmx
中的BaseConfigMBean
就是我们上文暴露的配置bean, 可以通过Operation invocation操作动态拿到程序里面该属性的值。
image.png
多个数据源配置信息合并操作
/**
* @author xiaoma
* @version V1.0
* @Description: TODO
* @date 2020/3/15 2:13
*/
public class SampleApplicationWithDefaultConfiguration {
static {
// sampleapp.properties is packaged within the shipped jar file
System.setProperty("archaius.configurationSource.defaultFileName", "sampleapp.properties");
System.setProperty(DynamicPropertyFactory.ENABLE_JMX, "true");
System.setProperty("com.netflix.config.samples.SampleApp.SampleBean.sensitiveBeanData", "value from system property");
}
public static void main(String[] args) {
new SampleApplication();
ConcurrentCompositeConfiguration myConfiguration =
(ConcurrentCompositeConfiguration) DynamicPropertyFactory.getInstance().getBackingConfigurationSource();
ConcurrentMapConfiguration subConfig = new ConcurrentMapConfiguration();
subConfig.setProperty("com.netflix.config.samples.SampleApp.SampleBean.name", "A Coffee Bean from Cuba");
myConfiguration.setProperty("com.netflix.config.samples.sampleapp.prop1", "value1");
myConfiguration.addConfiguration(subConfig);
System.out.println("Started SampleApplication. Launch JConsole to inspect and update properties.");
System.out.println("To see how callback work, update property com.netflix.config.samples.SampleApp.SampleBean.sensitiveBeanData from BaseConfigBean in JConsole");
SampleBean sampleBean = new SampleBean();
// this should show the bean taking properties from two different sources
// property "com.netflix.config.samples.SampleApp.SampleBean.numSeeds" is from sampleapp.properites
// property "com.netflix.config.samples.SampleApp.SampleBean.name" is from subConfig added above
System.out.println("SampleBean:" + sampleBean);
System.out.println(sampleBean.getName());
}
}
输出信息如下
12:32:40.176 [main] INFO com.netflix.config.sources.URLConfigurationSource - URLs to be used as dynamic configuration source: [jar:file:/E:/dev/mvn-resources/com/netflix/archaius/archaius-core/0.7.6/archaius-core-0.7.6.jar!/sampleapp.properties]
12:32:40.188 [main] DEBUG com.netflix.config.DynamicPropertyUpdater - adding property key [com.netflix.config.samples.SampleApp.SampleBean.numSeeds], value [5]
12:32:40.314 [main] INFO com.netflix.config.DynamicPropertyFactory - DynamicPropertyFactory is initialized with configuration sources: com.netflix.config.ConcurrentCompositeConfiguration@7f552bd3
Started SampleApplication. Launch JConsole to inspect and update properties.
To see how callback work, update property com.netflix.config.samples.SampleApp.SampleBean.sensitiveBeanData from BaseConfigBean in JConsole
SampleBean:SampleBean ->name:A Coffee Bean from Cuba numSeeds:DynamicProperty: {name=com.netflix.config.samples.SampleApp.SampleBean.numSeeds, current value=5} sensitiveBeanData:DynamicProperty: {name=com.netflix.config.samples.SampleApp.SampleBean.sensitiveBeanData, current value=value from system property}
A Coffee Bean from Cuba
配置custom.properties数据源, 同时每10s拉取一次数据。如果数据发生变更, 最终会通知DynamicPropertyListener, 来更新内存中的数据。后面源码分析会讲到
@Configuration
public class ArchaiusConfiguration {
@Bean
public AbstractConfiguration customPropertiesConfiguration() {
PolledConfigurationSource polledConfigurationSource = new CustomURLConfigurationSource("classpath:custom.properties");
return new DynamicConfiguration(polledConfigurationSource,
new FixedDelayPollingScheduler(10000, 10000, false));
}
}
custom.properties
archaius.name=cmazxiaomahr
ArchaiusController。如果想要开启JMX,那么设置系统属性System.setProperty(DynamicPropertyFactory.ENABLE_JMX, "true")
。DynamicStringProperties支持回调,当属性发生更变时,则回调到我们业务线程。
@RestController
@RequestMapping("/archaius")
public class ArchaiusController {
/**
* 通过callBack回调
*/
private DynamicStringProperty property1 = DynamicPropertyFactory.getInstance().
getStringProperty("archaius.name", "not found!", new Thread(new Runnable() {
@Override
public void run() {
System.out.println("DynamicStringProperty改变值:" + property1.get());
}
}));
/**
* 通过内部的propertyChange回调
*/
private CustomDynamicStringPropery customDynamicStringPropery =
new CustomDynamicStringPropery("archaius.name", "not found!");
@GetMapping("/get")
public HttpEntity<String> get() {
return new HttpEntity<>(property1.getValue());
}
@GetMapping("/getFromConfigManager")
public HttpEntity<String> getFromConfigManager() {
return new HttpEntity<>(ConfigurationManager.getConfigInstance().getString("archaius.name"));
}
static {
System.setProperty(DynamicPropertyFactory.ENABLE_JMX, "true");
}
@GetMapping("/getEnableJMS")
public HttpEntity<Boolean> getEnableJMS() {
return new HttpEntity<>(Boolean.getBoolean(DynamicPropertyFactory.ENABLE_JMX));
}
}
自定义DynamicStringProperty
,重写propertyChanged
方法。当属性发生变更时,会开启一个线程调用propertyChanged方法通知我们。在这里我们可以结合Spring内置的消息驱动模型,发出一个PropertyChangeEvent。
public class CustomDynamicStringPropery extends DynamicStringProperty {
public CustomDynamicStringPropery(String propName, String defaultValue) {
super(propName, defaultValue);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String get() {
return super.get();
}
@Override
public String getValue() {
return super.getValue();
}
@Override
protected void propertyChanged(String newValue) {
super.propertyChanged(newValue);
SpringContextUtil.getApplicationContext().publishEvent(new PropertyChangeEvent(getName(), newValue));
}
}
自定义PropertyChanged事件
public class PropertyChangeEvent extends ApplicationEvent {
private String key;
private String newValue;
public PropertyChangeEvent() {
super(System.currentTimeMillis());
}
public PropertyChangeEvent(String key, String newValue) {
super(System.currentTimeMillis());
this.key = key;
this.newValue = newValue;
}
public String getKey() {
return key;
}
public String getNewValue() {
return newValue;
}
}
自定义消息监听者, 实际最终会被ApplicationListenerDetector做后置处理。调用this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);
方法把自定义的消息监听者维护到Spring容器中。
@Component
public class PropertyChangeListener implements ApplicationListener<PropertyChangeEvent> {
@Override
public void onApplicationEvent(PropertyChangeEvent event) {
System.out.println("收到新的事件:" + JSON.toJSONString(event));
}
}
自定义数据源
public class CustomURLConfigurationSource extends URLConfigurationSource {
public CustomURLConfigurationSource(String... urls) {
super(urls);
}
public CustomURLConfigurationSource(URL... urls) {
super(urls);
}
public CustomURLConfigurationSource() {
}
@Override
public PollResult poll(boolean initial, Object checkPoint) throws IOException {
System.out.println("checkPoint:" + JSON.toJSONString(checkPoint));
System.out.println("configUrl:" + this.getConfigUrls().toString());
PollResult pollResult = super.poll(initial, checkPoint);
System.out.println("pollResult:" + JSON.toJSONString(pollResult));
return pollResult;
}
}
启动程序, 查看控制台。当我们变更custom.properties中的archaius.name属性, 程序同时已感知变化了。
checkPoint:null
configUrl:[classpath:custom.properties]
pollResult:{"complete":{"archaius.name":"cmazxiaomahr"},"incremental":false}
checkPoint:null
configUrl:[classpath:custom.properties]
pollResult:{"complete":{"archaius.name":"cmazxiaomahrV2"},"incremental":false}
DynamicStringProperty改变值:cmazxiaomahrV2
收到新的事件:{"key":"archaius.name","newValue":"cmazxiaomahrV2","timestamp":1584768086743}
ArchaiusAutoConfiguration
1.获取Spring
容器中的所有的AbstractConfiguration
,如果没有也不要紧。
2.通过ConfigurableEnvironment
创建ConfigurableEnvironmentConfiguration
。
@Configuration
@ConditionalOnClass({ ConcurrentCompositeConfiguration.class,
ConfigurationBuilder.class })
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class ArchaiusAutoConfiguration {
private static final Log log = LogFactory.getLog(ArchaiusAutoConfiguration.class);
private static final AtomicBoolean initialized = new AtomicBoolean(false);
@Autowired
private ConfigurableEnvironment env;
@Autowired(required = false)
private List<AbstractConfiguration> externalConfigurations = new ArrayList<>();
private static DynamicURLConfiguration defaultURLConfig;
@PreDestroy
public void close() {
if (defaultURLConfig != null) {
defaultURLConfig.stopLoading();
}
setStatic(ConfigurationManager.class, "instance", null);
setStatic(ConfigurationManager.class, "customConfigurationInstalled", false);
setStatic(DynamicPropertyFactory.class, "config", null);
setStatic(DynamicPropertyFactory.class, "initializedWithDefaultConfig", false);
setStatic(DynamicProperty.class, "dynamicPropertySupportImpl", null);
initialized.compareAndSet(true, false);
}
@Bean
public static ConfigurableEnvironmentConfiguration configurableEnvironmentConfiguration(ConfigurableEnvironment env,
ApplicationContext context) {
Map<String, AbstractConfiguration> abstractConfigurationMap = context.getBeansOfType(AbstractConfiguration.class);
List<AbstractConfiguration> externalConfigurations = new ArrayList<>(abstractConfigurationMap.values());
ConfigurableEnvironmentConfiguration envConfig = new ConfigurableEnvironmentConfiguration(env);
configureArchaius(envConfig, env, externalConfigurations);
return envConfig;
}
@Configuration
@ConditionalOnClass(Health.class)
protected static class ArchaiusEndpointConfiguration {
@Bean
@ConditionalOnEnabledEndpoint
protected ArchaiusEndpoint archaiusEndpoint() {
return new ArchaiusEndpoint();
}
}
@Configuration
@ConditionalOnProperty(value = "archaius.propagate.environmentChangedEvent", matchIfMissing = true)
@ConditionalOnClass(EnvironmentChangeEvent.class)
protected static class PropagateEventsConfiguration
implements ApplicationListener<EnvironmentChangeEvent> {
@Autowired
private Environment env;
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
AbstractConfiguration manager = ConfigurationManager.getConfigInstance();
for (String key : event.getKeys()) {
for (ConfigurationListener listener : manager
.getConfigurationListeners()) {
Object source = event.getSource();
// TODO: Handle add vs set vs delete?
int type = AbstractConfiguration.EVENT_SET_PROPERTY;
String value = this.env.getProperty(key);
boolean beforeUpdate = false;
listener.configurationChanged(new ConfigurationEvent(source, type,
key, value, beforeUpdate));
}
}
}
}
protected static void configureArchaius(ConfigurableEnvironmentConfiguration envConfig, ConfigurableEnvironment env, List<AbstractConfiguration> externalConfigurations) {
if (initialized.compareAndSet(false, true)) {
String appName = env.getProperty("spring.application.name");
if (appName == null) {
appName = "application";
log.warn("No spring.application.name found, defaulting to 'application'");
}
System.setProperty(DeploymentContext.ContextKey.appId.getKey(), appName);
ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration();
// support to add other Configurations (Jdbc, DynamoDb, Zookeeper, jclouds,
// etc...)
if (externalConfigurations != null) {
for (AbstractConfiguration externalConfig : externalConfigurations) {
config.addConfiguration(externalConfig);
}
}
config.addConfiguration(envConfig,
ConfigurableEnvironmentConfiguration.class.getSimpleName());
defaultURLConfig = new DynamicURLConfiguration();
try {
config.addConfiguration(defaultURLConfig, URL_CONFIG_NAME);
}
catch (Throwable ex) {
log.error("Cannot create config from " + defaultURLConfig, ex);
}
// TODO: sys/env above urls?
if (!Boolean.getBoolean(DISABLE_DEFAULT_SYS_CONFIG)) {
SystemConfiguration sysConfig = new SystemConfiguration();
config.addConfiguration(sysConfig, SYS_CONFIG_NAME);
}
if (!Boolean.getBoolean(DISABLE_DEFAULT_ENV_CONFIG)) {
EnvironmentConfiguration environmentConfiguration = new EnvironmentConfiguration();
config.addConfiguration(environmentConfiguration, ENV_CONFIG_NAME);
}
ConcurrentCompositeConfiguration appOverrideConfig = new ConcurrentCompositeConfiguration();
config.addConfiguration(appOverrideConfig, APPLICATION_PROPERTIES);
config.setContainerConfigurationIndex(
config.getIndexOfConfiguration(appOverrideConfig));
addArchaiusConfiguration(config);
}
else {
// TODO: reinstall ConfigurationManager
log.warn(
"Netflix ConfigurationManager has already been installed, unable to re-install");
}
}
private static void addArchaiusConfiguration(ConcurrentCompositeConfiguration config) {
if (ConfigurationManager.isConfigurationInstalled()) {
AbstractConfiguration installedConfiguration = ConfigurationManager
.getConfigInstance();
if (installedConfiguration instanceof ConcurrentCompositeConfiguration) {
ConcurrentCompositeConfiguration configInstance = (ConcurrentCompositeConfiguration) installedConfiguration;
configInstance.addConfiguration(config);
}
else {
installedConfiguration.append(config);
if (!(installedConfiguration instanceof AggregatedConfiguration)) {
log.warn(
"Appending a configuration to an existing non-aggregated installed configuration will have no effect");
}
}
}
else {
ConfigurationManager.install(config);
}
}
private static void setStatic(Class<?> type, String name, Object value) {
// Hack a private static field
Field field = ReflectionUtils.findField(type, name);
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, null, value);
}
}
ConfigurableEnvironmentConfiguration
内部定义了getPropertySources
,getKeys
,getProperty
方法。通过这些方法,可以获取Spring
容器中的配置信息。
@Override
public Object getProperty(String key) {
return this.environment.getProperty(key);
}
@Override
public Iterator<String> getKeys() {
List<String> result = new ArrayList<>();
for (Map.Entry<String, PropertySource<?>> entry : getPropertySources().entrySet()) {
PropertySource<?> source = entry.getValue();
if (source instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
for (String name : enumerable.getPropertyNames()) {
result.add(name);
}
}
}
return result.iterator();
}
private Map<String, PropertySource<?>> getPropertySources() {
Map<String, PropertySource<?>> map = new LinkedHashMap<>();
MutablePropertySources sources = (this.environment != null ? this.environment
.getPropertySources() : new StandardEnvironment().getPropertySources());
for (PropertySource<?> source : sources) {
extract("", map, source);
}
return map;
实际上是将ConfigurableEnviorment
里面所有的配置项交给Archaius
托管, 把不同数据源合并成ConcurrentCompositeConfiguration
,并且使用ConfigurationManager
进行安装。
这就是为什么没有在ribbon
和feign
源码中看到过ConfigurationProperties
类!
而且在application.properties
中写关于ribbon
和feign
的配置信息时,IDEA一直显示黄色的信息can not resolve
!
我一度怀疑是不是写错了,但是项目启动配置居然生效了。原来是ribbon和feign把配置交给了archaius
托管。
如果没有一点专研精神,我现在还被蒙在鼓里呢!
当配置变更时,会回调PropertyListener
。
动态数据源AbstractConfiguration
都会包装成ConfigurationBackedDynamicPropertySupportImpl
其PropertyListener
也会适配成ExpandedConfigurationListenerAdapter
详细的可以看ConfigurationManager.install()
函数, 内部调用的setDirect(config)
函数后续会讲到
public static synchronized void install(AbstractConfiguration config) throws IllegalStateException {
if (!customConfigurationInstalled) {
setDirect(config);
if (DynamicPropertyFactory.getBackingConfigurationSource() != config) {
DynamicPropertyFactory.initWithConfigurationSource(config);
}
} else {
throw new IllegalStateException("A non-default configuration is already installed");
}
}
DynamicPropertyFactory.initWithConfigurationSource(config)
初始化我们动态数据源Configuration
public static DynamicPropertyFactory initWithConfigurationSource(AbstractConfiguration config) {
synchronized (ConfigurationManager.class) {
if (config == null) {
throw new NullPointerException("config is null");
}
if (ConfigurationManager.isConfigurationInstalled() && config != ConfigurationManager.instance) {
throw new IllegalStateException("ConfigurationManager is already initialized with configuration "
+ ConfigurationManager.getConfigInstance());
}
if (config instanceof DynamicPropertySupport) {
return initWithConfigurationSource((DynamicPropertySupport) config);
}
return initWithConfigurationSource(new ConfigurationBackedDynamicPropertySupportImpl(config));
}
}
具体的流程如下, 毕竟简单, 一下就能看懂。
public static DynamicPropertyFactory initWithConfigurationSource(DynamicPropertySupport dynamicPropertySupport) {
synchronized (ConfigurationManager.class) {
if (dynamicPropertySupport == null) {
throw new IllegalArgumentException("dynamicPropertySupport is null");
}
AbstractConfiguration configuration = null;
if (dynamicPropertySupport instanceof AbstractConfiguration) {
configuration = (AbstractConfiguration) dynamicPropertySupport;
} else if (dynamicPropertySupport instanceof ConfigurationBackedDynamicPropertySupportImpl) {
configuration = ((ConfigurationBackedDynamicPropertySupportImpl) dynamicPropertySupport).getConfiguration();
}
if (initializedWithDefaultConfig) {
config = null;
} else if (config != null && config != dynamicPropertySupport) {
throw new IllegalStateException("DynamicPropertyFactory is already initialized with a diffrerent configuration source: " + config);
}
if (ConfigurationManager.isConfigurationInstalled()
&& (configuration != null && configuration != ConfigurationManager.instance)) {
throw new IllegalStateException("ConfigurationManager is already initialized with configuration "
+ ConfigurationManager.getConfigInstance());
}
if (configuration != null && configuration != ConfigurationManager.instance) {
ConfigurationManager.setDirect(configuration);
}
setDirect(dynamicPropertySupport);
return instance;
}
}
设置DynamicPropertyFactory
内部的config
为当前包装好的ConfigurationBackedDynamicPropertySupportImpl
对象
static void setDirect(DynamicPropertySupport support) {
synchronized (ConfigurationManager.class) {
config = support;
DynamicProperty.registerWithDynamicPropertySupport(support);
initializedWithDefaultConfig = false;
}
}
配置DynamicPropertyListener
static void registerWithDynamicPropertySupport(DynamicPropertySupport config) {
initialize(config);
}
static synchronized void initialize(DynamicPropertySupport config) {
dynamicPropertySupportImpl = config;
config.addConfigurationListener(new DynamicPropertyListener());
updateAllProperties();
}
在AbstractPollingScheduler
中获取pollResult
结果之后,就会调用populateProperties(result, config)
填充配置里面的属性
protected Runnable getPollingRunnable(final PolledConfigurationSource source, final Configuration config) {
return new Runnable() {
public void run() {
log.debug("Polling started");
PollResult result = null;
try {
result = source.poll(false, getNextCheckPoint(checkPoint));
checkPoint = result.getCheckPoint();
fireEvent(EventType.POLL_SUCCESS, result, null);
} catch (Throwable e) {
log.error("Error getting result from polling source", e);
fireEvent(EventType.POLL_FAILURE, null, e);
return;
}
try {
populateProperties(result, config);
} catch (Throwable e) {
log.error("Error occured applying properties", e);
}
}
};
}
实际上配置更新删除的操作全是委托给DynamicPropertyUpdater
完成的。
protected void populateProperties(final PollResult result, final Configuration config) {
if (result == null || !result.hasChanges()) {
return;
}
if (!result.isIncremental()) {
Map<String, Object> props = result.getComplete();
if (props == null) {
return;
}
for (Entry<String, Object> entry: props.entrySet()) {
propertyUpdater.addOrChangeProperty(entry.getKey(), entry.getValue(), config);
}
HashSet<String> existingKeys = new HashSet<String>();
for (Iterator<String> i = config.getKeys(); i.hasNext();) {
existingKeys.add(i.next());
}
if (!ignoreDeletesFromSource) {
for (String key: existingKeys) {
if (!props.containsKey(key)) {
propertyUpdater.deleteProperty(key, config);
}
}
}
} else {
Map<String, Object> props = result.getAdded();
if (props != null) {
for (Entry<String, Object> entry: props.entrySet()) {
propertyUpdater.addOrChangeProperty(entry.getKey(), entry.getValue(), config);
}
}
props = result.getChanged();
if (props != null) {
for (Entry<String, Object> entry: props.entrySet()) {
propertyUpdater.addOrChangeProperty(entry.getKey(), entry.getValue(), config);
}
}
if (!ignoreDeletesFromSource) {
props = result.getDeleted();
if (props != null) {
for (String name: props.keySet()) {
propertyUpdater.deleteProperty(name, config);
}
}
}
}
}
最终会调用AbstractConfiguration
中的addProperty
函数,同时发布EVENT_ADD_PROPERTY
事件
public void addProperty(String key, Object value)
{
fireEvent(EVENT_ADD_PROPERTY, key, value, true);
addPropertyValues(key, value,
isDelimiterParsingDisabled() ? DISABLED_DELIMITER
: getListDelimiter());
fireEvent(EVENT_ADD_PROPERTY, key, value, false);
}
protected void fireEvent(int type, String propName, Object propValue, boolean before)
{
if (checkDetailEvents(-1))
{
Iterator<ConfigurationListener> it = listeners.iterator();
if (it.hasNext())
{
ConfigurationEvent event =
createEvent(type, propName, propValue, before);
while (it.hasNext())
{
it.next().configurationChanged(event);
}
}
}
}
最终DynamicPropertyListener
收到了事件通知,先检验key
,value
。然后更新键值
@Override
public void addProperty(Object source, String name, Object value, boolean beforeUpdate) {
if (!beforeUpdate) {
updateProperty(name, value);
} else {
validate(name, value);
}
}
更新当前的DynamicProperty
的键值,以及changedTime
和关于值的缓存信息
private static final ConcurrentHashMap<String, DynamicProperty> ALL_PROPS
= new ConcurrentHashMap<String, DynamicProperty>();
private static boolean updateProperty(String propName, Object value) {
DynamicProperty prop = ALL_PROPS.get(propName);
if (prop != null && prop.updateValue(value)) {
prop.notifyCallbacks();
return true;
}
return false;
}
回调CallBack
,进行自己的业务操作
private void notifyCallbacks() {
for (Runnable r : callbacks) {
try {
r.run();
} catch (Exception e) {
logger.error("Error in DynamicProperty callback", e);
}
}
}
关于怎么聚合多个数据源, 可以看ConcurrentCompositeConfiguration
内部有一个ConcurrentMapConfiguration
属性。
实际上多个数据源的原始信息都维护在ConcurrentMapConfiguration
类的map
中
containerConfiguration = new ConcurrentMapConfiguration();
把多个数据源信息聚合到CurrentCompositeConfiguration
中,调用ConfigurationManager.install(config);
接着调用setDirect()
,获取所有数据源中的keys
信息,再调用每个数据源的getProperty(key)
获取值。
这些property
最终都交给了上文中的ConcurrentMapConfiguration
中的map
管理。
static synchronized void setDirect(AbstractConfiguration config) {
if (instance != null) {
Collection<ConfigurationListener> listeners = instance.getConfigurationListeners();
// transfer listeners
// transfer properties which are not in conflict with new configuration
for (Iterator<String> i = instance.getKeys(); i.hasNext();) {
String key = i.next();
Object value = instance.getProperty(key);
if (value != null && !config.containsKey(key)) {
config.setProperty(key, value);
}
}
if (listeners != null) {
for (ConfigurationListener listener: listeners) {
if (listener instanceof ExpandedConfigurationListenerAdapter
&& ((ExpandedConfigurationListenerAdapter) listener).getListener()
instanceof DynamicProperty.DynamicPropertyListener) {
// no need to transfer the fast property listener as it should be set later
// with the new configuration
continue;
}
config.addConfigurationListener(listener);
}
}
}
ConfigurationManager.removeDefaultConfiguration();
ConfigurationManager.instance = config;
ConfigurationManager.customConfigurationInstalled = true;
ConfigurationManager.registerConfigBean();
}
上文中调用到setProperty
函数, 首先把原始的key
,value
维护到map
中。
接着发布EVENT_SET_PROPERTY
事件,流程又回到上文中的DynamicPropertyListener
@Override
public void setProperty(String key, Object value) throws ValidationException
{
if (value == null) {
throw new NullPointerException("Value for property " + key + " is null");
}
fireEvent(EVENT_SET_PROPERTY, key, value, true);
setPropertyImpl(key, value);
fireEvent(EVENT_SET_PROPERTY, key, value, false);
}
protected void setPropertyImpl(String key, Object value) {
if (isDelimiterParsingDisabled()) {
map.put(key, value);
} else if ((value instanceof String) && ((String) value).indexOf(getListDelimiter()) < 0) {
map.put(key, value);
} else {
Iterator it = PropertyConverter.toIterator(value, getListDelimiter());
List<Object> list = new CopyOnWriteArrayList<Object>();
while (it.hasNext())
{
list.add(it.next());
}
if (list.size() == 1) {
map.put(key, list.get(0));
} else {
map.put(key, list);
}
}
}
还是有必要讲一下DynamicStringProperty
的父类
SUBCLASSES_WITH_NO_CALLBACK
里面维护了数据发生改变时不需要回调propertyChanged()
的Property
对象
如果明确知道PropertyWrapper
不需要注册回调PropertyChanged
回调的话,那就不注册呗
public abstract class PropertyWrapper<V> implements Property<V> {
protected final DynamicProperty prop;
protected final V defaultValue;
@SuppressWarnings("rawtypes")
private static final IdentityHashMap<Class<? extends PropertyWrapper>, Object> SUBCLASSES_WITH_NO_CALLBACK
= new IdentityHashMap<Class<? extends PropertyWrapper>, Object>();
private static final Logger logger = LoggerFactory.getLogger(PropertyWrapper.class);
private final List<Runnable> callbackList = Lists.newArrayList();
static {
PropertyWrapper.registerSubClassWithNoCallback(DynamicIntProperty.class);
PropertyWrapper.registerSubClassWithNoCallback(DynamicStringProperty.class);
PropertyWrapper.registerSubClassWithNoCallback(DynamicBooleanProperty.class);
PropertyWrapper.registerSubClassWithNoCallback(DynamicFloatProperty.class);
PropertyWrapper.registerSubClassWithNoCallback(DynamicLongProperty.class);
PropertyWrapper.registerSubClassWithNoCallback(DynamicDoubleProperty.class);
}
private static final Object DUMMY_VALUE = new Object();
/**
* By default, a subclass of PropertyWrapper will automatically register {@link #propertyChanged()} as a callback
* for property value change. This method provide a way for a subclass to avoid this overhead if it is not interested
* to get callback.
*
* @param c
*/
public static final void registerSubClassWithNoCallback(@SuppressWarnings("rawtypes") Class<? extends PropertyWrapper> c) {
SUBCLASSES_WITH_NO_CALLBACK.put(c, DUMMY_VALUE);
}
protected PropertyWrapper(String propName, V defaultValue) {
this.prop = DynamicProperty.getInstance(propName);
this.defaultValue = defaultValue;
Class<?> c = getClass();
// this checks whether this constructor is called by a class that
// extends the immediate sub classes (IntProperty, etc.) of PropertyWrapper.
// If yes, it is very likely that propertyChanged() is overriden
// in the sub class and we need to register the callback.
// Otherwise, we know that propertyChanged() does nothing in
// immediate subclasses and we can avoid registering the callback, which
// has the cost of modifying the CopyOnWriteArraySet
if (!SUBCLASSES_WITH_NO_CALLBACK.containsKey(c)) {
Runnable callback = new Runnable() {
public void run() {
propertyChanged();
}
};
this.prop.addCallback(callback);
callbackList.add(callback);
this.prop.addValidator(new PropertyChangeValidator() {
@Override
public void validate(String newValue) {
PropertyWrapper.this.validate(newValue);
}
});
try {
if (this.prop.getString() != null) {
this.validate(this.prop.getString());
}
} catch (ValidationException e) {
logger.warn("Error validating property at initialization. Will fallback to default value.", e);
prop.updateValue(defaultValue);
}
}
}
@Override
public String getName() {
return prop.getName();
}
public void addValidator(PropertyChangeValidator v) {
if (v != null) {
prop.addValidator(v);
}
}
/**
* Called when the property value is updated.
* The default does nothing.
* Subclasses are free to override this if desired.
*/
protected void propertyChanged() {
propertyChanged(getValue());
}
为什么会有这篇文章呢,因为我在看Ribbon源码的时候,很好奇DefaultClientConfigImpl是怎么获取到application.properties中的配置信息,很是纠结,于是有了这篇文章。