ssm基于zookeeperPropertySource实现动态配置刷新
写在前面
此博客是在:
spring篇-(ssm自定义zookeeperPropertySource实现动态配置的加载))的基础上扩展开发,实现基于zookeeper的动态配置刷新功能
修改PropertySourceLocator接口
定义contextRefreshEventListener回调函数,用于容器启动成功之后,回调此函数,实现配置刷新和事件发布等功能
package com.lhstack.custom.config.locator;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import java.io.IOException;
import java.util.List;
/**
* @author lhstack
* @date 2021/8/22
* @class PropertySourceLocator
* @since 1.8
*/
public interface PropertySourceLocator {
...
...
/**
* 容器刷新事件
* @param applicationContext
*/
default void contextRefreshEventListener(ApplicationContext applicationContext){
}
}
修改PropertySourceUtils
对数据进行优化处理,封装统一解析函数
package com.lhstack.custom.config.locator;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.io.IOUtils;
import org.yaml.snakeyaml.Yaml;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
/**
* @author lhstack
* @date 2021/8/22
* @class PropertySourceUtils
* @since 1.8
*/
public class PropertySourceUtils {
public static Map<String, Object> parseYaml(InputStream in) {
Yaml yaml = new Yaml();
Map<String, Object> map = new HashMap<>();
initSourceToMap("", yaml.loadAs(in, JSONObject.class), map);
return map;
}
/**
* 将数据扁平化
*/
private static void initSourceToMap(String parentKey, JSONObject jsonObject, Map<String, Object> map) {
jsonObject.forEach((k, v) -> {
if (v instanceof Map<?, ?>) {
if (parentKey == null || parentKey.isEmpty()) {
initSourceToMap(k, jsonObject.getJSONObject(k), map);
} else {
initSourceToMap(parentKey + "." + k, jsonObject.getJSONObject(k), map);
}
} else {
if (parentKey == null || parentKey.isEmpty()) {
map.put(k, v);
} else {
map.put(parentKey + "." + k, v);
}
}
});
}
public static Map<String, Object> parseJson(InputStream in) {
Map<String, Object> map = new HashMap<>();
try {
byte[] bytes = IOUtils.toByteArray(in);
initSourceToMap("", JSONObject.parseObject(new String(bytes, StandardCharsets.UTF_8)), map);
} catch (IOException e) {
e.printStackTrace();
}
return map;
}
public static Map<String, Object> parseProperties(ByteArrayInputStream in) throws IOException {
Properties properties = new Properties();
properties.load(in);
return properties.entrySet().stream().collect(Collectors.toMap(String::valueOf, Map.Entry::getValue));
}
public static Map<String, Object> parse(String path, byte[] bytes) throws IOException {
if (bytes.length == 0) {
return Collections.emptyMap();
}
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
Map<String, Object> result = null;
if (path.endsWith(".yml") || path.endsWith(".yaml")) {
result = parseYaml(in);
} else if (path.endsWith(".json")) {
result = parseJson(in);
} else if (path.endsWith(".properties")) {
result = parseProperties(in);
}
in.close();
return result;
}
}
定义配置相关事件类
用于配置加载完成,配置刷新等事件的定义,通过事件的方式,通知配置刷新处理器去动态变更配置
AbstractEnvironmentEvent .java
package com.lhstack.custom.config.locator.event;
import org.springframework.context.ApplicationEvent;
/**
* 定义抽象环境改变事件
* @author lhstack
* @date 2021/8/28
* @class AbstractEnvironmentEvent
* @since 1.8
*/
public abstract class AbstractEnvironmentEvent extends ApplicationEvent {
public AbstractEnvironmentEvent(Object source) {
super(source);
}
}
EnvironmentChangeEvent.java
package com.lhstack.custom.config.locator.event;
import java.util.Set;
/**
* @author lhstack
* @date 2021/8/28
* @class EnvironmentChangeEvent
* @since 1.8
*/
public class EnvironmentChangeEvent extends AbstractEnvironmentEvent {
private final Set<String> changeKeys;
private final EventType eventType;
private final String name;
/**
* 事件
*/
public enum EventType {
/**
* 添加事件
*/
ADD,
/**
* 修改事件
*/
CHANGE,
/**
* 删除事件
*/
REMOVE;
}
public EnvironmentChangeEvent(String name,EventType eventType, Set<String> changeKeys) {
super(changeKeys);
this.name = name;
this.changeKeys = changeKeys;
this.eventType = eventType;
}
public Set<String> getChangeKeys() {
return changeKeys;
}
public EventType getEventType() {
return eventType;
}
public String getName() {
return name;
}
}
EnvironmentFinishEvent.java
package com.lhstack.custom.config.locator.event;
import org.springframework.context.ApplicationEvent;
import java.util.Map;
/**
* 定义动态环境刷新事件
* @author lhstack
* @date 2021/8/27
* @class LocatorFinishEvent
* @since 1.8
*/
public class EnvironmentFinishEvent extends AbstractEnvironmentEvent {
private final Map<String, Map<String,Object>> propertyMap;
public EnvironmentFinishEvent(Map<String, Map<String,Object>> propertyMap) {
super(propertyMap);
this.propertyMap = propertyMap;
}
public Map<String, Map<String, Object>> getPropertyMap() {
return propertyMap;
}
}
修改ZookeeperPropertySourceLocator相关代码
主要是对数据结构的优化和添加配置变更监听器以及发布数据变更事件
package com.lhstack.custom.config.locator;
import com.lhstack.custom.config.locator.event.EnvironmentChangeEvent;
import com.lhstack.custom.config.locator.event.EnvironmentFinishEvent;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.BoundedExponentialBackoffRetry;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author lhstack
* @date 2021/8/22
* @class ZookeeperPropertySourceLocator
* @since 1.8
* 使用方式: yml配置如下
* spring:
* application:
* name: 应用名称,默认application
* profile: 应用作用域,默认为空
* zookeeper:
* enable: false | true
* connectString: localhost:2181,localhost:2191...
* namespace: xxx //可选,这样配置文件需要放在 /${namespace}${configContext}目录下面,如namespace为test contextContext为/config则目录为 /test/config
* sessionTimeout: session过期时间,单位为毫秒
* connectTimeout: 连接超时时间,单位为毫秒
* spector: 配置连接符 规则如下 ${spring.application.name:application}[-${spring.application.profile}]${zookeeper.spector}${zookeeper.extType}
* extType: 支持 json,yml,properties
* configContext: 设置目录,如果没设置namespace,则顶级目录为${contextContext},注意,需要添加/作为前缀
* defaultContext: 全局配置的application name 默认为application
* defaultEvtType: 支持json,yml,properties
* defaultSpector: 配置连接符 规则如下 ${defaultContext}[-${spring.application.profile}]${zookeeper.defaultSpector}${zookeeper.defaultEvtType}
* retry:
* retries: 连接失败最大重试次数
* baseSleepTimeMs: 基本重试延迟时间 单位毫秒
* maxSleepTimeMs: 最大重试延时时间 单位毫秒
*/
public class ZookeeperPropertySourceLocator implements PropertySourceLocator {
/**
* 操作zookeeper相关api的客户端
*/
private CuratorFramework curatorFramework;
private final List<String> contexts = new ArrayList<>();
private String namespace = "";
private static final Map<String, Map<String, Object>> PROPERTY_SOURCE_CACHE = new LinkedHashMap<>();
private Environment environment;
private ApplicationContext applicationContext;
@Override
public List<PropertySource<?>> locator() throws IOException {
if (environment.getProperty("zookeeper.enable", Boolean.class, true)) {
if (!this.namespace.isEmpty()) {
List<String> cs = this.contexts.stream().map(item -> "/" + this.namespace + item)
.collect(Collectors.toList());
System.out.println("load zookeeper config list " + cs);
} else {
System.out.println("load zookeeper config list " + this.contexts);
}
List<PropertySource<?>> list = new ArrayList<>();
this.contexts.forEach(item -> {
try {
//检查path是否存在
Stat stat = this.curatorFramework.checkExists().forPath(item);
if (stat != null) {
byte[] bytes = this.curatorFramework.getData().forPath(item);
addWatch(item);
Map<String, Object> map = PropertySourceUtils.parse(item, bytes);
PROPERTY_SOURCE_CACHE.put(item, map);
list.add(new MapPropertySource(item, map));
}
} catch (Exception e) {
e.printStackTrace();
}
});
return list;
}
return Collections.emptyList();
}
/**
* 配置加载完成之后回调
*
* @param applicationContext
*/
@Override
public void contextRefreshEventListener(ApplicationContext applicationContext) {
applicationContext.publishEvent(new EnvironmentFinishEvent(PROPERTY_SOURCE_CACHE));
this.applicationContext = applicationContext;
}
/**
* 添加监听
*
* @param path
*/
private void addWatch(String path) throws Exception {
this.curatorFramework.getData().usingWatcher((Watcher) watchedEvent -> {
if (watchedEvent.getState() == Watcher.Event.KeeperState.SyncConnected) {
//这里不管是否出现异常,都应该开启监听
try {
sync(path);
} catch (Exception ignore) {
}finally {
try {
addWatch(path);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).forPath(path);
}
/**
* 同步配置到容器
*
* @param path
*/
private void sync(String path) {
try {
Map<String, Object> cache = PROPERTY_SOURCE_CACHE.get(path);
if (Objects.isNull(cache)) {
PROPERTY_SOURCE_CACHE.put(path, new HashMap<>());
}
byte[] bytes = this.curatorFramework.getData().forPath(path);
if (bytes.length > 0) {
Map<String, Object> property = null;
//加载配置
property = PropertySourceUtils.parse(path, bytes);
//判断是否为空
if (Objects.nonNull(property)) {
//如果缓存为空,则putAll
if (cache.isEmpty()) {
cache.putAll(property);
System.out.println("add keys " + property.keySet());
applicationContext.publishEvent(new EnvironmentChangeEvent(path, EnvironmentChangeEvent.EventType.ADD, property.keySet()));
} else {
//否则判断添加或者同步了那些keys,删除了那些keys
Set<String> syncKeys = new LinkedHashSet<>();
Set<String> addKeys = new LinkedHashSet<>();
Set<String> removeKeys = new LinkedHashSet<>();
property.forEach((k, v) -> {
//如果包含,且值不相等,则修改
if (cache.containsKey(k)) {
if (!cache.get(k).equals(v)) {
syncKeys.add(k);
cache.put(k, v);
}
} else {
//否则添加
cache.put(k, v);
addKeys.add(k);
}
});
//匹配删除了那些keys
for (Map.Entry<String, Object> entry : cache.entrySet()) {
if (!property.containsKey(entry.getKey())) {
cache.remove(entry.getKey());
removeKeys.add(entry.getKey());
}
}
System.out.println("add keys " + addKeys + ",sync keys " + syncKeys + ",remove keys " + removeKeys);
//发布对应事件
applicationContext.publishEvent(new EnvironmentChangeEvent(path, EnvironmentChangeEvent.EventType.ADD, addKeys));
applicationContext.publishEvent(new EnvironmentChangeEvent(path, EnvironmentChangeEvent.EventType.CHANGE, syncKeys));
applicationContext.publishEvent(new EnvironmentChangeEvent(path, EnvironmentChangeEvent.EventType.REMOVE, removeKeys));
}
}
} else {
System.out.println("sync property , clear all environment");
applicationContext.publishEvent(new EnvironmentChangeEvent(path, EnvironmentChangeEvent.EventType.REMOVE, cache.keySet()));
cache.clear();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取zookeeper相关环境配置,这里设计ApplicationPropertySourceLocator优先级最高,就是考虑到后续需要application.yml等配置文件中的内容实现动态配置加载
*
* @param env
* @param beanFactory
*/
@Override
public void initEnvironment(Environment env, ConfigurableListableBeanFactory beanFactory) {
this.environment = env;
if (env.containsProperty("zookeeper.connectString") && env.getProperty(
"zookeeper.enable",
Boolean.class,
true
)
) {
CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
.connectString(env.getProperty("zookeeper.connectString"));
//namespace
if (env.containsProperty("zookeeper.namespace")) {
builder.namespace(env.getProperty("zookeeper.namespace"));
this.namespace = env.getProperty("zookeeper.namespace");
}
//session过期时间,30分钟
builder.sessionTimeoutMs(env.getProperty("zookeeper.sessionTimeout", Integer.class, 180000));
//连接超时
builder.connectionTimeoutMs(env.getProperty("zookeeper.connectTimeout", Integer.class, 30000));
//重试相关
Integer retries = env.getProperty("zookeeper.retry.retries", Integer.class, 3);
Integer retryBaseSleepTimeMs = env.getProperty("zookeeper.retry.baseSleepTimeMs", Integer.class, 10000);
Integer retryMaxSleepTimeMs = env.getProperty("zookeeper.retry.maxSleepTimeMs", Integer.class, 30000);
builder.retryPolicy(new BoundedExponentialBackoffRetry(retryBaseSleepTimeMs, retryMaxSleepTimeMs, retries));
//连接符
String spector = env.getProperty("zookeeper.spector", ".");
//默认扩展类型 全局应用加载的默认配置
String defaultExtType = env.getProperty("zookeeper.defaultEvtType", "yml");
//扩展类型
String extType = env.getProperty("zookeeper.extType", "yml");
//默认应用名 全局应用加载的默认配置
String defaultContext = env.getProperty("zookeeper.defaultContext", "application");
//配置所在的目录
String configContext = env.getProperty("zookeeper.configContext", "/config");
String defaultSpector = env.getProperty("zookeeper.defaultSpector", ".");
this.curatorFramework = builder.build();
//应用名称,通过类似这种定义去加载指定的配置文件
String applicationName = env.getProperty("spring.application.name", "application");
//环境
String profile = env.getProperty("spring.application.profile", "");
this.curatorFramework.start();
//考虑优先级问题,这里需要判断一下
if (!profile.isEmpty()) {
contexts.add(String.format("%s/%s-%s%s%s", configContext, applicationName, profile, spector, extType));
contexts.add(String.format("%s/%s%s%s", configContext, applicationName, spector, extType));
contexts.add(String.format("%s/%s-%s%s%s", configContext, defaultContext, profile, defaultSpector, defaultExtType));
} else {
contexts.add(String.format("%s/%s%s%s", configContext, applicationName, spector, extType));
}
contexts.add(String.format("%s/%s%s%s", configContext, defaultContext, defaultSpector, defaultExtType));
}
}
}
定义DynamicValue注解,标注用于配置刷新的字段或者方法
package com.lhstack.custom.config.dynamic;
import java.lang.annotation.*;
/**
* @author lhstack
* @date 2021/8/27
* @class DynamicValue
* @since 1.8
*/
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicValue {
/**
* spel表达式,获取环境变量的值
*
* @return
*/
String value();
}
定义用于配置刷新的包装类
AbstractValueUpdate.java
package com.lhstack.custom.config.dynamic.value;
import org.springframework.core.convert.ConversionService;
import java.util.Objects;
/**
* @author lhstack
* @date 2021/8/28
* @class AbstractValueUpdate
* @since 1.8
*/
public abstract class AbstractValueUpdate<T> {
protected final Object target;
/**
* field or method
*/
protected final T invokeTarget;
/**
* 转换服务
*/
protected final ConversionService conversionService;
public AbstractValueUpdate(Object target, T t, ConversionService conversionService) {
this.target = target;
this.invokeTarget = t;
this.conversionService = conversionService;
}
/**
* 更新值
*
* @param value
*/
public void update(String value) {
if (Objects.isNull(value) || value.isEmpty()) {
Class<?> type = this.getRawType();
if (type == int.class ||
type == double.class ||
type == float.class ||
type == long.class ||
type == short.class ||
type == char.class ||
type == boolean.class ||
type == byte.class
) {
value = "0";
}
}
this.doUpdate(value);
}
/**
* @return
*/
protected abstract Class<?> getRawType();
/**
* 更新值
*
* @param value
*/
public abstract void doUpdate(String value);
}
FieldValueUpdate.java
package com.lhstack.custom.config.dynamic.value;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.List;
/**
* @author lhstack
* @date 2021/8/28
* @class FieldValueUPdate
* @since 1.8
*/
public class FieldValueUpdate extends AbstractValueUpdate<Field> {
public FieldValueUpdate(Object target, Field field, ConversionService conversionService) {
super(target, field, conversionService);
ReflectionUtils.makeAccessible(field);
}
@Override
protected Class<?> getRawType() {
return this.invokeTarget.getType();
}
@Override
public void doUpdate(String value) {
try {
if (getRawType().isAssignableFrom(List.class)) {
ParameterizedType genericParameterType = (ParameterizedType) this.invokeTarget.getGenericType();
Class<?> actualTypeArguments = (Class<?>) genericParameterType.getActualTypeArguments()[0];
ReflectionUtils.setField(this.invokeTarget, target, this.conversionService.convert(value, TypeDescriptor.valueOf(String.class), TypeDescriptor.collection(getRawType(), TypeDescriptor.valueOf(actualTypeArguments))));
} else if (getRawType().isArray()) {
ParameterizedType genericParameterType = (ParameterizedType) this.invokeTarget.getGenericType();
Class<?> actualTypeArguments = (Class<?>) genericParameterType.getActualTypeArguments()[0];
ReflectionUtils.setField(this.invokeTarget, target, this.conversionService.convert(value, TypeDescriptor.valueOf(String.class), TypeDescriptor.array(TypeDescriptor.valueOf(actualTypeArguments))));
} else {
ReflectionUtils.setField(this.invokeTarget, target, this.conversionService.convert(value, getRawType()));
}
} catch (Exception e) {
ReflectionUtils.setField(this.invokeTarget, this.target, this.conversionService.convert(value, getRawType()));
}
}
}
MethodValueUpdate.java
package com.lhstack.custom.config.dynamic.value;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.List;
/**
* @author lhstack
* @date 2021/8/28
* @class FieldValueUPdate
* @since 1.8
*/
public class MethodValueUpdate extends AbstractValueUpdate<Method> {
public MethodValueUpdate(Object target, Method method, ConversionService conversionService) {
super(target, method, conversionService);
ReflectionUtils.makeAccessible(method);
}
@Override
public void doUpdate(String value) {
try {
if (getRawType().isAssignableFrom(List.class)) {
ParameterizedType genericParameterType = (ParameterizedType) this.invokeTarget.getGenericParameterTypes()[0];
Class<?> actualTypeArguments = (Class<?>) genericParameterType.getActualTypeArguments()[0];
ReflectionUtils.invokeMethod(this.invokeTarget, target, this.conversionService.convert(value, TypeDescriptor.valueOf(String.class), TypeDescriptor.collection(getRawType(), TypeDescriptor.valueOf(actualTypeArguments))));
} else if (getRawType().isArray()) {
ParameterizedType genericParameterType = (ParameterizedType) this.invokeTarget.getGenericParameterTypes()[0];
Class<?> actualTypeArguments = (Class<?>) genericParameterType.getActualTypeArguments()[0];
ReflectionUtils.invokeMethod(this.invokeTarget, target, this.conversionService.convert(value, TypeDescriptor.valueOf(String.class), TypeDescriptor.array(TypeDescriptor.valueOf(actualTypeArguments))));
} else {
ReflectionUtils.invokeMethod(this.invokeTarget, target, this.conversionService.convert(value, getRawType()));
}
} catch (Exception e) {
ReflectionUtils.invokeMethod(this.invokeTarget, target, this.conversionService.convert(value, getRawType()));
}
}
@Override
protected Class<?> getRawType() {
return this.invokeTarget.getParameterTypes()[0];
}
}
定义动态配置后置处理器
用于缓存需要动态刷新的字段或函数,并监听配置变更事件,实现配置的动态刷新
package com.lhstack.custom.config.dynamic;
import com.lhstack.custom.config.dynamic.value.AbstractValueUpdate;
import com.lhstack.custom.config.dynamic.value.FieldValueUpdate;
import com.lhstack.custom.config.dynamic.value.MethodValueUpdate;
import com.lhstack.custom.config.locator.event.AbstractEnvironmentEvent;
import com.lhstack.custom.config.locator.event.EnvironmentChangeEvent;
import com.lhstack.custom.config.locator.event.EnvironmentFinishEvent;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author lhstack
* @date 2021/8/27
* @class DynamicPropertyProcessor
* @since 1.8
*/
public class DynamicPropertyProcessor implements BeanPostProcessor, ApplicationListener<AbstractEnvironmentEvent> {
private final BeanDefinitionRegistry beanDefinitionRegistry;
private final AbstractEnvironment environment;
private Map<String, Map<String, Object>> propertyMap;
private static final Pattern PATTERN = Pattern.compile("^\\$\\{(?<value>.*)}$");
private static final Map<String, List<AbstractValueUpdate<?>>> PROPERTY_VALUE_UPDATE = new HashMap<>();
public DynamicPropertyProcessor(BeanDefinitionRegistry beanDefinitionRegistry, Environment environment) {
this.beanDefinitionRegistry = beanDefinitionRegistry;
this.environment = (AbstractEnvironment) environment;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
processField(bean, beanName);
processMethod(bean, beanName);
return bean;
}
private void processMethod(Object bean, String beanName) {
ReflectionUtils.doWithMethods(bean.getClass(), method -> {
DynamicValue annotation = AnnotationUtils.findAnnotation(method, DynamicValue.class);
Matcher matcher = PATTERN.matcher(annotation.value());
if (matcher.matches()) {
String value = matcher.group("value");
String[] split = value.split(":");
String defaultValue = split.length == 2 ? split[1] : "";
String key = split[0];
MethodValueUpdate methodValueUpdate = new MethodValueUpdate(bean, method, environment.getConversionService());
if (PROPERTY_VALUE_UPDATE.containsKey(key)) {
List<AbstractValueUpdate<?>> list = PROPERTY_VALUE_UPDATE.get(key);
list.add(methodValueUpdate);
} else {
List<AbstractValueUpdate<?>> list = new ArrayList<>();
list.add(methodValueUpdate);
PROPERTY_VALUE_UPDATE.put(key, list);
}
String property = environment.getProperty(key, defaultValue);
methodValueUpdate.update(property);
} else {
String value = environment.resolvePlaceholders(annotation.value());
method.setAccessible(true);
try {
method.invoke(bean, environment.getConversionService().convert(value, method.getParameterTypes()[0]));
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}, method -> AnnotationUtils.findAnnotation(method, DynamicValue.class) != null);
}
private void processField(Object bean, String beanName) {
ReflectionUtils.doWithFields(bean.getClass(), field -> {
DynamicValue annotation = AnnotationUtils.findAnnotation(field, DynamicValue.class);
Matcher matcher = PATTERN.matcher(annotation.value());
if (matcher.matches()) {
String value = matcher.group("value");
String[] split = value.split(":");
String defaultValue = split.length == 2 ? split[1] : "";
String key = split[0];
FieldValueUpdate fieldValueUpdate = new FieldValueUpdate(bean, field, environment.getConversionService());
if (PROPERTY_VALUE_UPDATE.containsKey(key)) {
List<AbstractValueUpdate<?>> list = PROPERTY_VALUE_UPDATE.get(key);
list.add(fieldValueUpdate);
} else {
List<AbstractValueUpdate<?>> list = new ArrayList<>();
list.add(fieldValueUpdate);
PROPERTY_VALUE_UPDATE.put(key, list);
}
String property = environment.getProperty(key, defaultValue);
fieldValueUpdate.update(property);
} else {
String value = environment.resolvePlaceholders(annotation.value());
field.setAccessible(true);
field.set(bean, environment.getConversionService().convert(value, field.getType()));
}
}, field -> AnnotationUtils.findAnnotation(field, DynamicValue.class) != null);
}
@Override
public void onApplicationEvent(AbstractEnvironmentEvent event) {
if (event instanceof EnvironmentFinishEvent) {
this.propertyMap = ((EnvironmentFinishEvent) event).getPropertyMap();
} else if (event instanceof EnvironmentChangeEvent) {
EnvironmentChangeEvent.EventType eventType = ((EnvironmentChangeEvent) event).getEventType();
switch (eventType) {
case CHANGE:
case ADD: {
Set<String> changeKeys = ((EnvironmentChangeEvent) event).getChangeKeys();
String name = ((EnvironmentChangeEvent) event).getName();
Map<String, Object> map = propertyMap.get(name);
changeKeys.forEach(item -> {
if (PROPERTY_VALUE_UPDATE.containsKey(item)) {
List<AbstractValueUpdate<?>> list = PROPERTY_VALUE_UPDATE.get(item);
list.forEach(e -> {
e.update(String.valueOf(map.get(item)));
});
}
});
}
break;
case REMOVE: {
Set<String> changeKeys = ((EnvironmentChangeEvent) event).getChangeKeys();
changeKeys.forEach(item -> {
if (PROPERTY_VALUE_UPDATE.containsKey(item)) {
List<AbstractValueUpdate<?>> list = PROPERTY_VALUE_UPDATE.get(item);
list.forEach(e -> {
e.update(null);
});
}
});
}
break;
default: {
}
}
}
}
}
修改PropertySourceLocatorProcessor
注册动态配置后置处理器,并对回调函数做幂等判断,保证locator以及环境这些参数之被初始化一次
package com.lhstack.custom.config.locator;
import com.lhstack.custom.config.dynamic.DynamicPropertyProcessor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.Environment;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author lhstack
* @date 2021/8/22
* @class CustomPropertySourcesPlaceholderConfigurer
* @since 1.8
*/
public class PropertySourceLocatorProcessor implements EnvironmentAware, BeanDefinitionRegistryPostProcessor, ApplicationContextAware {
private static AbstractEnvironment environment;
private static final AtomicBoolean IS_POST_PROCESS_BEAN_FACTORY_INITIALIZED = new AtomicBoolean(false);
/**
* 这里只需要保存一次environment就行了
*
* @param environment
*/
@Override
public void setEnvironment(Environment environment) {
if (Objects.nonNull(PropertySourceLocatorProcessor.environment)) {
return;
}
PropertySourceLocatorProcessor.environment = (AbstractEnvironment) environment;
}
/**
* 这里locator也只需要执行一次
*
* @param beanFactory
* @throws BeansException
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (!IS_POST_PROCESS_BEAN_FACTORY_INITIALIZED.get()) {
IS_POST_PROCESS_BEAN_FACTORY_INITIALIZED.compareAndSet(false, true);
try {
Map<String, PropertySourceLocator> beansOfType = beanFactory.getBeansOfType(PropertySourceLocator.class);
ApplicationPropertySourceLocator applicationPropertySourceLocator = new ApplicationPropertySourceLocator();
applicationPropertySourceLocator.initEnvironment(environment, beanFactory);
applicationPropertySourceLocator.locator().forEach(propertySource -> environment.getPropertySources().addLast(propertySource));
beansOfType.values().forEach(item -> {
item.initEnvironment(environment, beanFactory);
});
for (PropertySourceLocator value : beansOfType.values()) {
value.locator().forEach(e -> {
environment.getPropertySources().addLast(e);
});
}
} catch (Exception e) {
e.printStackTrace();
}
}
//这里每次都需要执行,注入每次的beanfactory里面
//定义PropertySourcesPlaceholderConfigurer,用于解析标签里面spel表达式
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = null;
try {
propertySourcesPlaceholderConfigurer = beanFactory.getBean(PropertySourcesPlaceholderConfigurer.class);
} catch (Exception e) {
propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
}
propertySourcesPlaceholderConfigurer.setEnvironment(environment);
propertySourcesPlaceholderConfigurer.setPropertySources(environment.getPropertySources());
propertySourcesPlaceholderConfigurer.postProcessBeanFactory(beanFactory);
}
/**
* 注册动态参数后置处理器
*
* @param beanDefinitionRegistry
*/
private void registryDynamicPropertyProcessor(BeanDefinitionRegistry beanDefinitionRegistry) {
if (!beanDefinitionRegistry.containsBeanDefinition(DynamicPropertyProcessor.class.getName())) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicPropertyProcessor.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, beanDefinitionRegistry);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(1, environment);
beanDefinitionRegistry.registerBeanDefinition(DynamicPropertyProcessor.class.getName(), beanDefinition);
}
}
/**
* 这里也只需要注册一次
*
* @param beanDefinitionRegistry
* @throws BeansException
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
registryDynamicPropertyProcessor(beanDefinitionRegistry);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, PropertySourceLocator> beansOfType = applicationContext.getBeansOfType(PropertySourceLocator.class);
beansOfType.values().forEach(item -> {
item.contextRefreshEventListener(applicationContext);
});
}
}
修改测试controller,加入用于标记动态配置的注解
package com.lhstack.custom.config.controller;
import com.lhstack.custom.config.dynamic.DynamicValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @author lhstack
* @date 2021/8/22
* @class TestController
* @since 1.8
*/
@RestController
@RequestMapping
public class TestController {
private String name;
private int value1;
@DynamicValue("${spring.application.array}")
private List<Double> arrays;
@DynamicValue("${spring.application.custom-config-dev}")
private String value2;
@DynamicValue("${spring.application}")
private String value3;
@DynamicValue("${spring.application-dev}")
private String value4;
@Autowired
private Environment environment;
@Autowired
private TestEnv testEnv;
@DynamicValue("${spring.application.name}")
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
@DynamicValue("${spring.application.custom-config}")
public void setValue1(int value1) {
this.value1 = value1;
}
public int getValue1() {
return value1;
}
@GetMapping("key")
public String test(@RequestParam(name = "key") String key) {
return environment.getProperty(key);
}
@GetMapping("test")
public String test1() {
return value1 + ":" + value2 + ":" + value3 + ":" + value4 + ":" + arrays;
}
@GetMapping
public String test() {
return name + "-" + testEnv.getValue();
}
public static class TestEnv {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
}
启动项目,查看加载的配置
修改配置