看Ribbon源码增值的Archaius源码分析

前言

Netflix Archaius 是一个功能强大的配置管理库。它是一个可用于从许多不同来源收集配置属性的框架,提供对配置信息的快速及线程安全访问。

image.png

Archaius的优点如下:

  • 配置可动态调整。
  • 配置支持类型(Int, Long, Boolean等)。
  • 高性能和线程安全。
  • 提供一个拉(pulling)配置的框架,可以从配置源动态拉取变更的配置。
  • 支持回调(callback)机制,在配置变更时自动调用。
  • 支持JMX MBean,可以通过JConsole查看配置和修改配置。

官方提供的Demo入门

/**
 * @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

与SpringBoot整合例子

配置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进行安装。

这就是为什么没有在ribbonfeign源码中看到过ConfigurationProperties类!

而且在application.properties中写关于ribbonfeign的配置信息时,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中的配置信息,很是纠结,于是有了这篇文章。

推荐阅读更多精彩内容