ソースを参照

实现配置中心的基础

YunaiV 4 年 前
コミット
ec9ed896f9
23 ファイル変更1250 行追加0 行削除
  1. 28 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/Config.java
  2. 19 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/ConfigChangeListener.java
  3. 17 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/DBConfig.java
  4. 14 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/enums/PropertyChangeType.java
  5. 35 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/model/ConfigChange.java
  6. 53 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/model/ConfigChangeEvent.java
  7. 16 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/package-info.java
  8. 68 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/spring/annotation/ApolloProcessor.java
  9. 141 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/spring/annotation/SpringValueProcessor.java
  10. 34 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/spring/boot/ApolloApplicationContextInitializer.java
  11. 18 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/spring/boot/ApolloAutoConfiguration.java
  12. 39 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/spring/config/ConfigPropertySource.java
  13. 42 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/spring/config/ConfigPropertySourcesProcessor.java
  14. 7 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/spring/config/PropertySourcesConstants.java
  15. 159 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/AutoUpdateConfigChangeListener.java
  16. 160 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/PlaceholderHelper.java
  17. 53 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/PropertySourcesProcessor.java
  18. 152 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/SpringValue.java
  19. 28 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/SpringValueDefinition.java
  20. 94 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/SpringValueDefinitionProcessor.java
  21. 31 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/SpringValueRegistry.java
  22. 38 0
      src/main/java/cn/iocoder/dashboard/framework/apollox/spring/util/BeanRegistrationUtil.java
  23. 4 0
      src/main/resources/META-INF/spring.factories

+ 28 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/Config.java

@@ -0,0 +1,28 @@
+package cn.iocoder.dashboard.framework.apollox;
+
+import java.util.Set;
+
+/**
+ * 配置接口
+ *
+ * @author Jason Song(song_s@ctrip.com)
+ */
+public interface Config {
+
+    /**
+     * Return the property value with the given key, or {@code defaultValue} if the key doesn't exist.
+     *
+     * @param key          the property name
+     * @param defaultValue the default value when key is not found or any error occurred
+     * @return the property value
+     */
+    String getProperty(String key, String defaultValue);
+
+    /**
+     * Return a set of the property names
+     *
+     * @return the property names
+     */
+    Set<String> getPropertyNames();
+
+}

+ 19 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/ConfigChangeListener.java

@@ -0,0 +1,19 @@
+package cn.iocoder.dashboard.framework.apollox;
+
+import cn.iocoder.dashboard.framework.apollox.model.ConfigChangeEvent;
+
+/**
+ * {@link Config} 变化监听器
+ *
+ * @author Jason Song(song_s@ctrip.com)
+ */
+public interface ConfigChangeListener {
+
+    /**
+     * Invoked when there is any config change for the namespace.
+     *
+     * @param changeEvent the event for this change
+     */
+    void onChange(ConfigChangeEvent changeEvent);
+
+}

+ 17 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/DBConfig.java

@@ -0,0 +1,17 @@
+package cn.iocoder.dashboard.framework.apollox;
+
+import java.util.Set;
+
+public class DBConfig implements Config {
+
+    @Override
+    public String getProperty(String key, String defaultValue) {
+        return null;
+    }
+
+    @Override
+    public Set<String> getPropertyNames() {
+        return null;
+    }
+
+}

+ 14 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/enums/PropertyChangeType.java

@@ -0,0 +1,14 @@
+package cn.iocoder.dashboard.framework.apollox.enums;
+
+/**
+ * 属性变化类型枚举
+ *
+ * @author Jason Song(song_s@ctrip.com)
+ */
+public enum PropertyChangeType {
+
+    ADDED, // 添加
+    MODIFIED, // 修改
+    DELETED // 删除
+
+}

+ 35 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/model/ConfigChange.java

@@ -0,0 +1,35 @@
+package cn.iocoder.dashboard.framework.apollox.model;
+
+
+import cn.iocoder.dashboard.framework.apollox.enums.PropertyChangeType;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * Holds the information for a config change.
+ * 配置每个属性变化的信息
+ *
+ * @author Jason Song(song_s@ctrip.com)
+ */
+@Data
+@AllArgsConstructor
+public class ConfigChange {
+
+    /**
+     * 属性名
+     */
+    private final String propertyName;
+    /**
+     * 老值
+     */
+    private String oldValue;
+    /**
+     * 新值
+     */
+    private String newValue;
+    /**
+     * 变化类型
+     */
+    private PropertyChangeType changeType;
+
+}

+ 53 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/model/ConfigChangeEvent.java

@@ -0,0 +1,53 @@
+package cn.iocoder.dashboard.framework.apollox.model;
+
+import lombok.AllArgsConstructor;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A change event when a namespace's config is changed.
+ *
+ * @author Jason Song(song_s@ctrip.com)
+ */
+@AllArgsConstructor
+public class ConfigChangeEvent {
+
+    /**
+     * 变化属性的集合
+     *
+     * KEY:属性名
+     * VALUE:配置变化
+     */
+    private final Map<String, ConfigChange> m_changes;
+
+    /**
+     * Get the keys changed.
+     *
+     * @return the list of the keys
+     */
+    public Set<String> changedKeys() {
+        return m_changes.keySet();
+    }
+
+    /**
+     * Get a specific change instance for the key specified.
+     *
+     * @param key the changed key
+     * @return the change instance
+     */
+    public ConfigChange getChange(String key) {
+        return m_changes.get(key);
+    }
+
+    /**
+     * Check whether the specified key is changed
+     *
+     * @param key the key
+     * @return true if the key is changed, false otherwise.
+     */
+    public boolean isChanged(String key) {
+        return m_changes.containsKey(key);
+    }
+
+}

+ 16 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/package-info.java

@@ -0,0 +1,16 @@
+/**
+ * 配置中心客户端,基于 Apollo Client 实现,所以叫 ApolloX
+ *
+ * 差别在于,我们使用 {@link cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.config.SysConfigDO} 表作为配置源。
+ * 当然,功能肯定也会相对少些,满足最小化诉求。
+ *
+ * 1. 项目初始化时,可以使用 SysConfigDO 表的配置
+ * 2. 使用 Spring @Value 可以注入属性
+ * 3. SysConfigDO 表的配置修改时,注入到 @Value 的属性可以刷新
+ *
+ * 另外,整个包结构会参考 Apollo 为主,方便维护与理解
+ *
+ * 注意,目前有两个特性是不支持的
+ * 1. 自定义配置变化的监听器
+ */
+package cn.iocoder.dashboard.framework.apollox;

+ 68 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/annotation/ApolloProcessor.java

@@ -0,0 +1,68 @@
+package cn.iocoder.dashboard.framework.apollox.spring.annotation;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.core.Ordered;
+import org.springframework.core.PriorityOrdered;
+import org.springframework.util.ReflectionUtils;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Apollo 处理器抽象类,封装了在 Spring Bean 初始化之前,处理属性和方法。
+ *
+ * Create by zhangzheng on 2018/2/6
+ */
+public abstract class ApolloProcessor implements BeanPostProcessor, PriorityOrdered {
+
+    @Override
+    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
+        Class<?> clazz = bean.getClass();
+        // 处理所有 Field
+        for (Field field : findAllField(clazz)) {
+            processField(bean, beanName, field);
+        }
+        // 处理所有的 Method
+        for (Method method : findAllMethod(clazz)) {
+            processMethod(bean, beanName, method);
+        }
+        return bean;
+    }
+
+    @Override
+    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+        return bean;
+    }
+
+    /**
+     * subclass should implement this method to process field
+     */
+    protected abstract void processField(Object bean, String beanName, Field field);
+
+    /**
+     * subclass should implement this method to process method
+     */
+    protected abstract void processMethod(Object bean, String beanName, Method method);
+
+    @Override
+    public int getOrder() {
+        // make it as late as possible
+        return Ordered.LOWEST_PRECEDENCE; // 最高优先级
+    }
+
+    private List<Field> findAllField(Class<?> clazz) {
+        final List<Field> res = new LinkedList<>();
+        ReflectionUtils.doWithFields(clazz, res::add);
+        return res;
+    }
+
+    private List<Method> findAllMethod(Class<?> clazz) {
+        final List<Method> res = new LinkedList<>();
+        ReflectionUtils.doWithMethods(clazz, res::add);
+        return res;
+    }
+
+}

+ 141 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/annotation/SpringValueProcessor.java

@@ -0,0 +1,141 @@
+package cn.iocoder.dashboard.framework.apollox.spring.annotation;
+
+import cn.hutool.core.lang.Singleton;
+import cn.iocoder.dashboard.framework.apollox.spring.property.*;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Multimap;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.context.annotation.Bean;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Spring value processor of field or method which has @Value and xml config placeholders.
+ *
+ * Spring Value 处理器,处理:
+ *
+ * 1. 带有 `@Value` 注解的 Field 和 Method
+ * 2. XML 配置的 Bean 的 PlaceHolder 们
+ *
+ * 每个 Field、Method、XML PlaceHolder 被处理成一个 SpringValue 对象,添加到 SpringValueRegistry 中。
+ *
+ * 目的还是,为了 PlaceHolder 的自动更新机制。
+ *
+ * @author github.com/zhegexiaohuozi  seimimaster@gmail.com
+ * @since 2017/12/20.
+ */
+@Slf4j
+public class SpringValueProcessor extends ApolloProcessor implements BeanFactoryPostProcessor {
+
+    /**
+     * SpringValueDefinition 集合
+     *
+     * KEY:beanName
+     * VALUE:SpringValueDefinition 集合
+     */
+    private static Multimap<String, SpringValueDefinition> beanName2SpringValueDefinitions = LinkedListMultimap.create();
+
+    private final PlaceholderHelper placeholderHelper = Singleton.get(PlaceholderHelper.class);
+    private final SpringValueRegistry springValueRegistry = Singleton.get(SpringValueRegistry.class);
+
+    @Override
+    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+        beanName2SpringValueDefinitions = SpringValueDefinitionProcessor.getBeanName2SpringValueDefinitions();
+    }
+
+    @Override
+    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
+        // 处理 Field 和 Method
+        super.postProcessBeforeInitialization(bean, beanName);
+        // 处理 XML 配置的 Bean 的 PlaceHolder 们
+        processBeanPropertyValues(bean, beanName);
+        return bean;
+    }
+
+    @Override
+    protected void processField(Object bean, String beanName, Field field) {
+        // register @Value on field
+        Value value = field.getAnnotation(Value.class);
+        if (value == null) {
+            return;
+        }
+        // 提取 `keys` 属性们。
+        Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
+        if (keys.isEmpty()) {
+            return;
+        }
+        // 循环 `keys` ,创建对应的 SpringValue 对象,并添加到 `springValueRegistry` 中。
+        for (String key : keys) {
+            SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
+            springValueRegistry.register(key, springValue);
+            log.debug("Monitoring {}", springValue);
+        }
+    }
+
+    @Override
+    protected void processMethod(Object bean, String beanName, Method method) {
+        // register @Value on method
+        Value value = method.getAnnotation(Value.class);
+        if (value == null) {
+            return;
+        }
+        // 忽略 @Bean 注解的方法
+        // skip Configuration bean methods
+        if (method.getAnnotation(Bean.class) != null) {
+            return;
+        }
+        // 忽略非 setting 方法
+        if (method.getParameterTypes().length != 1) {
+            log.error("Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters", bean.getClass().getName(), method.getName(), method.getParameterTypes().length);
+            return;
+        }
+        // 提取 `keys` 属性们。
+        Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
+        if (keys.isEmpty()) {
+            return;
+        }
+        // 循环 `keys` ,创建对应的 SpringValue 对象,并添加到 `springValueRegistry` 中。
+        for (String key : keys) {
+            SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, method, false);
+            springValueRegistry.register(key, springValue);
+            log.info("Monitoring {}", springValue);
+        }
+    }
+
+    private void processBeanPropertyValues(Object bean, String beanName) {
+        // 获得 SpringValueDefinition 数组
+        Collection<SpringValueDefinition> propertySpringValues = beanName2SpringValueDefinitions.get(beanName);
+        if (propertySpringValues == null || propertySpringValues.isEmpty()) {
+            return;
+        }
+        // 循环 SpringValueDefinition 数组,创建对应的 SpringValue 对象,并添加到 `springValueRegistry` 中。
+        for (SpringValueDefinition definition : propertySpringValues) {
+            try {
+                PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(bean.getClass(), definition.getPropertyName());
+                Method method = pd.getWriteMethod();
+                if (method == null) {
+                    continue;
+                }
+                SpringValue springValue = new SpringValue(definition.getKey(), definition.getPlaceholder(), bean, beanName, method, false);
+                springValueRegistry.register(definition.getKey(), springValue);
+                log.debug("Monitoring {}", springValue);
+            } catch (Throwable ex) {
+                log.error("Failed to enable auto update feature for {}.{}", bean.getClass(), definition.getPropertyName());
+            }
+        }
+
+        // clear
+        // 移除 Bean 对应的 SpringValueDefinition 数组
+        beanName2SpringValueDefinitions.removeAll(beanName);
+    }
+
+}

+ 34 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/boot/ApolloApplicationContextInitializer.java

@@ -0,0 +1,34 @@
+package cn.iocoder.dashboard.framework.apollox.spring.boot;
+
+
+import cn.hutool.core.lang.Singleton;
+import cn.iocoder.dashboard.framework.apollox.Config;
+import cn.iocoder.dashboard.framework.apollox.DBConfig;
+import cn.iocoder.dashboard.framework.apollox.spring.config.ConfigPropertySource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.env.ConfigurableEnvironment;
+
+import static cn.iocoder.dashboard.framework.apollox.spring.config.PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME;
+
+@Slf4j
+public class ApolloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
+
+    @Override
+    public void initialize(ConfigurableApplicationContext context) {
+        ConfigurableEnvironment environment = context.getEnvironment();
+        // 忽略,若已经有 APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME 的 PropertySource
+        if (environment.getPropertySources().contains(APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
+            // already initialized
+            return;
+        }
+
+        // 创建自定义的 APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME 的 PropertySource
+        Config config = Singleton.get(DBConfig.class);
+        ConfigPropertySource configPropertySource = new ConfigPropertySource(APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME, config);
+        // 添加到 `environment` 中,且优先级最高
+        environment.getPropertySources().addFirst(configPropertySource);
+    }
+
+}

+ 18 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/boot/ApolloAutoConfiguration.java

@@ -0,0 +1,18 @@
+package cn.iocoder.dashboard.framework.apollox.spring.boot;
+
+import cn.iocoder.dashboard.framework.apollox.spring.config.ConfigPropertySourcesProcessor;
+import cn.iocoder.dashboard.framework.apollox.spring.property.PropertySourcesProcessor;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConditionalOnMissingBean(PropertySourcesProcessor.class) // 缺失 PropertySourcesProcessor 时
+public class ApolloAutoConfiguration {
+
+    @Bean
+    public ConfigPropertySourcesProcessor configPropertySourcesProcessor() {
+        return new ConfigPropertySourcesProcessor(); // 注入 ConfigPropertySourcesProcessor bean 对象
+    }
+
+}

+ 39 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/config/ConfigPropertySource.java

@@ -0,0 +1,39 @@
+package cn.iocoder.dashboard.framework.apollox.spring.config;
+
+import cn.iocoder.dashboard.framework.apollox.Config;
+import org.springframework.core.env.EnumerablePropertySource;
+
+import java.util.Set;
+
+/**
+ * Property source wrapper for Config
+ *
+ * 基于 {@link Config} 的 PropertySource 实现类
+ *
+ * @author Jason Song(song_s@ctrip.com)
+ */
+public class ConfigPropertySource extends EnumerablePropertySource<Config> {
+
+    private static final String[] EMPTY_ARRAY = new String[0];
+
+    public ConfigPropertySource(String name, Config source) { // 此处的 Apollo Config 作为 `source`
+        super(name, source);
+    }
+
+    @Override
+    public String[] getPropertyNames() {
+        // 从 Config 中,获得属性名集合
+        Set<String> propertyNames = this.source.getPropertyNames();
+        // 转换成 String 数组,返回
+        if (propertyNames.isEmpty()) {
+            return EMPTY_ARRAY;
+        }
+        return propertyNames.toArray(new String[0]);
+    }
+
+    @Override
+    public Object getProperty(String name) {
+        return this.source.getProperty(name, null);
+    }
+
+}

+ 42 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/config/ConfigPropertySourcesProcessor.java

@@ -0,0 +1,42 @@
+package cn.iocoder.dashboard.framework.apollox.spring.config;
+
+import cn.iocoder.dashboard.framework.apollox.spring.annotation.SpringValueProcessor;
+import cn.iocoder.dashboard.framework.apollox.spring.property.PropertySourcesProcessor;
+import cn.iocoder.dashboard.framework.apollox.spring.property.SpringValueDefinitionProcessor;
+import cn.iocoder.dashboard.framework.apollox.spring.util.BeanRegistrationUtil;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
+import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
+
+/**
+ * Apollo Property Sources processor for Spring XML Based Application
+ *
+ * @author Jason Song(song_s@ctrip.com)
+ */
+public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor implements BeanDefinitionRegistryPostProcessor {
+
+    @Override
+    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
+        // 注册 PropertySourcesPlaceholderConfigurer 到 BeanDefinitionRegistry 中,替换 PlaceHolder 为对应的属性值,参考文章 https://leokongwq.github.io/2016/12/28/spring-PropertyPlaceholderConfigurer.html
+        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(), PropertySourcesPlaceholderConfigurer.class);
+        // 注册 SpringValueProcessor 到 BeanDefinitionRegistry 中,用于 PlaceHolder 自动更新机制
+        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class);
+
+        // 处理 XML 配置的 Spring PlaceHolder
+        processSpringValueDefinition(registry);
+    }
+
+    /**
+     * For Spring 3.x versions, the BeanDefinitionRegistryPostProcessor would not be
+     * instantiated if it is added in postProcessBeanDefinitionRegistry phase, so we have to manually
+     * call the postProcessBeanDefinitionRegistry method of SpringValueDefinitionProcessor here...
+     */
+    private void processSpringValueDefinition(BeanDefinitionRegistry registry) {
+        // 创建 SpringValueDefinitionProcessor 对象
+        SpringValueDefinitionProcessor springValueDefinitionProcessor = new SpringValueDefinitionProcessor();
+        // 处理 XML 配置的 Spring PlaceHolder
+        springValueDefinitionProcessor.postProcessBeanDefinitionRegistry(registry);
+    }
+
+}

+ 7 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/config/PropertySourcesConstants.java

@@ -0,0 +1,7 @@
+package cn.iocoder.dashboard.framework.apollox.spring.config;
+
+public interface PropertySourcesConstants {
+
+  String APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME = "ApolloBootstrapPropertySources";
+
+}

+ 159 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/AutoUpdateConfigChangeListener.java

@@ -0,0 +1,159 @@
+package cn.iocoder.dashboard.framework.apollox.spring.property;
+
+import cn.hutool.core.lang.Singleton;
+import cn.iocoder.dashboard.framework.apollox.ConfigChangeListener;
+import cn.iocoder.dashboard.framework.apollox.enums.PropertyChangeType;
+import cn.iocoder.dashboard.framework.apollox.model.ConfigChange;
+import cn.iocoder.dashboard.framework.apollox.model.ConfigChangeEvent;
+import com.alibaba.fastjson.JSON;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.TypeConverter;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.core.env.Environment;
+import org.springframework.util.CollectionUtils;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * 自动更新配置监听器
+ *
+ * Create by zhangzheng on 2018/3/6
+ */
+public class AutoUpdateConfigChangeListener implements ConfigChangeListener {
+
+    private static final Logger logger = LoggerFactory.getLogger(AutoUpdateConfigChangeListener.class);
+
+    /**
+     * {@link TypeConverter#convertIfNecessary(Object, Class, Field)} 是否带上 Field 参数,因为 Spring 3.2.0+ 才有该方法
+     */
+    private final boolean typeConverterHasConvertIfNecessaryWithFieldParameter;
+    private final Environment environment;
+    private final ConfigurableBeanFactory beanFactory;
+    /**
+     * TypeConverter 对象,参见 https://blog.csdn.net/rulerp2014/article/details/51100857
+     */
+    private final TypeConverter typeConverter;
+    private final PlaceholderHelper placeholderHelper;
+    private final SpringValueRegistry springValueRegistry;
+
+    public AutoUpdateConfigChangeListener(Environment environment, ConfigurableListableBeanFactory beanFactory) {
+        this.typeConverterHasConvertIfNecessaryWithFieldParameter = testTypeConverterHasConvertIfNecessaryWithFieldParameter();
+        this.beanFactory = beanFactory;
+        this.typeConverter = this.beanFactory.getTypeConverter();
+        this.environment = environment;
+        this.placeholderHelper = Singleton.get(PlaceholderHelper.class);
+        this.springValueRegistry = Singleton.get(SpringValueRegistry.class);
+    }
+
+    @Override
+    public void onChange(ConfigChangeEvent changeEvent) {
+        // 获得更新的 KEY 集合
+        Set<String> keys = changeEvent.changedKeys();
+        if (CollectionUtils.isEmpty(keys)) {
+            return;
+        }
+        // 循环 KEY 集合,更新 StringValue
+        for (String key : keys) {
+            // 忽略,若不在 SpringValueRegistry 中
+            // 1. check whether the changed key is relevant
+            Collection<SpringValue> targetValues = springValueRegistry.get(key);
+            if (targetValues == null || targetValues.isEmpty()) {
+                continue;
+            }
+            // 校验是否需要更新
+            // 2. check whether the value is really changed or not (since spring property sources have hierarchies)
+            if (!shouldTriggerAutoUpdate(changeEvent, key)) {
+                continue;
+            }
+            // 循环,更新 SpringValue
+            // 3. update the value
+            for (SpringValue val : targetValues) {
+                updateSpringValue(val);
+            }
+        }
+    }
+
+    /**
+     * Check whether we should trigger the auto update or not.
+     * <br />
+     * For added or modified keys, we should trigger auto update if the current value in Spring equals to the new value.
+     * <br />
+     * For deleted keys, we will trigger auto update anyway.
+     */
+    private boolean shouldTriggerAutoUpdate(ConfigChangeEvent changeEvent, String changedKey) {
+        ConfigChange configChange = changeEvent.getChange(changedKey);
+        // 若变更类型为删除,需要触发更新
+        if (configChange.getChangeType() == PropertyChangeType.DELETED) {
+            return true;
+        }
+        // 若变更类型为新增或修改,判断 environment 的值是否和最新值相等。
+        // 【高能】!!!
+        return Objects.equals(environment.getProperty(changedKey), configChange.getNewValue());
+    }
+
+    private void updateSpringValue(SpringValue springValue) {
+        try {
+            // 解析值
+            Object value = resolvePropertyValue(springValue);
+            // 更新 StringValue
+            springValue.update(value);
+            logger.info("Auto update apollo changed value successfully, new value: {}, {}", value, springValue);
+        } catch (Throwable ex) {
+            logger.error("Auto update apollo changed value failed, {}", springValue.toString(), ex);
+        }
+    }
+
+    /**
+     * Logic transplanted from DefaultListableBeanFactory
+     *
+     * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency(org.springframework.beans.factory.config.DependencyDescriptor, String, Set, TypeConverter)
+     */
+    private Object resolvePropertyValue(SpringValue springValue) {
+        // value will never be null, as @Value and @ApolloJsonValue will not allow that
+        Object value = placeholderHelper.resolvePropertyValue(beanFactory, springValue.getBeanName(), springValue.getPlaceholder());
+        // 如果值数据结构是 JSON 类型,则使用 Gson 解析成对应值的类型
+        if (springValue.isJson()) {
+            value = parseJsonValue((String) value, springValue.getGenericType());
+        } else {
+            // 如果类型为 Field
+            if (springValue.isField()) {
+                // org.springframework.beans.TypeConverter#convertIfNecessary(java.lang.Object, java.lang.Class, java.lang.reflect.Field) is available from Spring 3.2.0+
+                if (typeConverterHasConvertIfNecessaryWithFieldParameter) {
+                    value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType(), springValue.getField());
+                } else {
+                    value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType());
+                }
+            // 如果类型为 Method
+            } else {
+                value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType(), springValue.getMethodParameter());
+            }
+        }
+
+        return value;
+    }
+
+    private Object parseJsonValue(String json, Type targetType) {
+        try {
+            return JSON.parseObject(json, targetType);
+        } catch (Throwable ex) {
+            logger.error("Parsing json '{}' to type {} failed!", json, targetType, ex);
+            throw ex;
+        }
+    }
+
+    private boolean testTypeConverterHasConvertIfNecessaryWithFieldParameter() {
+        try {
+            TypeConverter.class.getMethod("convertIfNecessary", Object.class, Class.class, Field.class);
+        } catch (Throwable ex) {
+            return false;
+        }
+        return true;
+    }
+
+}

+ 160 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/PlaceholderHelper.java

@@ -0,0 +1,160 @@
+package cn.iocoder.dashboard.framework.apollox.spring.property;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Sets;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanExpressionContext;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.beans.factory.config.Scope;
+import org.springframework.util.StringUtils;
+
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * Placeholder 工具类
+ *
+ * Placeholder helper functions.
+ */
+public class PlaceholderHelper {
+
+    private static final String PLACEHOLDER_PREFIX = "${";
+    private static final String PLACEHOLDER_SUFFIX = "}";
+    private static final String VALUE_SEPARATOR = ":";
+    private static final String SIMPLE_PLACEHOLDER_PREFIX = "{";
+    private static final String EXPRESSION_PREFIX = "#{";
+    private static final String EXPRESSION_SUFFIX = "}";
+
+    /**
+     * Resolve placeholder property values, e.g.
+     *
+     * "${somePropertyValue}" -> "the actual property value"
+     */
+    public Object resolvePropertyValue(ConfigurableBeanFactory beanFactory, String beanName, String placeholder) {
+        // resolve string value
+        String strVal = beanFactory.resolveEmbeddedValue(placeholder);
+        // 获得 BeanDefinition 对象
+        BeanDefinition bd = (beanFactory.containsBean(beanName) ? beanFactory.getMergedBeanDefinition(beanName) : null);
+        // resolve expressions like "#{systemProperties.myProp}"
+        return evaluateBeanDefinitionString(beanFactory, strVal, bd);
+    }
+
+    private Object evaluateBeanDefinitionString(ConfigurableBeanFactory beanFactory, String value, BeanDefinition beanDefinition) {
+        if (beanFactory.getBeanExpressionResolver() == null) {
+            return value;
+        }
+        Scope scope = (beanDefinition != null ? beanFactory.getRegisteredScope(beanDefinition.getScope()) : null);
+        return beanFactory.getBeanExpressionResolver().evaluate(value, new BeanExpressionContext(beanFactory, scope));
+    }
+
+    /**
+     * Extract keys from placeholder, e.g.
+     * <ul>
+     * <li>${some.key} => "some.key"</li>
+     * <li>${some.key:${some.other.key:100}} => "some.key", "some.other.key"</li>
+     * <li>${${some.key}} => "some.key"</li>
+     * <li>${${some.key:other.key}} => "some.key"</li>
+     * <li>${${some.key}:${another.key}} => "some.key", "another.key"</li>
+     * <li>#{new java.text.SimpleDateFormat('${some.key}').parse('${another.key}')} => "some.key", "another.key"</li>
+     * </ul>
+     */
+    public Set<String> extractPlaceholderKeys(String propertyString) {
+        Set<String> placeholderKeys = Sets.newHashSet();
+
+        if (!isNormalizedPlaceholder(propertyString) && !isExpressionWithPlaceholder(propertyString)) {
+            return placeholderKeys;
+        }
+
+        Stack<String> stack = new Stack<>();
+        stack.push(propertyString);
+
+        while (!stack.isEmpty()) {
+            String strVal = stack.pop();
+            int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
+            if (startIndex == -1) {
+                placeholderKeys.add(strVal);
+                continue;
+            }
+            int endIndex = findPlaceholderEndIndex(strVal, startIndex);
+            if (endIndex == -1) {
+                // invalid placeholder?
+                continue;
+            }
+
+            String placeholderCandidate = strVal.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
+
+            // ${some.key:other.key}
+            if (placeholderCandidate.startsWith(PLACEHOLDER_PREFIX)) {
+                stack.push(placeholderCandidate);
+            } else {
+                // some.key:${some.other.key:100}
+                int separatorIndex = placeholderCandidate.indexOf(VALUE_SEPARATOR);
+
+                if (separatorIndex == -1) {
+                    stack.push(placeholderCandidate);
+                } else {
+                    stack.push(placeholderCandidate.substring(0, separatorIndex));
+                    String defaultValuePart =
+                            normalizeToPlaceholder(placeholderCandidate.substring(separatorIndex + VALUE_SEPARATOR.length()));
+                    if (!Strings.isNullOrEmpty(defaultValuePart)) {
+                        stack.push(defaultValuePart);
+                    }
+                }
+            }
+
+            // has remaining part, e.g. ${a}.${b}
+            if (endIndex + PLACEHOLDER_SUFFIX.length() < strVal.length() - 1) {
+                String remainingPart = normalizeToPlaceholder(strVal.substring(endIndex + PLACEHOLDER_SUFFIX.length()));
+                if (!Strings.isNullOrEmpty(remainingPart)) {
+                    stack.push(remainingPart);
+                }
+            }
+        }
+
+        return placeholderKeys;
+    }
+
+    private boolean isNormalizedPlaceholder(String propertyString) {
+        return propertyString.startsWith(PLACEHOLDER_PREFIX) && propertyString.endsWith(PLACEHOLDER_SUFFIX);
+    }
+
+    private boolean isExpressionWithPlaceholder(String propertyString) {
+        return propertyString.startsWith(EXPRESSION_PREFIX) && propertyString.endsWith(EXPRESSION_SUFFIX)
+                && propertyString.contains(PLACEHOLDER_PREFIX);
+    }
+
+    private String normalizeToPlaceholder(String strVal) {
+        int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
+        if (startIndex == -1) {
+            return null;
+        }
+        int endIndex = strVal.lastIndexOf(PLACEHOLDER_SUFFIX);
+        if (endIndex == -1) {
+            return null;
+        }
+
+        return strVal.substring(startIndex, endIndex + PLACEHOLDER_SUFFIX.length());
+    }
+
+    private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
+        int index = startIndex + PLACEHOLDER_PREFIX.length();
+        int withinNestedPlaceholder = 0;
+        while (index < buf.length()) {
+            if (StringUtils.substringMatch(buf, index, PLACEHOLDER_SUFFIX)) {
+                if (withinNestedPlaceholder > 0) {
+                    withinNestedPlaceholder--;
+                    index = index + PLACEHOLDER_SUFFIX.length();
+                } else {
+                    return index;
+                }
+            } else if (StringUtils.substringMatch(buf, index, SIMPLE_PLACEHOLDER_PREFIX)) {
+                withinNestedPlaceholder++;
+                index = index + SIMPLE_PLACEHOLDER_PREFIX.length();
+            } else {
+                index++;
+            }
+        }
+        return -1;
+    }
+
+}

+ 53 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/PropertySourcesProcessor.java

@@ -0,0 +1,53 @@
+package cn.iocoder.dashboard.framework.apollox.spring.property;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.context.EnvironmentAware;
+import org.springframework.core.Ordered;
+import org.springframework.core.PriorityOrdered;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.Environment;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Apollo Property Sources processor for Spring Annotation Based Application. <br /> <br />
+ * <p>
+ * The reason why PropertySourcesProcessor implements {@link BeanFactoryPostProcessor} instead of
+ * {@link org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor} is that lower versions of
+ * Spring (e.g. 3.1.1) doesn't support registering BeanDefinitionRegistryPostProcessor in ImportBeanDefinitionRegistrar
+ * - {@link com.ctrip.framework.apollo.spring.annotation.ApolloConfigRegistrar}
+ *
+ * @author Jason Song(song_s@ctrip.com)
+ */
+public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered {
+
+    /**
+     * 是否初始化的标识
+     */
+    private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false);
+
+    /**
+     * Spring ConfigurableEnvironment 对象
+     */
+    private ConfigurableEnvironment environment;
+
+    @Override
+    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+
+    }
+
+    @Override
+    public void setEnvironment(Environment environment) {
+        //it is safe enough to cast as all known environment is derived from ConfigurableEnvironment
+        this.environment = (ConfigurableEnvironment) environment;
+    }
+
+    @Override
+    public int getOrder() {
+        // make it as early as possible
+        return Ordered.HIGHEST_PRECEDENCE; // 最高优先级
+    }
+
+}

+ 152 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/SpringValue.java

@@ -0,0 +1,152 @@
+package cn.iocoder.dashboard.framework.apollox.spring.property;
+
+import org.springframework.core.MethodParameter;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+
+/**
+ * Spring @Value method info
+ *
+ * @author github.com/zhegexiaohuozi  seimimaster@gmail.com
+ * @since 2018/2/6.
+ */
+public class SpringValue {
+
+    /**
+     * Bean 对象
+     */
+    private Object bean;
+    /**
+     * Bean 名字
+     */
+    private String beanName;
+    /**
+     * Spring 方法参数封装
+     */
+    private MethodParameter methodParameter;
+    /**
+     * Field
+     */
+    private Field field;
+    /**
+     * KEY
+     *
+     * 即在 Config 中的属性 KEY 。
+     */
+    private String key;
+    /**
+     * 占位符
+     */
+    private String placeholder;
+    /**
+     * 值类型
+     */
+    private Class<?> targetType;
+    /**
+     * 是否 JSON
+     */
+    private boolean isJson;
+    /**
+     * 泛型。当是 JSON 类型时,使用
+     */
+    private Type genericType;
+
+    // Field
+    public SpringValue(String key, String placeholder, Object bean, String beanName, Field field, boolean isJson) {
+        this.bean = bean;
+        this.beanName = beanName;
+        // Field
+        this.field = field;
+        this.key = key;
+        this.placeholder = placeholder;
+        // Field 差异
+        this.targetType = field.getType();
+        this.isJson = isJson;
+        if (isJson) {
+            this.genericType = field.getGenericType();
+        }
+    }
+
+    // Method
+    public SpringValue(String key, String placeholder, Object bean, String beanName, Method method, boolean isJson) {
+        this.bean = bean;
+        this.beanName = beanName;
+        // Method
+        this.methodParameter = new MethodParameter(method, 0);
+        this.key = key;
+        this.placeholder = placeholder;
+        // Method 差异
+        Class<?>[] paramTps = method.getParameterTypes();
+        this.targetType = paramTps[0];
+        this.isJson = isJson;
+        if (isJson) {
+            this.genericType = method.getGenericParameterTypes()[0];
+        }
+    }
+
+    public void update(Object newVal) throws IllegalAccessException, InvocationTargetException {
+        // Field
+        if (isField()) {
+            injectField(newVal);
+        // Method
+        } else {
+            injectMethod(newVal);
+        }
+    }
+
+    private void injectField(Object newVal) throws IllegalAccessException {
+        boolean accessible = field.isAccessible();
+        field.setAccessible(true);
+        field.set(bean, newVal);
+        field.setAccessible(accessible);
+    }
+
+    private void injectMethod(Object newVal) throws InvocationTargetException, IllegalAccessException {
+        methodParameter.getMethod().invoke(bean, newVal);
+    }
+
+    public String getBeanName() {
+        return beanName;
+    }
+
+    public Class<?> getTargetType() {
+        return targetType;
+    }
+
+    public String getPlaceholder() {
+        return this.placeholder;
+    }
+
+    public MethodParameter getMethodParameter() {
+        return methodParameter;
+    }
+
+    public boolean isField() {
+        return this.field != null;
+    }
+
+    public Field getField() {
+        return field;
+    }
+
+    public Type getGenericType() {
+        return genericType;
+    }
+
+    public boolean isJson() {
+        return isJson;
+    }
+
+    @Override
+    public String toString() {
+        if (isField()) {
+            return String.format("key: %s, beanName: %s, field: %s.%s", key, beanName, bean.getClass().getName(), field.getName());
+        }
+        return String.format("key: %s, beanName: %s, method: %s.%s", key, beanName, bean.getClass().getName(),
+                methodParameter.getMethod().getName());
+    }
+
+}

+ 28 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/SpringValueDefinition.java

@@ -0,0 +1,28 @@
+package cn.iocoder.dashboard.framework.apollox.spring.property;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Spring Value 定义
+ */
+@Getter
+@AllArgsConstructor
+public class SpringValueDefinition {
+
+    /**
+     * KEY
+     *
+     * 即在 Config 中的属性 KEY 。
+     */
+    private final String key;
+    /**
+     * 占位符
+     */
+    private final String placeholder;
+    /**
+     * 属性名
+     */
+    private final String propertyName;
+
+}

+ 94 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/SpringValueDefinitionProcessor.java

@@ -0,0 +1,94 @@
+package cn.iocoder.dashboard.framework.apollox.spring.property;
+
+import cn.hutool.core.lang.Singleton;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Multimap;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.beans.PropertyValue;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.config.TypedStringValue;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * To process xml config placeholders, e.g.
+ *
+ * <pre>
+ *  &lt;bean class=&quot;com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean&quot;&gt;
+ *    &lt;property name=&quot;timeout&quot; value=&quot;${timeout:200}&quot;/&gt;
+ *    &lt;property name=&quot;batch&quot; value=&quot;${batch:100}&quot;/&gt;
+ *  &lt;/bean&gt;
+ * </pre>
+ */
+public class SpringValueDefinitionProcessor implements BeanDefinitionRegistryPostProcessor {
+
+    /**
+     * SpringValueDefinition 集合
+     * <p>
+     * KEY:beanName
+     * VALUE:SpringValueDefinition 集合
+     */
+    private static final Multimap<String, SpringValueDefinition> beanName2SpringValueDefinitions = LinkedListMultimap.create();
+    /**
+     * 是否初始化的标识
+     */
+    private static final AtomicBoolean initialized = new AtomicBoolean(false);
+
+    private final PlaceholderHelper placeholderHelper = Singleton.get(PlaceholderHelper.class);
+
+    @Override
+    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
+        processPropertyValues(registry);
+    }
+
+    @Override
+    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+    }
+
+    public static Multimap<String, SpringValueDefinition> getBeanName2SpringValueDefinitions() {
+        return beanName2SpringValueDefinitions;
+    }
+
+    private void processPropertyValues(BeanDefinitionRegistry beanRegistry) {
+        // 若已经初始化,直接返回
+        if (!initialized.compareAndSet(false, true)) {
+            // already initialized
+            return;
+        }
+        // 循环 BeanDefinition 集合
+        String[] beanNames = beanRegistry.getBeanDefinitionNames();
+        for (String beanName : beanNames) {
+            BeanDefinition beanDefinition = beanRegistry.getBeanDefinition(beanName);
+            // 循环 BeanDefinition 的 PropertyValue 数组
+            MutablePropertyValues mutablePropertyValues = beanDefinition.getPropertyValues();
+            List<PropertyValue> propertyValues = mutablePropertyValues.getPropertyValueList();
+            for (PropertyValue propertyValue : propertyValues) {
+                // 获得 `value` 属性。
+                Object value = propertyValue.getValue();
+                // 忽略非 Spring PlaceHolder 的 `value` 属性。
+                if (!(value instanceof TypedStringValue)) {
+                    continue;
+                }
+                // 获得 `placeholder` 属性。
+                String placeholder = ((TypedStringValue) value).getValue();
+                // 提取 `keys` 属性们。
+                Set<String> keys = placeholderHelper.extractPlaceholderKeys(placeholder);
+                if (keys.isEmpty()) {
+                    continue;
+                }
+                // 循环 `keys` ,创建对应的 SpringValueDefinition 对象,并添加到 `beanName2SpringValueDefinitions` 中。
+                for (String key : keys) {
+                    beanName2SpringValueDefinitions.put(beanName,
+                            new SpringValueDefinition(key, placeholder, propertyValue.getName()));
+                }
+            }
+        }
+    }
+
+}

+ 31 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/SpringValueRegistry.java

@@ -0,0 +1,31 @@
+package cn.iocoder.dashboard.framework.apollox.spring.property;
+
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Multimap;
+
+import java.util.Collection;
+
+/**
+ * {@link SpringValue} 注册表
+ */
+public class SpringValueRegistry {
+
+    /**
+     * SpringValue 集合
+     *
+     * KEY:属性 KEY ,即 Config 配置 KEY
+     * VALUE:SpringValue 数组
+     */
+    private final Multimap<String, SpringValue> registry = LinkedListMultimap.create();
+
+    // 注册
+    public void register(String key, SpringValue springValue) {
+        registry.put(key, springValue);
+    }
+
+    // 获得
+    public Collection<SpringValue> get(String key) {
+        return registry.get(key);
+    }
+
+}

+ 38 - 0
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/util/BeanRegistrationUtil.java

@@ -0,0 +1,38 @@
+package cn.iocoder.dashboard.framework.apollox.spring.util;
+
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+
+import java.util.Objects;
+
+/**
+ * Bean Registration 工具类
+ *
+ * @author Jason Song(song_s@ctrip.com)
+ */
+public class BeanRegistrationUtil {
+
+    // 注册 `beanClass` 到 BeanDefinitionRegistry 中,当且仅当 `beanName` 和 `beanClass` 都不存在对应的 BeanDefinition 时
+    public static boolean registerBeanDefinitionIfNotExists(BeanDefinitionRegistry registry, String beanName, Class<?> beanClass) {
+        // 不存在 `beanName` 对应的 BeanDefinition
+        if (registry.containsBeanDefinition(beanName)) {
+            return false;
+        }
+
+        // 不存在 `beanClass` 对应的 BeanDefinition
+        String[] candidates = registry.getBeanDefinitionNames();
+        for (String candidate : candidates) {
+            BeanDefinition beanDefinition = registry.getBeanDefinition(candidate);
+            if (Objects.equals(beanDefinition.getBeanClassName(), beanClass.getName())) {
+                return false;
+            }
+        }
+
+        // 注册 `beanClass` 到 BeanDefinitionRegistry 中
+        BeanDefinition annotationProcessor = BeanDefinitionBuilder.genericBeanDefinition(beanClass).getBeanDefinition();
+        registry.registerBeanDefinition(beanName, annotationProcessor);
+        return true;
+    }
+
+}

+ 4 - 0
src/main/resources/META-INF/spring.factories

@@ -0,0 +1,4 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+    cn.iocoder.dashboard.framework.apollox.spring.boot.ApolloAutoConfiguration
+org.springframework.context.ApplicationContextInitializer=\
+    cn.iocoder.dashboard.framework.apollox.spring.boot.ApolloApplicationContextInitializer