mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2024-11-30 03:01:53 +08:00
实现配置中心的基础
This commit is contained in:
parent
d2fa839d3c
commit
ec9ed896f9
@ -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();
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package cn.iocoder.dashboard.framework.apollox.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 属性变化类型枚举
|
||||||
|
*
|
||||||
|
* @author Jason Song(song_s@ctrip.com)
|
||||||
|
*/
|
||||||
|
public enum PropertyChangeType {
|
||||||
|
|
||||||
|
ADDED, // 添加
|
||||||
|
MODIFIED, // 修改
|
||||||
|
DELETED // 删除
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,141 @@
|
|||||||
|
package cn.iocoder.dashboard.framework.apollox.spring.annotation;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.Singleton;
|
||||||
|
import cn.iocoder.dashboard.framework.apollox.spring.property.*;
|
||||||
|
import com.google.common.collect.LinkedListMultimap;
|
||||||
|
import com.google.common.collect.Multimap;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||||
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
|
import java.beans.PropertyDescriptor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring value processor of field or method which has @Value and xml config placeholders.
|
||||||
|
*
|
||||||
|
* Spring Value 处理器,处理:
|
||||||
|
*
|
||||||
|
* 1. 带有 `@Value` 注解的 Field 和 Method
|
||||||
|
* 2. XML 配置的 Bean 的 PlaceHolder 们
|
||||||
|
*
|
||||||
|
* 每个 Field、Method、XML PlaceHolder 被处理成一个 SpringValue 对象,添加到 SpringValueRegistry 中。
|
||||||
|
*
|
||||||
|
* 目的还是,为了 PlaceHolder 的自动更新机制。
|
||||||
|
*
|
||||||
|
* @author github.com/zhegexiaohuozi seimimaster@gmail.com
|
||||||
|
* @since 2017/12/20.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class SpringValueProcessor extends ApolloProcessor implements BeanFactoryPostProcessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SpringValueDefinition 集合
|
||||||
|
*
|
||||||
|
* KEY:beanName
|
||||||
|
* VALUE:SpringValueDefinition 集合
|
||||||
|
*/
|
||||||
|
private static Multimap<String, SpringValueDefinition> beanName2SpringValueDefinitions = LinkedListMultimap.create();
|
||||||
|
|
||||||
|
private final PlaceholderHelper placeholderHelper = Singleton.get(PlaceholderHelper.class);
|
||||||
|
private final SpringValueRegistry springValueRegistry = Singleton.get(SpringValueRegistry.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||||
|
beanName2SpringValueDefinitions = SpringValueDefinitionProcessor.getBeanName2SpringValueDefinitions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||||
|
// 处理 Field 和 Method
|
||||||
|
super.postProcessBeforeInitialization(bean, beanName);
|
||||||
|
// 处理 XML 配置的 Bean 的 PlaceHolder 们
|
||||||
|
processBeanPropertyValues(bean, beanName);
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void processField(Object bean, String beanName, Field field) {
|
||||||
|
// register @Value on field
|
||||||
|
Value value = field.getAnnotation(Value.class);
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 提取 `keys` 属性们。
|
||||||
|
Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
|
||||||
|
if (keys.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 循环 `keys` ,创建对应的 SpringValue 对象,并添加到 `springValueRegistry` 中。
|
||||||
|
for (String key : keys) {
|
||||||
|
SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
|
||||||
|
springValueRegistry.register(key, springValue);
|
||||||
|
log.debug("Monitoring {}", springValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void processMethod(Object bean, String beanName, Method method) {
|
||||||
|
// register @Value on method
|
||||||
|
Value value = method.getAnnotation(Value.class);
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 忽略 @Bean 注解的方法
|
||||||
|
// skip Configuration bean methods
|
||||||
|
if (method.getAnnotation(Bean.class) != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 忽略非 setting 方法
|
||||||
|
if (method.getParameterTypes().length != 1) {
|
||||||
|
log.error("Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters", bean.getClass().getName(), method.getName(), method.getParameterTypes().length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 提取 `keys` 属性们。
|
||||||
|
Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
|
||||||
|
if (keys.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 循环 `keys` ,创建对应的 SpringValue 对象,并添加到 `springValueRegistry` 中。
|
||||||
|
for (String key : keys) {
|
||||||
|
SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, method, false);
|
||||||
|
springValueRegistry.register(key, springValue);
|
||||||
|
log.info("Monitoring {}", springValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processBeanPropertyValues(Object bean, String beanName) {
|
||||||
|
// 获得 SpringValueDefinition 数组
|
||||||
|
Collection<SpringValueDefinition> propertySpringValues = beanName2SpringValueDefinitions.get(beanName);
|
||||||
|
if (propertySpringValues == null || propertySpringValues.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 循环 SpringValueDefinition 数组,创建对应的 SpringValue 对象,并添加到 `springValueRegistry` 中。
|
||||||
|
for (SpringValueDefinition definition : propertySpringValues) {
|
||||||
|
try {
|
||||||
|
PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(bean.getClass(), definition.getPropertyName());
|
||||||
|
Method method = pd.getWriteMethod();
|
||||||
|
if (method == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
SpringValue springValue = new SpringValue(definition.getKey(), definition.getPlaceholder(), bean, beanName, method, false);
|
||||||
|
springValueRegistry.register(definition.getKey(), springValue);
|
||||||
|
log.debug("Monitoring {}", springValue);
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
log.error("Failed to enable auto update feature for {}.{}", bean.getClass(), definition.getPropertyName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear
|
||||||
|
// 移除 Bean 对应的 SpringValueDefinition 数组
|
||||||
|
beanName2SpringValueDefinitions.removeAll(beanName);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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 对象
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package cn.iocoder.dashboard.framework.apollox.spring.config;
|
||||||
|
|
||||||
|
public interface PropertySourcesConstants {
|
||||||
|
|
||||||
|
String APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME = "ApolloBootstrapPropertySources";
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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; // 最高优先级
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
|
||||||
|
}
|
@ -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>
|
||||||
|
* <bean class="com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean">
|
||||||
|
* <property name="timeout" value="${timeout:200}"/>
|
||||||
|
* <property name="batch" value="${batch:100}"/>
|
||||||
|
* </bean>
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public class SpringValueDefinitionProcessor implements BeanDefinitionRegistryPostProcessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SpringValueDefinition 集合
|
||||||
|
* <p>
|
||||||
|
* KEY:beanName
|
||||||
|
* VALUE:SpringValueDefinition 集合
|
||||||
|
*/
|
||||||
|
private static final Multimap<String, SpringValueDefinition> beanName2SpringValueDefinitions = LinkedListMultimap.create();
|
||||||
|
/**
|
||||||
|
* 是否初始化的标识
|
||||||
|
*/
|
||||||
|
private static final AtomicBoolean initialized = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
private final PlaceholderHelper placeholderHelper = Singleton.get(PlaceholderHelper.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
||||||
|
processPropertyValues(registry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Multimap<String, SpringValueDefinition> getBeanName2SpringValueDefinitions() {
|
||||||
|
return beanName2SpringValueDefinitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processPropertyValues(BeanDefinitionRegistry beanRegistry) {
|
||||||
|
// 若已经初始化,直接返回
|
||||||
|
if (!initialized.compareAndSet(false, true)) {
|
||||||
|
// already initialized
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 循环 BeanDefinition 集合
|
||||||
|
String[] beanNames = beanRegistry.getBeanDefinitionNames();
|
||||||
|
for (String beanName : beanNames) {
|
||||||
|
BeanDefinition beanDefinition = beanRegistry.getBeanDefinition(beanName);
|
||||||
|
// 循环 BeanDefinition 的 PropertyValue 数组
|
||||||
|
MutablePropertyValues mutablePropertyValues = beanDefinition.getPropertyValues();
|
||||||
|
List<PropertyValue> propertyValues = mutablePropertyValues.getPropertyValueList();
|
||||||
|
for (PropertyValue propertyValue : propertyValues) {
|
||||||
|
// 获得 `value` 属性。
|
||||||
|
Object value = propertyValue.getValue();
|
||||||
|
// 忽略非 Spring PlaceHolder 的 `value` 属性。
|
||||||
|
if (!(value instanceof TypedStringValue)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 获得 `placeholder` 属性。
|
||||||
|
String placeholder = ((TypedStringValue) value).getValue();
|
||||||
|
// 提取 `keys` 属性们。
|
||||||
|
Set<String> keys = placeholderHelper.extractPlaceholderKeys(placeholder);
|
||||||
|
if (keys.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 循环 `keys` ,创建对应的 SpringValueDefinition 对象,并添加到 `beanName2SpringValueDefinitions` 中。
|
||||||
|
for (String key : keys) {
|
||||||
|
beanName2SpringValueDefinitions.put(beanName,
|
||||||
|
new SpringValueDefinition(key, placeholder, propertyValue.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package cn.iocoder.dashboard.framework.apollox.spring.property;
|
||||||
|
|
||||||
|
import com.google.common.collect.LinkedListMultimap;
|
||||||
|
import com.google.common.collect.Multimap;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link SpringValue} 注册表
|
||||||
|
*/
|
||||||
|
public class SpringValueRegistry {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SpringValue 集合
|
||||||
|
*
|
||||||
|
* KEY:属性 KEY ,即 Config 配置 KEY
|
||||||
|
* VALUE:SpringValue 数组
|
||||||
|
*/
|
||||||
|
private final Multimap<String, SpringValue> registry = LinkedListMultimap.create();
|
||||||
|
|
||||||
|
// 注册
|
||||||
|
public void register(String key, SpringValue springValue) {
|
||||||
|
registry.put(key, springValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获得
|
||||||
|
public Collection<SpringValue> get(String key) {
|
||||||
|
return registry.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package cn.iocoder.dashboard.framework.apollox.spring.util;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bean Registration 工具类
|
||||||
|
*
|
||||||
|
* @author Jason Song(song_s@ctrip.com)
|
||||||
|
*/
|
||||||
|
public class BeanRegistrationUtil {
|
||||||
|
|
||||||
|
// 注册 `beanClass` 到 BeanDefinitionRegistry 中,当且仅当 `beanName` 和 `beanClass` 都不存在对应的 BeanDefinition 时
|
||||||
|
public static boolean registerBeanDefinitionIfNotExists(BeanDefinitionRegistry registry, String beanName, Class<?> beanClass) {
|
||||||
|
// 不存在 `beanName` 对应的 BeanDefinition
|
||||||
|
if (registry.containsBeanDefinition(beanName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不存在 `beanClass` 对应的 BeanDefinition
|
||||||
|
String[] candidates = registry.getBeanDefinitionNames();
|
||||||
|
for (String candidate : candidates) {
|
||||||
|
BeanDefinition beanDefinition = registry.getBeanDefinition(candidate);
|
||||||
|
if (Objects.equals(beanDefinition.getBeanClassName(), beanClass.getName())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册 `beanClass` 到 BeanDefinitionRegistry 中
|
||||||
|
BeanDefinition annotationProcessor = BeanDefinitionBuilder.genericBeanDefinition(beanClass).getBeanDefinition();
|
||||||
|
registry.registerBeanDefinition(beanName, annotationProcessor);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
4
src/main/resources/META-INF/spring.factories
Normal file
4
src/main/resources/META-INF/spring.factories
Normal 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
|
Loading…
Reference in New Issue
Block a user