Commit ec9ed896f96bee69d9f20ebc148fdf73b2efaee9

Authored by YunaiV
1 parent d2fa839d

实现配置中心的基础

Showing 23 changed files with 1250 additions and 0 deletions
src/main/java/cn/iocoder/dashboard/framework/apollox/Config.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox;
  2 +
  3 +import java.util.Set;
  4 +
  5 +/**
  6 + * 配置接口
  7 + *
  8 + * @author Jason Song(song_s@ctrip.com)
  9 + */
  10 +public interface Config {
  11 +
  12 + /**
  13 + * Return the property value with the given key, or {@code defaultValue} if the key doesn't exist.
  14 + *
  15 + * @param key the property name
  16 + * @param defaultValue the default value when key is not found or any error occurred
  17 + * @return the property value
  18 + */
  19 + String getProperty(String key, String defaultValue);
  20 +
  21 + /**
  22 + * Return a set of the property names
  23 + *
  24 + * @return the property names
  25 + */
  26 + Set<String> getPropertyNames();
  27 +
  28 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/ConfigChangeListener.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox;
  2 +
  3 +import cn.iocoder.dashboard.framework.apollox.model.ConfigChangeEvent;
  4 +
  5 +/**
  6 + * {@link Config} 变化监听器
  7 + *
  8 + * @author Jason Song(song_s@ctrip.com)
  9 + */
  10 +public interface ConfigChangeListener {
  11 +
  12 + /**
  13 + * Invoked when there is any config change for the namespace.
  14 + *
  15 + * @param changeEvent the event for this change
  16 + */
  17 + void onChange(ConfigChangeEvent changeEvent);
  18 +
  19 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/DBConfig.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox;
  2 +
  3 +import java.util.Set;
  4 +
  5 +public class DBConfig implements Config {
  6 +
  7 + @Override
  8 + public String getProperty(String key, String defaultValue) {
  9 + return null;
  10 + }
  11 +
  12 + @Override
  13 + public Set<String> getPropertyNames() {
  14 + return null;
  15 + }
  16 +
  17 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/enums/PropertyChangeType.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.enums;
  2 +
  3 +/**
  4 + * 属性变化类型枚举
  5 + *
  6 + * @author Jason Song(song_s@ctrip.com)
  7 + */
  8 +public enum PropertyChangeType {
  9 +
  10 + ADDED, // 添加
  11 + MODIFIED, // 修改
  12 + DELETED // 删除
  13 +
  14 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/model/ConfigChange.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.model;
  2 +
  3 +
  4 +import cn.iocoder.dashboard.framework.apollox.enums.PropertyChangeType;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Data;
  7 +
  8 +/**
  9 + * Holds the information for a config change.
  10 + * 配置每个属性变化的信息
  11 + *
  12 + * @author Jason Song(song_s@ctrip.com)
  13 + */
  14 +@Data
  15 +@AllArgsConstructor
  16 +public class ConfigChange {
  17 +
  18 + /**
  19 + * 属性名
  20 + */
  21 + private final String propertyName;
  22 + /**
  23 + * 老值
  24 + */
  25 + private String oldValue;
  26 + /**
  27 + * 新值
  28 + */
  29 + private String newValue;
  30 + /**
  31 + * 变化类型
  32 + */
  33 + private PropertyChangeType changeType;
  34 +
  35 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/model/ConfigChangeEvent.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.model;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +
  5 +import java.util.Map;
  6 +import java.util.Set;
  7 +
  8 +/**
  9 + * A change event when a namespace's config is changed.
  10 + *
  11 + * @author Jason Song(song_s@ctrip.com)
  12 + */
  13 +@AllArgsConstructor
  14 +public class ConfigChangeEvent {
  15 +
  16 + /**
  17 + * 变化属性的集合
  18 + *
  19 + * KEY:属性名
  20 + * VALUE:配置变化
  21 + */
  22 + private final Map<String, ConfigChange> m_changes;
  23 +
  24 + /**
  25 + * Get the keys changed.
  26 + *
  27 + * @return the list of the keys
  28 + */
  29 + public Set<String> changedKeys() {
  30 + return m_changes.keySet();
  31 + }
  32 +
  33 + /**
  34 + * Get a specific change instance for the key specified.
  35 + *
  36 + * @param key the changed key
  37 + * @return the change instance
  38 + */
  39 + public ConfigChange getChange(String key) {
  40 + return m_changes.get(key);
  41 + }
  42 +
  43 + /**
  44 + * Check whether the specified key is changed
  45 + *
  46 + * @param key the key
  47 + * @return true if the key is changed, false otherwise.
  48 + */
  49 + public boolean isChanged(String key) {
  50 + return m_changes.containsKey(key);
  51 + }
  52 +
  53 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/package-info.java 0 → 100644
  1 +/**
  2 + * 配置中心客户端,基于 Apollo Client 实现,所以叫 ApolloX
  3 + *
  4 + * 差别在于,我们使用 {@link cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.config.SysConfigDO} 表作为配置源。
  5 + * 当然,功能肯定也会相对少些,满足最小化诉求。
  6 + *
  7 + * 1. 项目初始化时,可以使用 SysConfigDO 表的配置
  8 + * 2. 使用 Spring @Value 可以注入属性
  9 + * 3. SysConfigDO 表的配置修改时,注入到 @Value 的属性可以刷新
  10 + *
  11 + * 另外,整个包结构会参考 Apollo 为主,方便维护与理解
  12 + *
  13 + * 注意,目前有两个特性是不支持的
  14 + * 1. 自定义配置变化的监听器
  15 + */
  16 +package cn.iocoder.dashboard.framework.apollox;
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/annotation/ApolloProcessor.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.spring.annotation;
  2 +
  3 +import org.springframework.beans.BeansException;
  4 +import org.springframework.beans.factory.config.BeanPostProcessor;
  5 +import org.springframework.core.Ordered;
  6 +import org.springframework.core.PriorityOrdered;
  7 +import org.springframework.util.ReflectionUtils;
  8 +
  9 +import java.lang.reflect.Field;
  10 +import java.lang.reflect.Method;
  11 +import java.util.LinkedList;
  12 +import java.util.List;
  13 +
  14 +/**
  15 + * Apollo 处理器抽象类,封装了在 Spring Bean 初始化之前,处理属性和方法。
  16 + *
  17 + * Create by zhangzheng on 2018/2/6
  18 + */
  19 +public abstract class ApolloProcessor implements BeanPostProcessor, PriorityOrdered {
  20 +
  21 + @Override
  22 + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  23 + Class<?> clazz = bean.getClass();
  24 + // 处理所有 Field
  25 + for (Field field : findAllField(clazz)) {
  26 + processField(bean, beanName, field);
  27 + }
  28 + // 处理所有的 Method
  29 + for (Method method : findAllMethod(clazz)) {
  30 + processMethod(bean, beanName, method);
  31 + }
  32 + return bean;
  33 + }
  34 +
  35 + @Override
  36 + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  37 + return bean;
  38 + }
  39 +
  40 + /**
  41 + * subclass should implement this method to process field
  42 + */
  43 + protected abstract void processField(Object bean, String beanName, Field field);
  44 +
  45 + /**
  46 + * subclass should implement this method to process method
  47 + */
  48 + protected abstract void processMethod(Object bean, String beanName, Method method);
  49 +
  50 + @Override
  51 + public int getOrder() {
  52 + // make it as late as possible
  53 + return Ordered.LOWEST_PRECEDENCE; // 最高优先级
  54 + }
  55 +
  56 + private List<Field> findAllField(Class<?> clazz) {
  57 + final List<Field> res = new LinkedList<>();
  58 + ReflectionUtils.doWithFields(clazz, res::add);
  59 + return res;
  60 + }
  61 +
  62 + private List<Method> findAllMethod(Class<?> clazz) {
  63 + final List<Method> res = new LinkedList<>();
  64 + ReflectionUtils.doWithMethods(clazz, res::add);
  65 + return res;
  66 + }
  67 +
  68 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/annotation/SpringValueProcessor.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.spring.annotation;
  2 +
  3 +import cn.hutool.core.lang.Singleton;
  4 +import cn.iocoder.dashboard.framework.apollox.spring.property.*;
  5 +import com.google.common.collect.LinkedListMultimap;
  6 +import com.google.common.collect.Multimap;
  7 +import lombok.extern.slf4j.Slf4j;
  8 +import org.springframework.beans.BeanUtils;
  9 +import org.springframework.beans.BeansException;
  10 +import org.springframework.beans.factory.annotation.Value;
  11 +import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
  12 +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
  13 +import org.springframework.context.annotation.Bean;
  14 +
  15 +import java.beans.PropertyDescriptor;
  16 +import java.lang.reflect.Field;
  17 +import java.lang.reflect.Method;
  18 +import java.util.Collection;
  19 +import java.util.Set;
  20 +
  21 +/**
  22 + * Spring value processor of field or method which has @Value and xml config placeholders.
  23 + *
  24 + * Spring Value 处理器,处理:
  25 + *
  26 + * 1. 带有 `@Value` 注解的 Field 和 Method
  27 + * 2. XML 配置的 Bean 的 PlaceHolder 们
  28 + *
  29 + * 每个 Field、Method、XML PlaceHolder 被处理成一个 SpringValue 对象,添加到 SpringValueRegistry 中。
  30 + *
  31 + * 目的还是,为了 PlaceHolder 的自动更新机制。
  32 + *
  33 + * @author github.com/zhegexiaohuozi seimimaster@gmail.com
  34 + * @since 2017/12/20.
  35 + */
  36 +@Slf4j
  37 +public class SpringValueProcessor extends ApolloProcessor implements BeanFactoryPostProcessor {
  38 +
  39 + /**
  40 + * SpringValueDefinition 集合
  41 + *
  42 + * KEY:beanName
  43 + * VALUE:SpringValueDefinition 集合
  44 + */
  45 + private static Multimap<String, SpringValueDefinition> beanName2SpringValueDefinitions = LinkedListMultimap.create();
  46 +
  47 + private final PlaceholderHelper placeholderHelper = Singleton.get(PlaceholderHelper.class);
  48 + private final SpringValueRegistry springValueRegistry = Singleton.get(SpringValueRegistry.class);
  49 +
  50 + @Override
  51 + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
  52 + beanName2SpringValueDefinitions = SpringValueDefinitionProcessor.getBeanName2SpringValueDefinitions();
  53 + }
  54 +
  55 + @Override
  56 + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  57 + // 处理 Field 和 Method
  58 + super.postProcessBeforeInitialization(bean, beanName);
  59 + // 处理 XML 配置的 Bean 的 PlaceHolder 们
  60 + processBeanPropertyValues(bean, beanName);
  61 + return bean;
  62 + }
  63 +
  64 + @Override
  65 + protected void processField(Object bean, String beanName, Field field) {
  66 + // register @Value on field
  67 + Value value = field.getAnnotation(Value.class);
  68 + if (value == null) {
  69 + return;
  70 + }
  71 + // 提取 `keys` 属性们。
  72 + Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
  73 + if (keys.isEmpty()) {
  74 + return;
  75 + }
  76 + // 循环 `keys` ,创建对应的 SpringValue 对象,并添加到 `springValueRegistry` 中。
  77 + for (String key : keys) {
  78 + SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
  79 + springValueRegistry.register(key, springValue);
  80 + log.debug("Monitoring {}", springValue);
  81 + }
  82 + }
  83 +
  84 + @Override
  85 + protected void processMethod(Object bean, String beanName, Method method) {
  86 + // register @Value on method
  87 + Value value = method.getAnnotation(Value.class);
  88 + if (value == null) {
  89 + return;
  90 + }
  91 + // 忽略 @Bean 注解的方法
  92 + // skip Configuration bean methods
  93 + if (method.getAnnotation(Bean.class) != null) {
  94 + return;
  95 + }
  96 + // 忽略非 setting 方法
  97 + if (method.getParameterTypes().length != 1) {
  98 + log.error("Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters", bean.getClass().getName(), method.getName(), method.getParameterTypes().length);
  99 + return;
  100 + }
  101 + // 提取 `keys` 属性们。
  102 + Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
  103 + if (keys.isEmpty()) {
  104 + return;
  105 + }
  106 + // 循环 `keys` ,创建对应的 SpringValue 对象,并添加到 `springValueRegistry` 中。
  107 + for (String key : keys) {
  108 + SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, method, false);
  109 + springValueRegistry.register(key, springValue);
  110 + log.info("Monitoring {}", springValue);
  111 + }
  112 + }
  113 +
  114 + private void processBeanPropertyValues(Object bean, String beanName) {
  115 + // 获得 SpringValueDefinition 数组
  116 + Collection<SpringValueDefinition> propertySpringValues = beanName2SpringValueDefinitions.get(beanName);
  117 + if (propertySpringValues == null || propertySpringValues.isEmpty()) {
  118 + return;
  119 + }
  120 + // 循环 SpringValueDefinition 数组,创建对应的 SpringValue 对象,并添加到 `springValueRegistry` 中。
  121 + for (SpringValueDefinition definition : propertySpringValues) {
  122 + try {
  123 + PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(bean.getClass(), definition.getPropertyName());
  124 + Method method = pd.getWriteMethod();
  125 + if (method == null) {
  126 + continue;
  127 + }
  128 + SpringValue springValue = new SpringValue(definition.getKey(), definition.getPlaceholder(), bean, beanName, method, false);
  129 + springValueRegistry.register(definition.getKey(), springValue);
  130 + log.debug("Monitoring {}", springValue);
  131 + } catch (Throwable ex) {
  132 + log.error("Failed to enable auto update feature for {}.{}", bean.getClass(), definition.getPropertyName());
  133 + }
  134 + }
  135 +
  136 + // clear
  137 + // 移除 Bean 对应的 SpringValueDefinition 数组
  138 + beanName2SpringValueDefinitions.removeAll(beanName);
  139 + }
  140 +
  141 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/boot/ApolloApplicationContextInitializer.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.spring.boot;
  2 +
  3 +
  4 +import cn.hutool.core.lang.Singleton;
  5 +import cn.iocoder.dashboard.framework.apollox.Config;
  6 +import cn.iocoder.dashboard.framework.apollox.DBConfig;
  7 +import cn.iocoder.dashboard.framework.apollox.spring.config.ConfigPropertySource;
  8 +import lombok.extern.slf4j.Slf4j;
  9 +import org.springframework.context.ApplicationContextInitializer;
  10 +import org.springframework.context.ConfigurableApplicationContext;
  11 +import org.springframework.core.env.ConfigurableEnvironment;
  12 +
  13 +import static cn.iocoder.dashboard.framework.apollox.spring.config.PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME;
  14 +
  15 +@Slf4j
  16 +public class ApolloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
  17 +
  18 + @Override
  19 + public void initialize(ConfigurableApplicationContext context) {
  20 + ConfigurableEnvironment environment = context.getEnvironment();
  21 + // 忽略,若已经有 APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME 的 PropertySource
  22 + if (environment.getPropertySources().contains(APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
  23 + // already initialized
  24 + return;
  25 + }
  26 +
  27 + // 创建自定义的 APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME 的 PropertySource
  28 + Config config = Singleton.get(DBConfig.class);
  29 + ConfigPropertySource configPropertySource = new ConfigPropertySource(APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME, config);
  30 + // 添加到 `environment` 中,且优先级最高
  31 + environment.getPropertySources().addFirst(configPropertySource);
  32 + }
  33 +
  34 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/boot/ApolloAutoConfiguration.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.spring.boot;
  2 +
  3 +import cn.iocoder.dashboard.framework.apollox.spring.config.ConfigPropertySourcesProcessor;
  4 +import cn.iocoder.dashboard.framework.apollox.spring.property.PropertySourcesProcessor;
  5 +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  6 +import org.springframework.context.annotation.Bean;
  7 +import org.springframework.context.annotation.Configuration;
  8 +
  9 +@Configuration
  10 +@ConditionalOnMissingBean(PropertySourcesProcessor.class) // 缺失 PropertySourcesProcessor 时
  11 +public class ApolloAutoConfiguration {
  12 +
  13 + @Bean
  14 + public ConfigPropertySourcesProcessor configPropertySourcesProcessor() {
  15 + return new ConfigPropertySourcesProcessor(); // 注入 ConfigPropertySourcesProcessor bean 对象
  16 + }
  17 +
  18 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/config/ConfigPropertySource.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.spring.config;
  2 +
  3 +import cn.iocoder.dashboard.framework.apollox.Config;
  4 +import org.springframework.core.env.EnumerablePropertySource;
  5 +
  6 +import java.util.Set;
  7 +
  8 +/**
  9 + * Property source wrapper for Config
  10 + *
  11 + * 基于 {@link Config} 的 PropertySource 实现类
  12 + *
  13 + * @author Jason Song(song_s@ctrip.com)
  14 + */
  15 +public class ConfigPropertySource extends EnumerablePropertySource<Config> {
  16 +
  17 + private static final String[] EMPTY_ARRAY = new String[0];
  18 +
  19 + public ConfigPropertySource(String name, Config source) { // 此处的 Apollo Config 作为 `source`
  20 + super(name, source);
  21 + }
  22 +
  23 + @Override
  24 + public String[] getPropertyNames() {
  25 + // 从 Config 中,获得属性名集合
  26 + Set<String> propertyNames = this.source.getPropertyNames();
  27 + // 转换成 String 数组,返回
  28 + if (propertyNames.isEmpty()) {
  29 + return EMPTY_ARRAY;
  30 + }
  31 + return propertyNames.toArray(new String[0]);
  32 + }
  33 +
  34 + @Override
  35 + public Object getProperty(String name) {
  36 + return this.source.getProperty(name, null);
  37 + }
  38 +
  39 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/config/ConfigPropertySourcesProcessor.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.spring.config;
  2 +
  3 +import cn.iocoder.dashboard.framework.apollox.spring.annotation.SpringValueProcessor;
  4 +import cn.iocoder.dashboard.framework.apollox.spring.property.PropertySourcesProcessor;
  5 +import cn.iocoder.dashboard.framework.apollox.spring.property.SpringValueDefinitionProcessor;
  6 +import cn.iocoder.dashboard.framework.apollox.spring.util.BeanRegistrationUtil;
  7 +import org.springframework.beans.BeansException;
  8 +import org.springframework.beans.factory.support.BeanDefinitionRegistry;
  9 +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
  10 +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
  11 +
  12 +/**
  13 + * Apollo Property Sources processor for Spring XML Based Application
  14 + *
  15 + * @author Jason Song(song_s@ctrip.com)
  16 + */
  17 +public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor implements BeanDefinitionRegistryPostProcessor {
  18 +
  19 + @Override
  20 + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
  21 + // 注册 PropertySourcesPlaceholderConfigurer 到 BeanDefinitionRegistry 中,替换 PlaceHolder 为对应的属性值,参考文章 https://leokongwq.github.io/2016/12/28/spring-PropertyPlaceholderConfigurer.html
  22 + BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(), PropertySourcesPlaceholderConfigurer.class);
  23 + // 注册 SpringValueProcessor 到 BeanDefinitionRegistry 中,用于 PlaceHolder 自动更新机制
  24 + BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class);
  25 +
  26 + // 处理 XML 配置的 Spring PlaceHolder
  27 + processSpringValueDefinition(registry);
  28 + }
  29 +
  30 + /**
  31 + * For Spring 3.x versions, the BeanDefinitionRegistryPostProcessor would not be
  32 + * instantiated if it is added in postProcessBeanDefinitionRegistry phase, so we have to manually
  33 + * call the postProcessBeanDefinitionRegistry method of SpringValueDefinitionProcessor here...
  34 + */
  35 + private void processSpringValueDefinition(BeanDefinitionRegistry registry) {
  36 + // 创建 SpringValueDefinitionProcessor 对象
  37 + SpringValueDefinitionProcessor springValueDefinitionProcessor = new SpringValueDefinitionProcessor();
  38 + // 处理 XML 配置的 Spring PlaceHolder
  39 + springValueDefinitionProcessor.postProcessBeanDefinitionRegistry(registry);
  40 + }
  41 +
  42 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/config/PropertySourcesConstants.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.spring.config;
  2 +
  3 +public interface PropertySourcesConstants {
  4 +
  5 + String APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME = "ApolloBootstrapPropertySources";
  6 +
  7 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/AutoUpdateConfigChangeListener.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.spring.property;
  2 +
  3 +import cn.hutool.core.lang.Singleton;
  4 +import cn.iocoder.dashboard.framework.apollox.ConfigChangeListener;
  5 +import cn.iocoder.dashboard.framework.apollox.enums.PropertyChangeType;
  6 +import cn.iocoder.dashboard.framework.apollox.model.ConfigChange;
  7 +import cn.iocoder.dashboard.framework.apollox.model.ConfigChangeEvent;
  8 +import com.alibaba.fastjson.JSON;
  9 +import org.slf4j.Logger;
  10 +import org.slf4j.LoggerFactory;
  11 +import org.springframework.beans.TypeConverter;
  12 +import org.springframework.beans.factory.config.ConfigurableBeanFactory;
  13 +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
  14 +import org.springframework.core.env.Environment;
  15 +import org.springframework.util.CollectionUtils;
  16 +
  17 +import java.lang.reflect.Field;
  18 +import java.lang.reflect.Type;
  19 +import java.util.Collection;
  20 +import java.util.Objects;
  21 +import java.util.Set;
  22 +
  23 +/**
  24 + * 自动更新配置监听器
  25 + *
  26 + * Create by zhangzheng on 2018/3/6
  27 + */
  28 +public class AutoUpdateConfigChangeListener implements ConfigChangeListener {
  29 +
  30 + private static final Logger logger = LoggerFactory.getLogger(AutoUpdateConfigChangeListener.class);
  31 +
  32 + /**
  33 + * {@link TypeConverter#convertIfNecessary(Object, Class, Field)} 是否带上 Field 参数,因为 Spring 3.2.0+ 才有该方法
  34 + */
  35 + private final boolean typeConverterHasConvertIfNecessaryWithFieldParameter;
  36 + private final Environment environment;
  37 + private final ConfigurableBeanFactory beanFactory;
  38 + /**
  39 + * TypeConverter 对象,参见 https://blog.csdn.net/rulerp2014/article/details/51100857
  40 + */
  41 + private final TypeConverter typeConverter;
  42 + private final PlaceholderHelper placeholderHelper;
  43 + private final SpringValueRegistry springValueRegistry;
  44 +
  45 + public AutoUpdateConfigChangeListener(Environment environment, ConfigurableListableBeanFactory beanFactory) {
  46 + this.typeConverterHasConvertIfNecessaryWithFieldParameter = testTypeConverterHasConvertIfNecessaryWithFieldParameter();
  47 + this.beanFactory = beanFactory;
  48 + this.typeConverter = this.beanFactory.getTypeConverter();
  49 + this.environment = environment;
  50 + this.placeholderHelper = Singleton.get(PlaceholderHelper.class);
  51 + this.springValueRegistry = Singleton.get(SpringValueRegistry.class);
  52 + }
  53 +
  54 + @Override
  55 + public void onChange(ConfigChangeEvent changeEvent) {
  56 + // 获得更新的 KEY 集合
  57 + Set<String> keys = changeEvent.changedKeys();
  58 + if (CollectionUtils.isEmpty(keys)) {
  59 + return;
  60 + }
  61 + // 循环 KEY 集合,更新 StringValue
  62 + for (String key : keys) {
  63 + // 忽略,若不在 SpringValueRegistry 中
  64 + // 1. check whether the changed key is relevant
  65 + Collection<SpringValue> targetValues = springValueRegistry.get(key);
  66 + if (targetValues == null || targetValues.isEmpty()) {
  67 + continue;
  68 + }
  69 + // 校验是否需要更新
  70 + // 2. check whether the value is really changed or not (since spring property sources have hierarchies)
  71 + if (!shouldTriggerAutoUpdate(changeEvent, key)) {
  72 + continue;
  73 + }
  74 + // 循环,更新 SpringValue
  75 + // 3. update the value
  76 + for (SpringValue val : targetValues) {
  77 + updateSpringValue(val);
  78 + }
  79 + }
  80 + }
  81 +
  82 + /**
  83 + * Check whether we should trigger the auto update or not.
  84 + * <br />
  85 + * For added or modified keys, we should trigger auto update if the current value in Spring equals to the new value.
  86 + * <br />
  87 + * For deleted keys, we will trigger auto update anyway.
  88 + */
  89 + private boolean shouldTriggerAutoUpdate(ConfigChangeEvent changeEvent, String changedKey) {
  90 + ConfigChange configChange = changeEvent.getChange(changedKey);
  91 + // 若变更类型为删除,需要触发更新
  92 + if (configChange.getChangeType() == PropertyChangeType.DELETED) {
  93 + return true;
  94 + }
  95 + // 若变更类型为新增或修改,判断 environment 的值是否和最新值相等。
  96 + // 【高能】!!!
  97 + return Objects.equals(environment.getProperty(changedKey), configChange.getNewValue());
  98 + }
  99 +
  100 + private void updateSpringValue(SpringValue springValue) {
  101 + try {
  102 + // 解析值
  103 + Object value = resolvePropertyValue(springValue);
  104 + // 更新 StringValue
  105 + springValue.update(value);
  106 + logger.info("Auto update apollo changed value successfully, new value: {}, {}", value, springValue);
  107 + } catch (Throwable ex) {
  108 + logger.error("Auto update apollo changed value failed, {}", springValue.toString(), ex);
  109 + }
  110 + }
  111 +
  112 + /**
  113 + * Logic transplanted from DefaultListableBeanFactory
  114 + *
  115 + * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency(org.springframework.beans.factory.config.DependencyDescriptor, String, Set, TypeConverter)
  116 + */
  117 + private Object resolvePropertyValue(SpringValue springValue) {
  118 + // value will never be null, as @Value and @ApolloJsonValue will not allow that
  119 + Object value = placeholderHelper.resolvePropertyValue(beanFactory, springValue.getBeanName(), springValue.getPlaceholder());
  120 + // 如果值数据结构是 JSON 类型,则使用 Gson 解析成对应值的类型
  121 + if (springValue.isJson()) {
  122 + value = parseJsonValue((String) value, springValue.getGenericType());
  123 + } else {
  124 + // 如果类型为 Field
  125 + if (springValue.isField()) {
  126 + // org.springframework.beans.TypeConverter#convertIfNecessary(java.lang.Object, java.lang.Class, java.lang.reflect.Field) is available from Spring 3.2.0+
  127 + if (typeConverterHasConvertIfNecessaryWithFieldParameter) {
  128 + value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType(), springValue.getField());
  129 + } else {
  130 + value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType());
  131 + }
  132 + // 如果类型为 Method
  133 + } else {
  134 + value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType(), springValue.getMethodParameter());
  135 + }
  136 + }
  137 +
  138 + return value;
  139 + }
  140 +
  141 + private Object parseJsonValue(String json, Type targetType) {
  142 + try {
  143 + return JSON.parseObject(json, targetType);
  144 + } catch (Throwable ex) {
  145 + logger.error("Parsing json '{}' to type {} failed!", json, targetType, ex);
  146 + throw ex;
  147 + }
  148 + }
  149 +
  150 + private boolean testTypeConverterHasConvertIfNecessaryWithFieldParameter() {
  151 + try {
  152 + TypeConverter.class.getMethod("convertIfNecessary", Object.class, Class.class, Field.class);
  153 + } catch (Throwable ex) {
  154 + return false;
  155 + }
  156 + return true;
  157 + }
  158 +
  159 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/PlaceholderHelper.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.spring.property;
  2 +
  3 +import com.google.common.base.Strings;
  4 +import com.google.common.collect.Sets;
  5 +import org.springframework.beans.factory.config.BeanDefinition;
  6 +import org.springframework.beans.factory.config.BeanExpressionContext;
  7 +import org.springframework.beans.factory.config.ConfigurableBeanFactory;
  8 +import org.springframework.beans.factory.config.Scope;
  9 +import org.springframework.util.StringUtils;
  10 +
  11 +import java.util.Set;
  12 +import java.util.Stack;
  13 +
  14 +/**
  15 + * Placeholder 工具类
  16 + *
  17 + * Placeholder helper functions.
  18 + */
  19 +public class PlaceholderHelper {
  20 +
  21 + private static final String PLACEHOLDER_PREFIX = "${";
  22 + private static final String PLACEHOLDER_SUFFIX = "}";
  23 + private static final String VALUE_SEPARATOR = ":";
  24 + private static final String SIMPLE_PLACEHOLDER_PREFIX = "{";
  25 + private static final String EXPRESSION_PREFIX = "#{";
  26 + private static final String EXPRESSION_SUFFIX = "}";
  27 +
  28 + /**
  29 + * Resolve placeholder property values, e.g.
  30 + *
  31 + * "${somePropertyValue}" -> "the actual property value"
  32 + */
  33 + public Object resolvePropertyValue(ConfigurableBeanFactory beanFactory, String beanName, String placeholder) {
  34 + // resolve string value
  35 + String strVal = beanFactory.resolveEmbeddedValue(placeholder);
  36 + // 获得 BeanDefinition 对象
  37 + BeanDefinition bd = (beanFactory.containsBean(beanName) ? beanFactory.getMergedBeanDefinition(beanName) : null);
  38 + // resolve expressions like "#{systemProperties.myProp}"
  39 + return evaluateBeanDefinitionString(beanFactory, strVal, bd);
  40 + }
  41 +
  42 + private Object evaluateBeanDefinitionString(ConfigurableBeanFactory beanFactory, String value, BeanDefinition beanDefinition) {
  43 + if (beanFactory.getBeanExpressionResolver() == null) {
  44 + return value;
  45 + }
  46 + Scope scope = (beanDefinition != null ? beanFactory.getRegisteredScope(beanDefinition.getScope()) : null);
  47 + return beanFactory.getBeanExpressionResolver().evaluate(value, new BeanExpressionContext(beanFactory, scope));
  48 + }
  49 +
  50 + /**
  51 + * Extract keys from placeholder, e.g.
  52 + * <ul>
  53 + * <li>${some.key} => "some.key"</li>
  54 + * <li>${some.key:${some.other.key:100}} => "some.key", "some.other.key"</li>
  55 + * <li>${${some.key}} => "some.key"</li>
  56 + * <li>${${some.key:other.key}} => "some.key"</li>
  57 + * <li>${${some.key}:${another.key}} => "some.key", "another.key"</li>
  58 + * <li>#{new java.text.SimpleDateFormat('${some.key}').parse('${another.key}')} => "some.key", "another.key"</li>
  59 + * </ul>
  60 + */
  61 + public Set<String> extractPlaceholderKeys(String propertyString) {
  62 + Set<String> placeholderKeys = Sets.newHashSet();
  63 +
  64 + if (!isNormalizedPlaceholder(propertyString) && !isExpressionWithPlaceholder(propertyString)) {
  65 + return placeholderKeys;
  66 + }
  67 +
  68 + Stack<String> stack = new Stack<>();
  69 + stack.push(propertyString);
  70 +
  71 + while (!stack.isEmpty()) {
  72 + String strVal = stack.pop();
  73 + int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
  74 + if (startIndex == -1) {
  75 + placeholderKeys.add(strVal);
  76 + continue;
  77 + }
  78 + int endIndex = findPlaceholderEndIndex(strVal, startIndex);
  79 + if (endIndex == -1) {
  80 + // invalid placeholder?
  81 + continue;
  82 + }
  83 +
  84 + String placeholderCandidate = strVal.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
  85 +
  86 + // ${some.key:other.key}
  87 + if (placeholderCandidate.startsWith(PLACEHOLDER_PREFIX)) {
  88 + stack.push(placeholderCandidate);
  89 + } else {
  90 + // some.key:${some.other.key:100}
  91 + int separatorIndex = placeholderCandidate.indexOf(VALUE_SEPARATOR);
  92 +
  93 + if (separatorIndex == -1) {
  94 + stack.push(placeholderCandidate);
  95 + } else {
  96 + stack.push(placeholderCandidate.substring(0, separatorIndex));
  97 + String defaultValuePart =
  98 + normalizeToPlaceholder(placeholderCandidate.substring(separatorIndex + VALUE_SEPARATOR.length()));
  99 + if (!Strings.isNullOrEmpty(defaultValuePart)) {
  100 + stack.push(defaultValuePart);
  101 + }
  102 + }
  103 + }
  104 +
  105 + // has remaining part, e.g. ${a}.${b}
  106 + if (endIndex + PLACEHOLDER_SUFFIX.length() < strVal.length() - 1) {
  107 + String remainingPart = normalizeToPlaceholder(strVal.substring(endIndex + PLACEHOLDER_SUFFIX.length()));
  108 + if (!Strings.isNullOrEmpty(remainingPart)) {
  109 + stack.push(remainingPart);
  110 + }
  111 + }
  112 + }
  113 +
  114 + return placeholderKeys;
  115 + }
  116 +
  117 + private boolean isNormalizedPlaceholder(String propertyString) {
  118 + return propertyString.startsWith(PLACEHOLDER_PREFIX) && propertyString.endsWith(PLACEHOLDER_SUFFIX);
  119 + }
  120 +
  121 + private boolean isExpressionWithPlaceholder(String propertyString) {
  122 + return propertyString.startsWith(EXPRESSION_PREFIX) && propertyString.endsWith(EXPRESSION_SUFFIX)
  123 + && propertyString.contains(PLACEHOLDER_PREFIX);
  124 + }
  125 +
  126 + private String normalizeToPlaceholder(String strVal) {
  127 + int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
  128 + if (startIndex == -1) {
  129 + return null;
  130 + }
  131 + int endIndex = strVal.lastIndexOf(PLACEHOLDER_SUFFIX);
  132 + if (endIndex == -1) {
  133 + return null;
  134 + }
  135 +
  136 + return strVal.substring(startIndex, endIndex + PLACEHOLDER_SUFFIX.length());
  137 + }
  138 +
  139 + private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
  140 + int index = startIndex + PLACEHOLDER_PREFIX.length();
  141 + int withinNestedPlaceholder = 0;
  142 + while (index < buf.length()) {
  143 + if (StringUtils.substringMatch(buf, index, PLACEHOLDER_SUFFIX)) {
  144 + if (withinNestedPlaceholder > 0) {
  145 + withinNestedPlaceholder--;
  146 + index = index + PLACEHOLDER_SUFFIX.length();
  147 + } else {
  148 + return index;
  149 + }
  150 + } else if (StringUtils.substringMatch(buf, index, SIMPLE_PLACEHOLDER_PREFIX)) {
  151 + withinNestedPlaceholder++;
  152 + index = index + SIMPLE_PLACEHOLDER_PREFIX.length();
  153 + } else {
  154 + index++;
  155 + }
  156 + }
  157 + return -1;
  158 + }
  159 +
  160 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/PropertySourcesProcessor.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.spring.property;
  2 +
  3 +import org.springframework.beans.BeansException;
  4 +import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
  5 +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
  6 +import org.springframework.context.EnvironmentAware;
  7 +import org.springframework.core.Ordered;
  8 +import org.springframework.core.PriorityOrdered;
  9 +import org.springframework.core.env.ConfigurableEnvironment;
  10 +import org.springframework.core.env.Environment;
  11 +
  12 +import java.util.concurrent.atomic.AtomicBoolean;
  13 +
  14 +/**
  15 + * Apollo Property Sources processor for Spring Annotation Based Application. <br /> <br />
  16 + * <p>
  17 + * The reason why PropertySourcesProcessor implements {@link BeanFactoryPostProcessor} instead of
  18 + * {@link org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor} is that lower versions of
  19 + * Spring (e.g. 3.1.1) doesn't support registering BeanDefinitionRegistryPostProcessor in ImportBeanDefinitionRegistrar
  20 + * - {@link com.ctrip.framework.apollo.spring.annotation.ApolloConfigRegistrar}
  21 + *
  22 + * @author Jason Song(song_s@ctrip.com)
  23 + */
  24 +public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered {
  25 +
  26 + /**
  27 + * 是否初始化的标识
  28 + */
  29 + private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false);
  30 +
  31 + /**
  32 + * Spring ConfigurableEnvironment 对象
  33 + */
  34 + private ConfigurableEnvironment environment;
  35 +
  36 + @Override
  37 + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
  38 +
  39 + }
  40 +
  41 + @Override
  42 + public void setEnvironment(Environment environment) {
  43 + //it is safe enough to cast as all known environment is derived from ConfigurableEnvironment
  44 + this.environment = (ConfigurableEnvironment) environment;
  45 + }
  46 +
  47 + @Override
  48 + public int getOrder() {
  49 + // make it as early as possible
  50 + return Ordered.HIGHEST_PRECEDENCE; // 最高优先级
  51 + }
  52 +
  53 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/SpringValue.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.spring.property;
  2 +
  3 +import org.springframework.core.MethodParameter;
  4 +
  5 +import java.lang.reflect.Field;
  6 +import java.lang.reflect.InvocationTargetException;
  7 +import java.lang.reflect.Method;
  8 +import java.lang.reflect.Type;
  9 +
  10 +/**
  11 + * Spring @Value method info
  12 + *
  13 + * @author github.com/zhegexiaohuozi seimimaster@gmail.com
  14 + * @since 2018/2/6.
  15 + */
  16 +public class SpringValue {
  17 +
  18 + /**
  19 + * Bean 对象
  20 + */
  21 + private Object bean;
  22 + /**
  23 + * Bean 名字
  24 + */
  25 + private String beanName;
  26 + /**
  27 + * Spring 方法参数封装
  28 + */
  29 + private MethodParameter methodParameter;
  30 + /**
  31 + * Field
  32 + */
  33 + private Field field;
  34 + /**
  35 + * KEY
  36 + *
  37 + * 即在 Config 中的属性 KEY 。
  38 + */
  39 + private String key;
  40 + /**
  41 + * 占位符
  42 + */
  43 + private String placeholder;
  44 + /**
  45 + * 值类型
  46 + */
  47 + private Class<?> targetType;
  48 + /**
  49 + * 是否 JSON
  50 + */
  51 + private boolean isJson;
  52 + /**
  53 + * 泛型。当是 JSON 类型时,使用
  54 + */
  55 + private Type genericType;
  56 +
  57 + // Field
  58 + public SpringValue(String key, String placeholder, Object bean, String beanName, Field field, boolean isJson) {
  59 + this.bean = bean;
  60 + this.beanName = beanName;
  61 + // Field
  62 + this.field = field;
  63 + this.key = key;
  64 + this.placeholder = placeholder;
  65 + // Field 差异
  66 + this.targetType = field.getType();
  67 + this.isJson = isJson;
  68 + if (isJson) {
  69 + this.genericType = field.getGenericType();
  70 + }
  71 + }
  72 +
  73 + // Method
  74 + public SpringValue(String key, String placeholder, Object bean, String beanName, Method method, boolean isJson) {
  75 + this.bean = bean;
  76 + this.beanName = beanName;
  77 + // Method
  78 + this.methodParameter = new MethodParameter(method, 0);
  79 + this.key = key;
  80 + this.placeholder = placeholder;
  81 + // Method 差异
  82 + Class<?>[] paramTps = method.getParameterTypes();
  83 + this.targetType = paramTps[0];
  84 + this.isJson = isJson;
  85 + if (isJson) {
  86 + this.genericType = method.getGenericParameterTypes()[0];
  87 + }
  88 + }
  89 +
  90 + public void update(Object newVal) throws IllegalAccessException, InvocationTargetException {
  91 + // Field
  92 + if (isField()) {
  93 + injectField(newVal);
  94 + // Method
  95 + } else {
  96 + injectMethod(newVal);
  97 + }
  98 + }
  99 +
  100 + private void injectField(Object newVal) throws IllegalAccessException {
  101 + boolean accessible = field.isAccessible();
  102 + field.setAccessible(true);
  103 + field.set(bean, newVal);
  104 + field.setAccessible(accessible);
  105 + }
  106 +
  107 + private void injectMethod(Object newVal) throws InvocationTargetException, IllegalAccessException {
  108 + methodParameter.getMethod().invoke(bean, newVal);
  109 + }
  110 +
  111 + public String getBeanName() {
  112 + return beanName;
  113 + }
  114 +
  115 + public Class<?> getTargetType() {
  116 + return targetType;
  117 + }
  118 +
  119 + public String getPlaceholder() {
  120 + return this.placeholder;
  121 + }
  122 +
  123 + public MethodParameter getMethodParameter() {
  124 + return methodParameter;
  125 + }
  126 +
  127 + public boolean isField() {
  128 + return this.field != null;
  129 + }
  130 +
  131 + public Field getField() {
  132 + return field;
  133 + }
  134 +
  135 + public Type getGenericType() {
  136 + return genericType;
  137 + }
  138 +
  139 + public boolean isJson() {
  140 + return isJson;
  141 + }
  142 +
  143 + @Override
  144 + public String toString() {
  145 + if (isField()) {
  146 + return String.format("key: %s, beanName: %s, field: %s.%s", key, beanName, bean.getClass().getName(), field.getName());
  147 + }
  148 + return String.format("key: %s, beanName: %s, method: %s.%s", key, beanName, bean.getClass().getName(),
  149 + methodParameter.getMethod().getName());
  150 + }
  151 +
  152 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/SpringValueDefinition.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.spring.property;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * Spring Value 定义
  8 + */
  9 +@Getter
  10 +@AllArgsConstructor
  11 +public class SpringValueDefinition {
  12 +
  13 + /**
  14 + * KEY
  15 + *
  16 + * 即在 Config 中的属性 KEY 。
  17 + */
  18 + private final String key;
  19 + /**
  20 + * 占位符
  21 + */
  22 + private final String placeholder;
  23 + /**
  24 + * 属性名
  25 + */
  26 + private final String propertyName;
  27 +
  28 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/SpringValueDefinitionProcessor.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.spring.property;
  2 +
  3 +import cn.hutool.core.lang.Singleton;
  4 +import com.google.common.collect.LinkedListMultimap;
  5 +import com.google.common.collect.Multimap;
  6 +import org.springframework.beans.BeansException;
  7 +import org.springframework.beans.MutablePropertyValues;
  8 +import org.springframework.beans.PropertyValue;
  9 +import org.springframework.beans.factory.config.BeanDefinition;
  10 +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
  11 +import org.springframework.beans.factory.config.TypedStringValue;
  12 +import org.springframework.beans.factory.support.BeanDefinitionRegistry;
  13 +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
  14 +
  15 +import java.util.List;
  16 +import java.util.Set;
  17 +import java.util.concurrent.atomic.AtomicBoolean;
  18 +
  19 +/**
  20 + * To process xml config placeholders, e.g.
  21 + *
  22 + * <pre>
  23 + * &lt;bean class=&quot;com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean&quot;&gt;
  24 + * &lt;property name=&quot;timeout&quot; value=&quot;${timeout:200}&quot;/&gt;
  25 + * &lt;property name=&quot;batch&quot; value=&quot;${batch:100}&quot;/&gt;
  26 + * &lt;/bean&gt;
  27 + * </pre>
  28 + */
  29 +public class SpringValueDefinitionProcessor implements BeanDefinitionRegistryPostProcessor {
  30 +
  31 + /**
  32 + * SpringValueDefinition 集合
  33 + * <p>
  34 + * KEY:beanName
  35 + * VALUE:SpringValueDefinition 集合
  36 + */
  37 + private static final Multimap<String, SpringValueDefinition> beanName2SpringValueDefinitions = LinkedListMultimap.create();
  38 + /**
  39 + * 是否初始化的标识
  40 + */
  41 + private static final AtomicBoolean initialized = new AtomicBoolean(false);
  42 +
  43 + private final PlaceholderHelper placeholderHelper = Singleton.get(PlaceholderHelper.class);
  44 +
  45 + @Override
  46 + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
  47 + processPropertyValues(registry);
  48 + }
  49 +
  50 + @Override
  51 + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
  52 + }
  53 +
  54 + public static Multimap<String, SpringValueDefinition> getBeanName2SpringValueDefinitions() {
  55 + return beanName2SpringValueDefinitions;
  56 + }
  57 +
  58 + private void processPropertyValues(BeanDefinitionRegistry beanRegistry) {
  59 + // 若已经初始化,直接返回
  60 + if (!initialized.compareAndSet(false, true)) {
  61 + // already initialized
  62 + return;
  63 + }
  64 + // 循环 BeanDefinition 集合
  65 + String[] beanNames = beanRegistry.getBeanDefinitionNames();
  66 + for (String beanName : beanNames) {
  67 + BeanDefinition beanDefinition = beanRegistry.getBeanDefinition(beanName);
  68 + // 循环 BeanDefinition 的 PropertyValue 数组
  69 + MutablePropertyValues mutablePropertyValues = beanDefinition.getPropertyValues();
  70 + List<PropertyValue> propertyValues = mutablePropertyValues.getPropertyValueList();
  71 + for (PropertyValue propertyValue : propertyValues) {
  72 + // 获得 `value` 属性。
  73 + Object value = propertyValue.getValue();
  74 + // 忽略非 Spring PlaceHolder 的 `value` 属性。
  75 + if (!(value instanceof TypedStringValue)) {
  76 + continue;
  77 + }
  78 + // 获得 `placeholder` 属性。
  79 + String placeholder = ((TypedStringValue) value).getValue();
  80 + // 提取 `keys` 属性们。
  81 + Set<String> keys = placeholderHelper.extractPlaceholderKeys(placeholder);
  82 + if (keys.isEmpty()) {
  83 + continue;
  84 + }
  85 + // 循环 `keys` ,创建对应的 SpringValueDefinition 对象,并添加到 `beanName2SpringValueDefinitions` 中。
  86 + for (String key : keys) {
  87 + beanName2SpringValueDefinitions.put(beanName,
  88 + new SpringValueDefinition(key, placeholder, propertyValue.getName()));
  89 + }
  90 + }
  91 + }
  92 + }
  93 +
  94 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/SpringValueRegistry.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.spring.property;
  2 +
  3 +import com.google.common.collect.LinkedListMultimap;
  4 +import com.google.common.collect.Multimap;
  5 +
  6 +import java.util.Collection;
  7 +
  8 +/**
  9 + * {@link SpringValue} 注册表
  10 + */
  11 +public class SpringValueRegistry {
  12 +
  13 + /**
  14 + * SpringValue 集合
  15 + *
  16 + * KEY:属性 KEY ,即 Config 配置 KEY
  17 + * VALUE:SpringValue 数组
  18 + */
  19 + private final Multimap<String, SpringValue> registry = LinkedListMultimap.create();
  20 +
  21 + // 注册
  22 + public void register(String key, SpringValue springValue) {
  23 + registry.put(key, springValue);
  24 + }
  25 +
  26 + // 获得
  27 + public Collection<SpringValue> get(String key) {
  28 + return registry.get(key);
  29 + }
  30 +
  31 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/util/BeanRegistrationUtil.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.spring.util;
  2 +
  3 +import org.springframework.beans.factory.config.BeanDefinition;
  4 +import org.springframework.beans.factory.support.BeanDefinitionBuilder;
  5 +import org.springframework.beans.factory.support.BeanDefinitionRegistry;
  6 +
  7 +import java.util.Objects;
  8 +
  9 +/**
  10 + * Bean Registration 工具类
  11 + *
  12 + * @author Jason Song(song_s@ctrip.com)
  13 + */
  14 +public class BeanRegistrationUtil {
  15 +
  16 + // 注册 `beanClass` 到 BeanDefinitionRegistry 中,当且仅当 `beanName` 和 `beanClass` 都不存在对应的 BeanDefinition 时
  17 + public static boolean registerBeanDefinitionIfNotExists(BeanDefinitionRegistry registry, String beanName, Class<?> beanClass) {
  18 + // 不存在 `beanName` 对应的 BeanDefinition
  19 + if (registry.containsBeanDefinition(beanName)) {
  20 + return false;
  21 + }
  22 +
  23 + // 不存在 `beanClass` 对应的 BeanDefinition
  24 + String[] candidates = registry.getBeanDefinitionNames();
  25 + for (String candidate : candidates) {
  26 + BeanDefinition beanDefinition = registry.getBeanDefinition(candidate);
  27 + if (Objects.equals(beanDefinition.getBeanClassName(), beanClass.getName())) {
  28 + return false;
  29 + }
  30 + }
  31 +
  32 + // 注册 `beanClass` 到 BeanDefinitionRegistry 中
  33 + BeanDefinition annotationProcessor = BeanDefinitionBuilder.genericBeanDefinition(beanClass).getBeanDefinition();
  34 + registry.registerBeanDefinition(beanName, annotationProcessor);
  35 + return true;
  36 + }
  37 +
  38 +}
src/main/resources/META-INF/spring.factories 0 → 100644
  1 +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  2 + cn.iocoder.dashboard.framework.apollox.spring.boot.ApolloAutoConfiguration
  3 +org.springframework.context.ApplicationContextInitializer=\
  4 + cn.iocoder.dashboard.framework.apollox.spring.boot.ApolloApplicationContextInitializer