美文网首页
看Ribbon源码增值的Archaius源码分析

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

作者: cmazxiaoma | 来源:发表于2020-03-25 18:31 被阅读0次

    前言

    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中的配置信息,很是纠结,于是有了这篇文章。

    相关文章

      网友评论

          本文标题:看Ribbon源码增值的Archaius源码分析

          本文链接:https://www.haomeiwen.com/subject/wgdyyhtx.html