前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >看Ribbon源码增值的Archaius源码分析

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

作者头像
用户2032165
发布2020-03-27 11:12:12
1.6K0
发布2020-03-27 11:12:12
举报
文章被收录于专栏:cmazxiaoma的架构师之路

前言

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

image.png

Archaius的优点如下:

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

官方提供的Demo入门

代码语言:javascript
复制
/**
 * @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();
    };
}
代码语言:javascript
复制
/**
 * @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

代码语言:javascript
复制
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

多个数据源配置信息合并操作

代码语言:javascript
复制
/**
 * @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());
    }

}

输出信息如下

代码语言:javascript
复制
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, 来更新内存中的数据。后面源码分析会讲到

代码语言:javascript
复制
@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

代码语言:javascript
复制
archaius.name=cmazxiaomahr

ArchaiusController。如果想要开启JMX,那么设置系统属性System.setProperty(DynamicPropertyFactory.ENABLE_JMX, "true")。DynamicStringProperties支持回调,当属性发生更变时,则回调到我们业务线程。

代码语言:javascript
复制
@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。

代码语言:javascript
复制
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事件

代码语言:javascript
复制
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容器中。

代码语言:javascript
复制
@Component
public class PropertyChangeListener implements ApplicationListener<PropertyChangeEvent> {

    @Override
    public void onApplicationEvent(PropertyChangeEvent event) {
        System.out.println("收到新的事件:" + JSON.toJSONString(event));
    }
}

自定义数据源

代码语言:javascript
复制
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属性, 程序同时已感知变化了。

代码语言:javascript
复制
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

代码语言:javascript
复制
@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容器中的配置信息。

代码语言:javascript
复制
@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都会包装成ConfigurationBackedDynamicPropertySupportImplPropertyListener也会适配成ExpandedConfigurationListenerAdapter

详细的可以看ConfigurationManager.install()函数, 内部调用的setDirect(config)函数后续会讲到

代码语言:javascript
复制
    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

代码语言:javascript
复制
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));
        }
    }

具体的流程如下, 毕竟简单, 一下就能看懂。

代码语言:javascript
复制
    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对象

代码语言:javascript
复制
    static void setDirect(DynamicPropertySupport support) {
        synchronized (ConfigurationManager.class) {
            config = support;
            DynamicProperty.registerWithDynamicPropertySupport(support);
            initializedWithDefaultConfig = false;
        }
    }

配置DynamicPropertyListener

代码语言:javascript
复制
    static void registerWithDynamicPropertySupport(DynamicPropertySupport config) {
        initialize(config);
    }
代码语言:javascript
复制
    static synchronized void initialize(DynamicPropertySupport config) {
        dynamicPropertySupportImpl = config;
        config.addConfigurationListener(new DynamicPropertyListener());
        updateAllProperties();
    }

AbstractPollingScheduler中获取pollResult结果之后,就会调用populateProperties(result, config)填充配置里面的属性

代码语言:javascript
复制
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完成的。

代码语言:javascript
复制
    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事件

代码语言:javascript
复制
    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);
    }
代码语言:javascript
复制
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。然后更新键值

代码语言:javascript
复制
@Override
        public void addProperty(Object source, String name, Object value, boolean beforeUpdate) {
            if (!beforeUpdate) {
                updateProperty(name, value);
            } else {
                validate(name, value);
            }
        }

更新当前的DynamicProperty的键值,以及changedTime和关于值的缓存信息

代码语言:javascript
复制
    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,进行自己的业务操作

代码语言:javascript
复制
private void notifyCallbacks() {
        for (Runnable r : callbacks) {
            try {
                r.run();
            } catch (Exception e) {
                logger.error("Error in DynamicProperty callback", e);
            }
        }
    }

关于怎么聚合多个数据源, 可以看ConcurrentCompositeConfiguration 内部有一个ConcurrentMapConfiguration属性。 实际上多个数据源的原始信息都维护在ConcurrentMapConfiguration类的map

代码语言:javascript
复制
containerConfiguration = new ConcurrentMapConfiguration();

把多个数据源信息聚合到CurrentCompositeConfiguration中,调用ConfigurationManager.install(config); 接着调用setDirect(),获取所有数据源中的keys信息,再调用每个数据源的getProperty(key)获取值。 这些property最终都交给了上文中的ConcurrentMapConfiguration中的map管理。

代码语言:javascript
复制
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

代码语言:javascript
复制
    @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);
    }
代码语言:javascript
复制
    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回调的话,那就不注册呗

代码语言:javascript
复制
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中的配置信息,很是纠结,于是有了这篇文章。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 官方提供的Demo入门
  • 与SpringBoot整合例子
  • 源码分析
  • 尾言
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档