Commit ec9ed896f96bee69d9f20ebc148fdf73b2efaee9
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
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
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 | + * <bean class="com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean"> | |
| 24 | + * <property name="timeout" value="${timeout:200}"/> | |
| 25 | + * <property name="batch" value="${batch:100}"/> | |
| 26 | + * </bean> | |
| 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 | ... | ... |