实现配置中心的基础

This commit is contained in:
YunaiV 2021-01-19 23:43:52 +08:00
parent d2fa839d3c
commit ec9ed896f9
23 changed files with 1250 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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